From 64d1e817d1726116abe148dc9ee02da34fce488a Mon Sep 17 00:00:00 2001 From: "byte[]" Date: Sat, 30 Nov 2019 17:40:53 -0500 Subject: [PATCH] settings page --- lib/philomena/filters/filter.ex | 92 ++------------ lib/philomena/image_scope.ex | 10 +- lib/philomena/schema/search.ex | 18 +++ lib/philomena/schema/tag_list.ex | 52 ++++++++ lib/philomena/users.ex | 6 + lib/philomena/users/user.ex | 36 +++++- .../controllers/activity_controller.ex | 4 +- .../controllers/channel_controller.ex | 7 +- .../controllers/filter_controller.ex | 7 +- .../controllers/image/comment_controller.ex | 2 +- .../controllers/image_controller.ex | 4 +- .../controllers/search_controller.ex | 2 +- .../controllers/setting_controller.ex | 59 +++++++++ .../controllers/tag_controller.ex | 2 +- lib/philomena_web/plugs/pagination_plug.ex | 12 +- lib/philomena_web/router.ex | 1 + .../image/_image_container.html.slime | 37 +++--- .../templates/image/_image_meta.html.slime | 6 +- .../templates/layout/app.html.slime | 2 +- .../templates/search/index.html.slime | 2 +- .../templates/setting/edit.html.slime | 118 ++++++++++++++++++ lib/philomena_web/views/image_view.ex | 43 +++++++ lib/philomena_web/views/layout_view.ex | 3 + lib/philomena_web/views/setting_view.ex | 18 +++ .../controllers/setting_controller_test.exs | 88 +++++++++++++ 25 files changed, 516 insertions(+), 115 deletions(-) create mode 100644 lib/philomena/schema/search.ex create mode 100644 lib/philomena/schema/tag_list.ex create mode 100644 lib/philomena_web/controllers/setting_controller.ex create mode 100644 lib/philomena_web/templates/setting/edit.html.slime create mode 100644 lib/philomena_web/views/setting_view.ex create mode 100644 test/philomena_web/controllers/setting_controller_test.exs diff --git a/lib/philomena/filters/filter.ex b/lib/philomena/filters/filter.ex index 2414c22c..d14f991e 100644 --- a/lib/philomena/filters/filter.ex +++ b/lib/philomena/filters/filter.ex @@ -1,10 +1,9 @@ defmodule Philomena.Filters.Filter do use Ecto.Schema + import Philomena.Schema.TagList + import Philomena.Schema.Search import Ecto.Changeset - import Ecto.Query - alias Philomena.Tags.Tag - alias Philomena.Images.Query alias Philomena.Users.User alias Philomena.Repo @@ -29,67 +28,21 @@ defmodule Philomena.Filters.Filter do @doc false def changeset(filter, attrs) do + user = + filter + |> Repo.preload(:user) + |> Map.get(:user) + filter |> cast(attrs, [:spoilered_tag_list, :hidden_tag_list, :description, :name, :spoilered_complex_str, :hidden_complex_str]) - |> propagate_tag_lists() + |> propagate_tag_list(:spoilered_tag_list, :spoilered_tag_ids) + |> propagate_tag_list(:hidden_tag_list, :hidden_tag_ids) |> validate_required([:name, :description]) - |> unsafe_validate_unique([:user_id, :name], Repo) |> validate_my_downvotes(:spoilered_complex_str) |> validate_my_downvotes(:hidden_complex_str) - |> validate_search(:spoilered_complex_str) - |> validate_search(:hidden_complex_str) - end - - def assign_tag_lists(filter) do - tags = Enum.uniq(filter.spoilered_tag_ids ++ filter.hidden_tag_ids) - - lookup = - Tag - |> where([t], t.id in ^tags) - |> Repo.all() - |> Map.new(fn t -> {t.id, t.name} end) - - spoilered_tag_list = - filter.spoilered_tag_ids - |> Enum.map(&lookup[&1]) - |> Enum.filter(& &1 != nil) - |> Enum.sort() - |> Enum.join(", ") - - hidden_tag_list = - filter.hidden_tag_ids - |> Enum.map(&lookup[&1]) - |> Enum.filter(& &1 != nil) - |> Enum.sort() - |> Enum.join(", ") - - %{filter | hidden_tag_list: hidden_tag_list, spoilered_tag_list: spoilered_tag_list} - end - - defp propagate_tag_lists(changeset) do - spoilers = get_field(changeset, :spoilered_tag_list) |> parse_tag_list - filters = get_field(changeset, :hidden_tag_list) |> parse_tag_list - tags = Enum.uniq(spoilers ++ filters) - - lookup = - Tag - |> where([t], t.name in ^tags) - |> Repo.all() - |> Map.new(fn t -> {t.name, t.id} end) - - spoilered_tag_ids = - spoilers - |> Enum.map(&lookup[&1]) - |> Enum.filter(& &1 != nil) - - hidden_tag_ids = - filters - |> Enum.map(&lookup[&1]) - |> Enum.filter(& &1 != nil) - - changeset - |> put_change(:spoilered_tag_ids, spoilered_tag_ids) - |> put_change(:hidden_tag_ids, hidden_tag_ids) + |> validate_search(:spoilered_complex_str, user) + |> validate_search(:hidden_complex_str, user) + |> unsafe_validate_unique([:user_id, :name], Repo) end defp validate_my_downvotes(changeset, field) do @@ -102,25 +55,4 @@ defmodule Philomena.Filters.Filter do changeset end end - - defp validate_search(changeset, field) do - user_id = get_field(changeset, :user_id) - user = if user_id, do: User |> Repo.get!(user_id) - - output = Query.compile(user, get_field(changeset, field)) - - case output do - {:ok, _} -> changeset - _ -> - changeset - |> add_error(field, "is invalid") - end - end - - defp parse_tag_list(list) do - (list || "") - |> String.split(",") - |> Enum.map(&String.trim(&1)) - |> Enum.filter(& &1 != "") - end end diff --git a/lib/philomena/image_scope.ex b/lib/philomena/image_scope.ex index b7f07885..97d56ec2 100644 --- a/lib/philomena/image_scope.ex +++ b/lib/philomena/image_scope.ex @@ -1,16 +1,16 @@ defmodule Philomena.ImageScope do def scope(conn) do [] - |> scope(conn, "q") - |> scope(conn, "sf") - |> scope(conn, "sd") + |> scope(conn, "q", :q) + |> scope(conn, "sf", :sf) + |> scope(conn, "sd", :sf) end - defp scope(list, conn, key) do + defp scope(list, conn, key, key_atom) do case conn.params[key] do nil -> list "" -> list - val -> [{key, val} | list] + val -> [{key_atom, val} | list] end end end \ No newline at end of file diff --git a/lib/philomena/schema/search.ex b/lib/philomena/schema/search.ex new file mode 100644 index 00000000..0f67bffb --- /dev/null +++ b/lib/philomena/schema/search.ex @@ -0,0 +1,18 @@ +defmodule Philomena.Schema.Search do + alias Philomena.Images.Query + import Search.String + import Ecto.Changeset + + def validate_search(changeset, field, user, watched \\ false) do + query = changeset |> get_field(field) |> normalize() + output = Query.compile(user, query, watched) + + case output do + {:ok, _} -> + changeset + + _ -> + add_error(changeset, field, "is invalid") + end + end +end \ No newline at end of file diff --git a/lib/philomena/schema/tag_list.ex b/lib/philomena/schema/tag_list.ex new file mode 100644 index 00000000..d7224a64 --- /dev/null +++ b/lib/philomena/schema/tag_list.ex @@ -0,0 +1,52 @@ +defmodule Philomena.Schema.TagList do + # TODO: remove this in favor of normalized relations + alias Philomena.Tags.Tag + alias Philomena.Repo + import Ecto.Changeset + import Ecto.Query + + def assign_tag_list(model, field, target_field) do + tags = model |> Map.get(field) |> Enum.uniq() + + lookup = + Tag + |> where([t], t.id in ^tags) + |> order_by(asc: :name) + |> Repo.all() + |> Map.new(fn t -> {t.id, t.name} end) + + tag_list = + model + |> Map.get(field) + |> Enum.map(&lookup[&1]) + |> Enum.reject(&is_nil/1) + |> Enum.join(", ") + + %{model | target_field => tag_list} + end + + def propagate_tag_list(changeset, field, target_field) do + tag_list = changeset |> get_field(field) |> parse_tag_list() + + lookup = + Tag + |> where([t], t.name in ^tag_list) + |> Repo.all() + |> Map.new(fn t -> {t.name, t.id} end) + + tag_ids = + tag_list + |> Enum.map(&lookup[&1]) + |> Enum.reject(&is_nil/1) + + changeset + |> put_change(target_field, tag_ids) + end + + defp parse_tag_list(list) do + (list || "") + |> String.split(",") + |> Enum.map(&String.trim(&1)) + |> Enum.filter(& &1 != "") + end +end \ No newline at end of file diff --git a/lib/philomena/users.ex b/lib/philomena/users.ex index 6288171a..f4b2cdbb 100644 --- a/lib/philomena/users.ex +++ b/lib/philomena/users.ex @@ -83,6 +83,12 @@ defmodule Philomena.Users do |> Repo.update() end + def update_settings(%User{} = user, attrs) do + user + |> User.settings_changeset(attrs) + |> Repo.update() + end + @doc """ Returns an `%Ecto.Changeset{}` for tracking user changes. diff --git a/lib/philomena/users/user.ex b/lib/philomena/users/user.ex index c0672aed..7fc27bfd 100644 --- a/lib/philomena/users/user.ex +++ b/lib/philomena/users/user.ex @@ -12,6 +12,8 @@ defmodule Philomena.Users.User do extensions: [PowResetPassword, PowLockout] import Ecto.Changeset + import Philomena.Schema.TagList + import Philomena.Schema.Search alias Philomena.Filters.Filter alias Philomena.UserLinks.UserLink @@ -106,6 +108,7 @@ defmodule Philomena.Users.User do # Poorly denormalized associations field :recent_filter_ids, {:array, :integer}, default: [] field :watched_tag_ids, {:array, :integer}, default: [] + field :watched_tag_list, :string, virtual: true # Other stuff field :last_donation_at, :naive_datetime @@ -158,6 +161,28 @@ defmodule Philomena.Users.User do |> validate_inclusion(:spoiler_type, ~W(static click hover off)) end + def settings_changeset(user, attrs) do + user + |> cast(attrs, [ + :watched_tag_list, :images_per_page, :fancy_tag_field_on_upload, + :fancy_tag_field_on_edit, :anonymous_by_default, :scale_large_images, + :comments_per_page, :theme, :watched_images_query_str, + :no_spoilered_in_watched, :watched_images_exclude_str, + :use_centered_layout, :hide_vote_counts + ]) + |> validate_required([ + :images_per_page, :fancy_tag_field_on_upload, :fancy_tag_field_on_edit, + :anonymous_by_default, :scale_large_images, :comments_per_page, :theme, + :no_spoilered_in_watched, :use_centered_layout, :hide_vote_counts + ]) + |> propagate_tag_list(:watched_tag_list, :watched_tag_ids) + |> validate_inclusion(:theme, ~W(default dark red)) + |> validate_inclusion(:images_per_page, 15..50) + |> validate_inclusion(:comments_per_page, 15..50) + |> validate_search(:watched_images_query_str, user, true) + |> validate_search(:watched_images_exclude_str, user, true) + end + def create_totp_secret_changeset(user) do secret = :crypto.strong_rand_bytes(15) |> Base.encode32() data = Philomena.Users.Encryptor.encrypt_model(secret) @@ -288,8 +313,15 @@ defmodule Philomena.Users.User do |> put_change(:slug, Slug.slug(name)) end - defp totp_valid?(user, token), - do: :pot.valid_totp(token, totp_secret(user), window: 1) + defp totp_valid?(user, token) do + case Integer.parse(token) do + {int_token, _rest} -> + int_token != user.consumed_timestep and :pot.valid_totp(token, totp_secret(user), window: 1) + + _error -> + false + end + end defp backup_code_valid?(user, token), do: Enum.any?(user.otp_backup_codes, &Password.verify_pass(token, &1)) diff --git a/lib/philomena_web/controllers/activity_controller.ex b/lib/philomena_web/controllers/activity_controller.ex index c09c8e27..f2c8897b 100644 --- a/lib/philomena_web/controllers/activity_controller.ex +++ b/lib/philomena_web/controllers/activity_controller.ex @@ -26,7 +26,7 @@ defmodule PhilomenaWeb.ActivityController do }, sort: %{created_at: :desc} }, - %{page_number: 1, page_size: 25}, + %{conn.assigns.image_pagination | page_number: 1}, Image |> preload([:tags]) ) @@ -84,7 +84,7 @@ defmodule PhilomenaWeb.ActivityController do }, sort: %{created_at: :desc} }, - %{page_number: 1, page_size: 25}, + %{conn.assigns.image_pagination | page_number: 1}, Image |> preload([:tags]) ) end diff --git a/lib/philomena_web/controllers/channel_controller.ex b/lib/philomena_web/controllers/channel_controller.ex index a4f658d9..a9b32338 100644 --- a/lib/philomena_web/controllers/channel_controller.ex +++ b/lib/philomena_web/controllers/channel_controller.ex @@ -9,9 +9,11 @@ defmodule PhilomenaWeb.ChannelController do plug :load_resource, model: Channel def index(conn, _params) do + show_nsfw? = conn.cookies["chan_nsfw"] == "true" channels = Channel - |> where([c], c.nsfw == false and not is_nil(c.last_fetched_at)) + |> maybe_show_nsfw(show_nsfw?) + |> where([c], not is_nil(c.last_fetched_at)) |> order_by(desc: :is_live, asc: :title) |> preload(:associated_artist_tag) |> Repo.paginate(conn.assigns.scrivener) @@ -28,6 +30,9 @@ defmodule PhilomenaWeb.ChannelController do redirect(conn, external: url(channel)) end + defp maybe_show_nsfw(query, true), do: query + defp maybe_show_nsfw(query, _falsy), do: where(query, [c], c.nsfw == false) + defp url(%{type: "LivestreamChannel", short_name: short_name}), do: "http://www.livestream.com/#{short_name}" defp url(%{type: "PicartoChannel", short_name: short_name}), diff --git a/lib/philomena_web/controllers/filter_controller.ex b/lib/philomena_web/controllers/filter_controller.ex index 36c571f3..dbf76432 100644 --- a/lib/philomena_web/controllers/filter_controller.ex +++ b/lib/philomena_web/controllers/filter_controller.ex @@ -2,6 +2,7 @@ defmodule PhilomenaWeb.FilterController do use PhilomenaWeb, :controller alias Philomena.{Filters, Filters.Filter, Tags.Tag} + alias Philomena.Schema.TagList alias Philomena.Repo import Ecto.Query @@ -67,7 +68,11 @@ defmodule PhilomenaWeb.FilterController do end def edit(conn, _params) do - filter = conn.assigns.filter |> Filter.assign_tag_lists() + filter = + conn.assigns.filter + |> TagList.assign_tag_list(:spoilered_tag_ids, :spoilered_tag_list) + |> TagList.assign_tag_list(:hidden_tag_ids, :hidden_tag_list) + changeset = Filters.change_filter(filter) render(conn, "edit.html", filter: filter, changeset: changeset) diff --git a/lib/philomena_web/controllers/image/comment_controller.ex b/lib/philomena_web/controllers/image/comment_controller.ex index 46e1a430..cbf7a048 100644 --- a/lib/philomena_web/controllers/image/comment_controller.ex +++ b/lib/philomena_web/controllers/image/comment_controller.ex @@ -43,7 +43,7 @@ defmodule PhilomenaWeb.Image.CommentController do |> where(image_id: ^conn.assigns.image.id) |> order_by(desc: :created_at) |> preload([:image, user: [awards: :badge]]) - |> Repo.paginate(conn.assigns.scrivener) + |> Repo.paginate(conn.assigns.comment_scrivener) rendered = comments.entries diff --git a/lib/philomena_web/controllers/image_controller.ex b/lib/philomena_web/controllers/image_controller.ex index 2dac0d25..c5668697 100644 --- a/lib/philomena_web/controllers/image_controller.ex +++ b/lib/philomena_web/controllers/image_controller.ex @@ -26,7 +26,7 @@ defmodule PhilomenaWeb.ImageController do query: %{bool: %{must_not: [query, %{term: %{hidden_from_users: true}}]}}, sort: %{created_at: :desc} }, - conn.assigns.pagination, + conn.assigns.image_pagination, Image |> preload([:tags, :user]) ) @@ -45,7 +45,7 @@ defmodule PhilomenaWeb.ImageController do |> preload([:image, user: [awards: :badge]]) |> order_by(desc: :created_at) |> limit(25) - |> Repo.paginate(conn.assigns.scrivener) + |> Repo.paginate(conn.assigns.comment_scrivener) rendered = comments.entries diff --git a/lib/philomena_web/controllers/search_controller.ex b/lib/philomena_web/controllers/search_controller.ex index c6a94d5e..71c42d4a 100644 --- a/lib/philomena_web/controllers/search_controller.ex +++ b/lib/philomena_web/controllers/search_controller.ex @@ -19,7 +19,7 @@ defmodule PhilomenaWeb.SearchController do query: %{bool: %{must: [query | sort.queries], must_not: [filter, %{term: %{hidden_from_users: true}}]}}, sort: sort.sorts }, - conn.assigns.pagination, + conn.assigns.image_pagination, Image |> preload(:tags) ) diff --git a/lib/philomena_web/controllers/setting_controller.ex b/lib/philomena_web/controllers/setting_controller.ex new file mode 100644 index 00000000..426a80a2 --- /dev/null +++ b/lib/philomena_web/controllers/setting_controller.ex @@ -0,0 +1,59 @@ +defmodule PhilomenaWeb.SettingController do + use PhilomenaWeb, :controller + + alias Philomena.Users + alias Philomena.Users.User + alias Philomena.Schema.TagList + alias Plug.Conn + + def edit(conn, _params) do + changeset = + (conn.assigns.current_user || %User{}) + |> TagList.assign_tag_list(:watched_tag_ids, :watched_tag_list) + |> Users.change_user() + + render(conn, "edit.html", changeset: changeset) + end + + def update(conn, %{"user" => user_params}) do + user = conn.assigns.current_user + + conn + |> update_local_settings(user_params) + |> maybe_update_user(user, user_params) + |> case do + {:ok, conn} -> + conn + |> put_flash(:info, "Settings updated successfully.") + |> redirect(to: Routes.setting_path(conn, :edit)) + + {:error, changeset} -> + conn + |> put_flash(:error, "Your settings could not be saved!") + |> render("edit.html", changeset: changeset) + end + end + + defp update_local_settings(conn, user_params) do + conn + |> set_cookie(user_params, "hidpi", "hidpi") + |> set_cookie(user_params, "webm", "webm") + |> set_cookie(user_params, "chan_nsfw", "chan_nsfw") + end + + defp set_cookie(conn, params, param_name, cookie_name) do + # JS wants access; max-age is set to 25 years from now + Conn.put_resp_cookie(conn, cookie_name, to_string(params[param_name] == "true"), max_age: 788_923_800, http_only: false) + end + + defp maybe_update_user(conn, nil, _user_params), do: {:ok, conn} + defp maybe_update_user(conn, user, user_params) do + case Users.update_settings(user, user_params) do + {:ok, _user} -> + {:ok, conn} + + error -> + error + end + end +end \ No newline at end of file diff --git a/lib/philomena_web/controllers/tag_controller.ex b/lib/philomena_web/controllers/tag_controller.ex index 12caa426..154a0cfd 100644 --- a/lib/philomena_web/controllers/tag_controller.ex +++ b/lib/philomena_web/controllers/tag_controller.ex @@ -50,7 +50,7 @@ defmodule PhilomenaWeb.TagController do }, sort: %{created_at: :desc} }, - conn.assigns.pagination, + conn.assigns.image_pagination, Image |> preload([:tags, :user]) ) diff --git a/lib/philomena_web/plugs/pagination_plug.ex b/lib/philomena_web/plugs/pagination_plug.ex index 71930e0f..299742a6 100644 --- a/lib/philomena_web/plugs/pagination_plug.ex +++ b/lib/philomena_web/plugs/pagination_plug.ex @@ -1,12 +1,14 @@ defmodule PhilomenaWeb.PaginationPlug do import Plug.Conn + alias Pow.Plug # No options - def init([]), do: false + def init([]), do: [] # Assign pagination info def call(conn, _opts) do conn = conn |> fetch_query_params() + user = conn |> Plug.current_user() params = conn.params page_number = @@ -31,6 +33,14 @@ defmodule PhilomenaWeb.PaginationPlug do conn |> assign(:pagination, %{page_number: page_number, page_size: page_size}) + |> assign(:image_pagination, %{page_number: page_number, page_size: image_page_size(user)}) |> assign(:scrivener, [page: page_number, page_size: page_size]) + |> assign(:comment_scrivener, [page: page_number, page_size: comment_page_size(user)]) end + + defp image_page_size(%{images_per_page: x}), do: x + defp image_page_size(_user), do: 15 + + defp comment_page_size(%{comments_per_page: x}), do: x + defp comment_page_size(_user), do: 25 end diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index c5f2e281..f5cb17b7 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -136,6 +136,7 @@ defmodule PhilomenaWeb.Router do resources "/staff", StaffController, only: [:index] resources "/stats", StatController, only: [:index] resources "/channels", ChannelController, only: [:index, :show] + resources "/settings", SettingController, only: [:edit, :update], singleton: true get "/:id", ImageController, :show # get "/:forum_id", ForumController, :show # impossible to do without constraints diff --git a/lib/philomena_web/templates/image/_image_container.html.slime b/lib/philomena_web/templates/image/_image_container.html.slime index 4a5c4163..f951c24d 100644 --- a/lib/philomena_web/templates/image/_image_container.html.slime +++ b/lib/philomena_web/templates/image/_image_container.html.slime @@ -16,19 +16,28 @@ .media-box__overlay.js-spoiler-info-overlay a href=link - = if @image.thumbnails_generated do - - uris = thumb_urls(@image, false) - - vid = @image.image_mime_type == "video/webm" - - tags = Enum.map(@image.tags, & &1.name) |> Enum.sort() |> Enum.join(", ") - - alt = "Size: #{@image.image_width}x#{@image.image_height} | Tagged: #{tags}" - - = if vid do - video alt=alt autoplay="autoplay" muted="muted" loop="loop" playsinline="playsinline" - source src=uris[@size] type="video/webm" - source src=String.replace(uris[@size], ".webm", ".mp4") type="video/mp4" - - else + = case render_intent(@conn, @image, @size) do + - {:hidpi, small_url, medium_url, hover_text} -> picture - img alt=alt src=thumb_url(@image, false, @size) srcset="#{uris[@size]} 1x, #{uris[:medium]} 2x" + img src=small_url srcset="#{small_url} 1x, #{medium_url} 2x" alt=hover_text - - else - | Thumbnails not yet generated + - {:image, small_url, hover_text} -> + picture + img src=small_url alt=hover_text + + - {:video, webm, mp4, hover_text} -> + video alt=hover_text autoplay="autoplay" muted="muted" loop="loop" playsinline="playsinline" + source src=webm type="video/webm" + source src=mp4 type="video/mp4" + img alt=hover_text + + - {:filtered_image, hover_text} -> + picture + img alt=hover_text + + - {:filtered_video, hover_text} + video autoplay="autoplay" muted="muted" loop="loop" playsinline="playsinline" + img alt=hover_text + + - :not_rendered -> + ' Thumbnails not yet generated \ No newline at end of file diff --git a/lib/philomena_web/templates/image/_image_meta.html.slime b/lib/philomena_web/templates/image/_image_meta.html.slime index 44e7b8a3..59f0875f 100644 --- a/lib/philomena_web/templates/image/_image_meta.html.slime +++ b/lib/philomena_web/templates/image/_image_meta.html.slime @@ -15,14 +15,16 @@ span.fave-span title="Fave!" i.fa.fa-star a.interaction--upvote href="#" rel="nofollow" data-image-id=@image.id - span.upvotes> title="Upvotes" data-image-id=@image.id = @image.upvotes_count + = if show_vote_counts?(@conn.assigns.current_user) do + span.upvotes> title="Upvotes" data-image-id=@image.id = @image.upvotes_count span.upvote-span title="Yay!" i.fa.fa-arrow-up span.score.block__header__title data-image-id=@image.id = @image.score a.interaction--downvote href="#" rel="nofollow" data-image-id=@image.id span.downvote-span title="Neigh!" i.fa.fa-arrow-down - span.downvotes< title="Downvotes" data-image-id=@image.id = @image.downvotes_count + = if show_vote_counts?(@conn.assigns.current_user) do + span.downvotes< title="Downvotes" data-image-id=@image.id = @image.downvotes_count a.interaction--comments href="#comments" title="Comments" i.fa.fa-comments span.comments_count< data-image-id=@image.id = @image.comments_count diff --git a/lib/philomena_web/templates/layout/app.html.slime b/lib/philomena_web/templates/layout/app.html.slime index 66a0e825..9f60cc95 100644 --- a/lib/philomena_web/templates/layout/app.html.slime +++ b/lib/philomena_web/templates/layout/app.html.slime @@ -21,7 +21,7 @@ html lang="en" script type="text/javascript" src=Routes.static_path(@conn, "/js/app.js") async="async" body data-theme="default" = render PhilomenaWeb.LayoutView, "_burger.html", assigns - #container + #container class=container_class(@current_user) = render PhilomenaWeb.LayoutView, "_header.html", assigns = render PhilomenaWeb.LayoutView, "_flash_warnings.html", assigns main#content class=layout_class(@conn) diff --git a/lib/philomena_web/templates/search/index.html.slime b/lib/philomena_web/templates/search/index.html.slime index 2e84196b..5ed08e72 100644 --- a/lib/philomena_web/templates/search/index.html.slime +++ b/lib/philomena_web/templates/search/index.html.slime @@ -1,6 +1,6 @@ = cond do - Enum.any?(@images) -> - = render PhilomenaWeb.ImageView, "index.html", conn: @conn, images: @images, route: fn p -> Routes.search_path(@conn, :index, p) end, scope: [q: @conn.params["q"], sf: @conn.params["sf"], sd: @conn.params["sd"]] + = render PhilomenaWeb.ImageView, "index.html", conn: @conn, images: @images, route: fn p -> Routes.search_path(@conn, :index, p) end, scope: scope(@conn) - assigns[:error] -> p ' Oops, there was an error evaluating your query: diff --git a/lib/philomena_web/templates/setting/edit.html.slime b/lib/philomena_web/templates/setting/edit.html.slime new file mode 100644 index 00000000..4a680aad --- /dev/null +++ b/lib/philomena_web/templates/setting/edit.html.slime @@ -0,0 +1,118 @@ +h1 Content Settings += form_for @changeset, Routes.setting_path(@conn, :update), [method: "put"], fn f -> + = if @changeset.action do + .alert.alert-danger + p Oops, something went wrong! Please check the errors below. + + #js-setting-table.block + .block__header.block__header--js-tabbed + = if @conn.assigns.current_user do + = link "Watch List", to: "#", class: "selected", data: [click_tab: "watched"] + = link "Display", to: "#", data: [click_tab: "display"] + = link "Metadata", to: "#", data: [click_tab: "metadata"] + = link "Local", to: "#", data: [click_tab: "local"] + - else + = link "Local", to: "#", class: "selected", data: [click_tab: "local"] + = link "More settings", to: "#", data: [click_tab: "join-the-herd"] + + = if @conn.assigns.current_user do + .block__tab data-tab="watched" + h4 Tags + .field + = label f, :watched_tag_list, "Tags to watch" + = render PhilomenaWeb.TagView, "_tag_editor.html", f: f, name: :watched_tag_list, type: :edit, conn: @conn + + h4 Watchlist queries and filtering + p + ' The following two areas are for search queries to control what other images show up in your watch list. Lines are ORed together. See + => link "the syntax guide", to: "/pages/search_syntax" + ' for how to write queries. + + .field + = label f, :watched_images_query_str, "Watch list search string (images found by this search are added to your watched images list)" + = textarea f, :watched_images_query_str, class: "input input--wide", autocapitalize: "none" + .field + = label f, :watched_images_exclude_str, "Watch list filter string (any images found by this search are removed from your watched images list)" + = textarea f, :watched_images_exclude_str, class: "input input--wide", autocapitalize: "none" + .field + => checkbox f, :no_spoilered_in_watched, class: "checkbox" + => label f, :no_spoilered_in_watched, "Hide images spoilered by filter in watchlist" + + .block__tab.hidden.flex.flex--maybe-wrap data-tab="display" + div + .field + => label f, :use_centered_layout + => checkbox f, :use_centered_layout, class: "checkbox" + .fieldlabel: i Align content to the center of the page - try this option out if you browse the site on a tablet or a fairly wide screen. + .field + => label f, :hide_vote_counts + => checkbox f, :hide_vote_counts, class: "checkbox" + .fieldlabel: i Hide upvote and downvote counts on images, showing only the overall score + .field + => label f, :images_per_page + => number_input f, :images_per_page, min: 15, max: 50, step: 1, class: "input" + .fieldlabel + i + ' This is the number of images per page that are displayed on image listings and searches, up to a maximum of 50. + ' For 1080p monitors, try 24. + .field + => label f, :comments_per_page + => number_input f, :comments_per_page, min: 15, max: 50, step: 1, class: "input" + .fieldlabel: i This is the number of comments per page that are displayed on image pages. + .field + => label f, :scale_large_images + => checkbox f, :scale_large_images, class: "checkbox" + .fieldlabel: i Scale large images down to fit your monitor (approximately) server-side before downloading. Disabling this will load full images immediately on image pages. + .field + => label f, :theme + => select f, :theme, theme_options(@conn), class: "input" + .fieldlabel: i Preview themes by selecting one from the dropdown. Saving sets the currently selected theme. + + .block__tab.hidden.flex.flex--maybe-wrap data-tab="metadata" + div + .field + => label f, :fancy_tag_field_on_upload, "Fancy tags - uploads" + => checkbox f, :fancy_tag_field_on_upload, class: "checkbox" + .field + => label f, :fancy_tag_field_on_edit, "Fancy tags - edits" + => checkbox f, :fancy_tag_field_on_edit, class: "checkbox" + .fieldlabel: i The fancy tag editor gives you autosuggestions and visual representations of the tags, but is sometimes not desired - for instance when dealing with batch uploads where you might want to copy-paste tags. You can choose which type of editor to use by default here. + .field + => label f, :anonymous_by_default + => checkbox f, :anonymous_by_default, class: "checkbox" + .fieldlabel: i Check this box to post images and comments as anonymous by default, even if logged in. + + .block__tab class=local_tab_class(@conn) data-tab="local" + .block.block--fixed.block--warning Settings on this tab are saved in the current browser. They are independent of your login. + .field + => label f, :hidpi, "Serve HiDPI thumbnails" + => checkbox f, :hidpi + .fieldlabel: i Use high quality thumbnails on displays with a high pixel density. Requires more data than regular thumbnails. + .field + => label f, :serve_webm, "Serve WebM" + => checkbox f, :serve_webm + .fieldlabel: i Serve WebM/MP4 versions of GIF images when available. Good for lower-bandwidth connections, but the video versions may have missing start/end frames, and do not support transparency. + .field + => label f, :webm, "Use video thumbnails" + => checkbox f, :webm + .fieldlabel: i Use video thumbnails for WebM videos. Does not apply to GIF images. + .field + => label f, :hide_uploader + => checkbox f, :hide_uploader + .fieldlabel: i Hide the uploader and date posted information on image pages. + .field + => label f, :chan_nsfw, "Show NSFW channels" + => checkbox f, :chan_nsfw + .fieldlabel: i Show streams marked as NSFW on the channels page. + + = if !@conn.assigns.current_user do + .block__tab.hidden data-tab="join-the-herd" + p + ' Consider + => link "creating an account!", to: Routes.pow_registration_path(@conn, :new) + br + ' You will be able to customize the number of images and comments you get on a single page, as well as change the appearance of the site with custom themes. + + br + = submit "Save My Settings", class: "button" + br \ No newline at end of file diff --git a/lib/philomena_web/views/image_view.ex b/lib/philomena_web/views/image_view.ex index 6e291f6a..0485c41e 100644 --- a/lib/philomena_web/views/image_view.ex +++ b/lib/philomena_web/views/image_view.ex @@ -2,6 +2,35 @@ defmodule PhilomenaWeb.ImageView do use PhilomenaWeb, :view alias Philomena.Tags.Tag + alias Plug.Conn + + def show_vote_counts?(%{hide_vote_counts: true}), do: false + def show_vote_counts?(_user), do: true + + # this is a bit ridculous + def render_intent(conn, %{thumbnails_generated: false}, _size), do: :not_rendered + def render_intent(conn, image, size) do + uris = thumb_urls(image, false) + vid? = image.image_mime_type == "video/webm" + gif? = image.image_mime_type == "image/gif" + tags = Tag.display_order(image.tags) |> Enum.map_join(", ", & &1.name) + alt = "Size: #{image.image_width}x#{image.image_height} | Tagged: #{tags}" + + hidpi? = conn.cookies["hidpi"] == "true" + webm? = conn.cookies["webm"] == "true" + use_gif? = vid? and not webm? and size in ~W(thumb thumb_small thumb_tiny)a + + cond do + hidpi? and not (gif? or vid?) -> + {:hidpi, uris[size], uris[:medium], alt} + + not vid? or use_gif? -> + {:image, String.replace(uris[size], ".webm", ".gif"), alt} + + true -> + {:video, uris[size], String.replace(uris[size], ".webm", ".mp4"), alt} + end + end def thumb_urls(image, show_hidden) do %{ @@ -14,8 +43,22 @@ defmodule PhilomenaWeb.ImageView do tall: thumb_url(image, show_hidden, :tall), full: pretty_url(image, true, false) } + |> append_gif_urls(image, show_hidden) end + defp append_gif_urls(urls, %{image_mime_type: "image/gif"} = image, show_hidden) do + full_url = thumb_url(image, show_hidden, :full) + + Map.merge( + urls, + %{ + webm: String.replace(full_url, ".gif", ".webm"), + mp4: String.replace(full_url, ".gif", ".mp4") + } + ) + end + defp append_gif_urls(urls, _image, _show_hidden), do: urls + def thumb_url(image, show_hidden, name) do %{year: year, month: month, day: day} = image.created_at deleted = image.hidden_from_users diff --git a/lib/philomena_web/views/layout_view.ex b/lib/philomena_web/views/layout_view.ex index 183bf2c8..ca244656 100644 --- a/lib/philomena_web/views/layout_view.ex +++ b/lib/philomena_web/views/layout_view.ex @@ -5,6 +5,9 @@ defmodule PhilomenaWeb.LayoutView do conn.assigns[:layout_class] || "layout--narrow" end + def container_class(%{use_centered_layout: true}), do: "layout--center-aligned" + def container_class(_user), do: nil + def render_time(conn) do (Time.diff(Time.utc_now(), conn.assigns[:start_time], :microsecond) / 1000.0) |> Float.round(3) diff --git a/lib/philomena_web/views/setting_view.ex b/lib/philomena_web/views/setting_view.ex new file mode 100644 index 00000000..805986c7 --- /dev/null +++ b/lib/philomena_web/views/setting_view.ex @@ -0,0 +1,18 @@ +defmodule PhilomenaWeb.SettingView do + use PhilomenaWeb, :view + + def theme_options(conn) do + [ + [key: "Default", value: "default", data: [theme_path: Routes.static_path(conn, "/css/default.css")]], + [key: "Dark", value: "dark", data: [theme_path: Routes.static_path(conn, "/css/dark.css")]], + [key: "Red", value: "red", data: [theme_path: Routes.static_path(conn, "/css/red.css")]] + ] + end + + def local_tab_class(conn) do + case conn.assigns.current_user do + nil -> "" + _user -> "hidden" + end + end +end diff --git a/test/philomena_web/controllers/setting_controller_test.exs b/test/philomena_web/controllers/setting_controller_test.exs new file mode 100644 index 00000000..90227711 --- /dev/null +++ b/test/philomena_web/controllers/setting_controller_test.exs @@ -0,0 +1,88 @@ +defmodule PhilomenaWeb.SettingControllerTest do + use PhilomenaWeb.ConnCase + + alias Philomena.Settings + + @create_attrs %{} + @update_attrs %{} + @invalid_attrs %{} + + def fixture(:setting) do + {:ok, setting} = Settings.create_setting(@create_attrs) + setting + end + + describe "index" do + test "lists all settings", %{conn: conn} do + conn = get(conn, Routes.setting_path(conn, :index)) + assert html_response(conn, 200) =~ "Listing Settings" + end + end + + describe "new setting" do + test "renders form", %{conn: conn} do + conn = get(conn, Routes.setting_path(conn, :new)) + assert html_response(conn, 200) =~ "New Setting" + end + end + + describe "create setting" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, Routes.setting_path(conn, :create), setting: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == Routes.setting_path(conn, :show, id) + + conn = get(conn, Routes.setting_path(conn, :show, id)) + assert html_response(conn, 200) =~ "Show Setting" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, Routes.setting_path(conn, :create), setting: @invalid_attrs) + assert html_response(conn, 200) =~ "New Setting" + end + end + + describe "edit setting" do + setup [:create_setting] + + test "renders form for editing chosen setting", %{conn: conn, setting: setting} do + conn = get(conn, Routes.setting_path(conn, :edit, setting)) + assert html_response(conn, 200) =~ "Edit Setting" + end + end + + describe "update setting" do + setup [:create_setting] + + test "redirects when data is valid", %{conn: conn, setting: setting} do + conn = put(conn, Routes.setting_path(conn, :update, setting), setting: @update_attrs) + assert redirected_to(conn) == Routes.setting_path(conn, :show, setting) + + conn = get(conn, Routes.setting_path(conn, :show, setting)) + assert html_response(conn, 200) + end + + test "renders errors when data is invalid", %{conn: conn, setting: setting} do + conn = put(conn, Routes.setting_path(conn, :update, setting), setting: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Setting" + end + end + + describe "delete setting" do + setup [:create_setting] + + test "deletes chosen setting", %{conn: conn, setting: setting} do + conn = delete(conn, Routes.setting_path(conn, :delete, setting)) + assert redirected_to(conn) == Routes.setting_path(conn, :index) + assert_error_sent 404, fn -> + get(conn, Routes.setting_path(conn, :show, setting)) + end + end + end + + defp create_setting(_) do + setting = fixture(:setting) + {:ok, setting: setting} + end +end