User settings (#96)

Resolves derpibooru/philomena#121
Resolves derpibooru/philomena#122
This commit is contained in:
VcSaJen 2021-02-10 07:21:30 +09:00 committed by GitHub
parent 8f3965b9f5
commit 767a2ba949
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 226 additions and 67 deletions

View file

@ -18,6 +18,7 @@ defmodule Philomena.Comments do
alias Philomena.NotificationWorker alias Philomena.NotificationWorker
alias Philomena.Versions alias Philomena.Versions
alias Philomena.Reports alias Philomena.Reports
alias Philomena.Users.User
@doc """ @doc """
Gets a single comment. Gets a single comment.
@ -59,12 +60,21 @@ defmodule Philomena.Comments do
Multi.new() Multi.new()
|> Multi.insert(:comment, comment) |> Multi.insert(:comment, comment)
|> Multi.update_all(:image, image_query, inc: [comments_count: 1]) |> Multi.update_all(:image, image_query, inc: [comments_count: 1])
|> Multi.run(:subscribe, fn _repo, _changes -> |> maybe_create_subscription_on_reply(image, attribution[:user])
Images.create_subscription(image, attribution[:user])
end)
|> Repo.transaction() |> Repo.transaction()
end end
defp maybe_create_subscription_on_reply(multi, image, %User{watch_on_reply: true} = user) do
multi
|> Multi.run(:subscribe, fn _repo, _changes ->
Images.create_subscription(image, user)
end)
end
defp maybe_create_subscription_on_reply(multi, _image, _user) do
multi
end
def notify_comment(comment) do def notify_comment(comment) do
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Comments", comment.id]) Exq.enqueue(Exq, "notifications", NotificationWorker, ["Comments", comment.id])
end end

View file

@ -34,6 +34,7 @@ defmodule Philomena.Images do
alias Philomena.Comments alias Philomena.Comments
alias Philomena.Galleries.Gallery alias Philomena.Galleries.Gallery
alias Philomena.Galleries.Interaction alias Philomena.Galleries.Interaction
alias Philomena.Users.User
@doc """ @doc """
Gets a single image. Gets a single image.
@ -95,9 +96,7 @@ defmodule Philomena.Images do
{:ok, count} {:ok, count}
end) end)
|> Multi.run(:subscribe, fn _repo, %{image: image} -> |> maybe_create_subscription_on_upload(attribution[:user])
create_subscription(image, attribution[:user])
end)
|> Repo.transaction() |> Repo.transaction()
|> case do |> case do
{:ok, %{image: image}} = result -> {:ok, %{image: image}} = result ->
@ -116,6 +115,17 @@ defmodule Philomena.Images do
end end
end end
defp maybe_create_subscription_on_upload(multi, %User{watch_on_upload: true} = user) do
multi
|> Multi.run(:subscribe, fn _repo, %{image: image} ->
create_subscription(image, user)
end)
end
defp maybe_create_subscription_on_upload(multi, _user) do
multi
end
def feature_image(featurer, %Image{} = image) do def feature_image(featurer, %Image{} = image) do
%ImageFeature{user_id: featurer.id, image_id: image.id} %ImageFeature{user_id: featurer.id, image_id: image.id}
|> ImageFeature.changeset(%{}) |> ImageFeature.changeset(%{})

View file

@ -19,6 +19,7 @@ defmodule Philomena.Posts do
alias Philomena.Versions alias Philomena.Versions
alias Philomena.Reports alias Philomena.Reports
alias Philomena.Reports.Report alias Philomena.Reports.Report
alias Philomena.Users.User
@doc """ @doc """
Gets a single post. Gets a single post.
@ -88,9 +89,7 @@ defmodule Philomena.Posts do
{:ok, count} {:ok, count}
end) end)
|> Multi.run(:subscribe, fn _repo, _changes -> |> maybe_create_subscription_on_reply(topic, attributes[:user])
Topics.create_subscription(topic, attributes[:user])
end)
|> Repo.transaction() |> Repo.transaction()
|> case do |> case do
{:ok, %{post: post}} = result -> {:ok, %{post: post}} = result ->
@ -103,6 +102,17 @@ defmodule Philomena.Posts do
end end
end end
defp maybe_create_subscription_on_reply(multi, topic, %User{watch_on_reply: true} = user) do
multi
|> Multi.run(:subscribe, fn _repo, _changes ->
Topics.create_subscription(topic, user)
end)
end
defp maybe_create_subscription_on_reply(multi, _topic, _user) do
multi
end
def notify_post(post) do def notify_post(post) do
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Posts", post.id]) Exq.enqueue(Exq, "notifications", NotificationWorker, ["Posts", post.id])
end end

View file

@ -12,6 +12,7 @@ defmodule Philomena.Topics do
alias Philomena.Posts alias Philomena.Posts
alias Philomena.Notifications alias Philomena.Notifications
alias Philomena.NotificationWorker alias Philomena.NotificationWorker
alias Philomena.Users.User
@doc """ @doc """
Gets a single topic. Gets a single topic.
@ -69,9 +70,7 @@ defmodule Philomena.Topics do
{:ok, count} {:ok, count}
end) end)
|> Multi.run(:subscribe, fn _repo, %{topic: topic} -> |> maybe_create_subscription_on_new_topic(attribution[:user])
create_subscription(topic, attribution[:user])
end)
|> Repo.transaction() |> Repo.transaction()
|> case do |> case do
{:ok, %{topic: topic}} = result -> {:ok, %{topic: topic}} = result ->
@ -84,6 +83,17 @@ defmodule Philomena.Topics do
end end
end end
defp maybe_create_subscription_on_new_topic(multi, %User{watch_on_new_topic: true} = user) do
multi
|> Multi.run(:subscribe, fn _repo, %{topic: topic} ->
create_subscription(topic, user)
end)
end
defp maybe_create_subscription_on_new_topic(multi, _user) do
multi
end
def notify_topic(topic, post) do def notify_topic(topic, post) do
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Topics", [topic.id, post.id]]) Exq.enqueue(Exq, "notifications", NotificationWorker, ["Topics", [topic.id, post.id]])
end end

View file

@ -654,6 +654,12 @@ defmodule Philomena.Users do
|> Repo.update() |> Repo.update()
end end
def clear_recent_filters(%User{} = user) do
user
|> User.clear_recent_filters_changeset()
|> Repo.update()
end
defp load_with_roles(query) do defp load_with_roles(query) do
query query
|> Repo.one() |> Repo.one()

View file

@ -314,7 +314,13 @@ defmodule Philomena.Users.User do
:watched_images_exclude_str, :watched_images_exclude_str,
:use_centered_layout, :use_centered_layout,
:hide_vote_counts, :hide_vote_counts,
:comments_newest_first :comments_newest_first,
:watch_on_reply,
:watch_on_upload,
:watch_on_new_topic,
:comments_always_jump_to_last,
:messages_newest_first,
:show_sidebar_and_watched_images
]) ])
|> validate_required([ |> validate_required([
:images_per_page, :images_per_page,
@ -326,7 +332,13 @@ defmodule Philomena.Users.User do
:theme, :theme,
:no_spoilered_in_watched, :no_spoilered_in_watched,
:use_centered_layout, :use_centered_layout,
:hide_vote_counts :hide_vote_counts,
:watch_on_reply,
:watch_on_upload,
:watch_on_new_topic,
:comments_always_jump_to_last,
:messages_newest_first,
:show_sidebar_and_watched_images
]) ])
|> TagList.propagate_tag_list(:watched_tag_list, :watched_tag_ids) |> TagList.propagate_tag_list(:watched_tag_list, :watched_tag_ids)
|> validate_inclusion(:theme, ~W(default dark red)) |> validate_inclusion(:theme, ~W(default dark red))
@ -511,6 +523,13 @@ defmodule Philomena.Users.User do
) )
end end
def clear_recent_filters_changeset(user) do
user
|> change(%{
recent_filter_ids: [user.current_filter_id]
})
end
defp enable_totp_changeset(user, backup_codes) do defp enable_totp_changeset(user, backup_codes) do
hashed_codes = Enum.map(backup_codes, &Password.hash_pwd_salt/1) hashed_codes = Enum.map(backup_codes, &Password.hash_pwd_salt/1)

View file

@ -34,6 +34,18 @@ defmodule PhilomenaWeb.CommentLoader do
div(offset, page_size) + 1 div(offset, page_size) + 1
end end
def last_page(conn, image) do
offset =
Comment
|> where(image_id: ^image.id)
|> Repo.aggregate(:count, :id)
page_size = conn.assigns.comment_scrivener[:page_size]
# Pagination starts at page 1
div(offset, page_size) + 1
end
defp load_direction(%{comments_newest_first: false}), do: :asc defp load_direction(%{comments_newest_first: false}), do: :asc
defp load_direction(_user), do: :desc defp load_direction(_user), do: :desc

View file

@ -116,7 +116,8 @@ defmodule PhilomenaWeb.ActivityController do
streams: streams, streams: streams,
topics: topics, topics: topics,
interactions: interactions, interactions: interactions,
layout_class: "layout--wide" layout_class: "layout--wide",
show_sidebar: show_sidebar?(user) || !index_page?(conn)
) )
end end
@ -165,4 +166,12 @@ defmodule PhilomenaWeb.ActivityController do
] ]
) )
end end
defp index_page?(%{assigns: %{index: true}}), do: true
defp index_page?(_conn), do: false
defp show_sidebar?(%{show_sidebar_and_watched_images: false}), do: false
defp show_sidebar?(_user), do: true
end end

View file

@ -61,11 +61,12 @@ defmodule PhilomenaWeb.ConversationController do
def show(conn, _params) do def show(conn, _params) do
conversation = conn.assigns.conversation conversation = conn.assigns.conversation
user = conn.assigns.current_user user = conn.assigns.current_user
pref = load_direction(user)
messages = messages =
Message Message
|> where(conversation_id: ^conversation.id) |> where(conversation_id: ^conversation.id)
|> order_by(asc: :created_at) |> order_by([{^pref, :created_at}])
|> preload([:from]) |> preload([:from])
|> Repo.paginate(conn.assigns.scrivener) |> Repo.paginate(conn.assigns.scrivener)
@ -115,4 +116,7 @@ defmodule PhilomenaWeb.ConversationController do
|> render("new.html", changeset: changeset) |> render("new.html", changeset: changeset)
end end
end end
defp load_direction(%{messages_newest_first: false}), do: :asc
defp load_direction(_user), do: :desc
end end

View file

@ -0,0 +1,15 @@
defmodule PhilomenaWeb.Filter.ClearRecentController do
use PhilomenaWeb, :controller
alias Philomena.Users
plug PhilomenaWeb.RequireUserPlug
def delete(conn, _params) do
{:ok, user} = Users.clear_recent_filters(conn.assigns.current_user)
conn
|> put_flash(:info, "Cleared recent filters")
|> redirect(external: conn.assigns.referrer)
end
end

View file

@ -59,6 +59,8 @@ defmodule PhilomenaWeb.ImageController do
# Update the notification ticker in the header # Update the notification ticker in the header
conn = NotificationCountPlug.call(conn) conn = NotificationCountPlug.call(conn)
conn = maybe_skip_to_last_comment_page(conn, image, user)
comments = CommentLoader.load_comments(conn, image) comments = CommentLoader.load_comments(conn, image)
rendered = TextileRenderer.render_collection(comments.entries, conn) rendered = TextileRenderer.render_collection(comments.entries, conn)
@ -132,6 +134,14 @@ defmodule PhilomenaWeb.ImageController do
end end
end end
defp maybe_skip_to_last_comment_page(conn, image, %{comments_newest_first: false, comments_always_jump_to_last: true}) do
page = CommentLoader.last_page(conn, image)
conn
|> assign(:comment_scrivener, Keyword.merge(conn.assigns.comment_scrivener, [page: page]))
end
defp maybe_skip_to_last_comment_page(conn, _image, _user), do: conn
defp user_galleries(_image, nil), do: [] defp user_galleries(_image, nil), do: []
defp user_galleries(image, user) do defp user_galleries(image, user) do

View file

@ -418,7 +418,7 @@ defmodule PhilomenaWeb.Router do
scope "/", PhilomenaWeb do scope "/", PhilomenaWeb do
pipe_through [:browser, :ensure_totp, :ensure_tor_authorized] pipe_through [:browser, :ensure_totp, :ensure_tor_authorized]
get "/", ActivityController, :index get "/", ActivityController, :index, [assigns: %{index: true}]
resources "/activity", ActivityController, only: [:index] resources "/activity", ActivityController, only: [:index]
@ -474,6 +474,7 @@ defmodule PhilomenaWeb.Router do
scope "/filters", Filter, as: :filter do scope "/filters", Filter, as: :filter do
resources "/current", CurrentController, only: [:update], singleton: true resources "/current", CurrentController, only: [:update], singleton: true
resources "/clear_recent", ClearRecentController, only: [:delete], singleton: true
end end
resources "/filters", FilterController do resources "/filters", FilterController do

View file

@ -1,45 +1,46 @@
.column-layout .column-layout
aside.column-layout__left#activity-side = if @show_sidebar do
= if not is_nil(@featured_image) and not PhilomenaWeb.ImageView.filter_or_spoiler_hits?(@conn, @featured_image) do aside.column-layout__left#activity-side
.center = if not is_nil(@featured_image) and not PhilomenaWeb.ImageView.filter_or_spoiler_hits?(@conn, @featured_image) do
h4.remove-top-margin Featured Image .center
= render PhilomenaWeb.ImageView, "_image_box.html", image: @featured_image, size: :medium, conn: @conn h4.remove-top-margin Featured Image
.block.block--fixed.block--fixed--sub.block--success.center.hide-mobile = render PhilomenaWeb.ImageView, "_image_box.html", image: @featured_image, size: :medium, conn: @conn
' Enjoy the site? .block.block--fixed.block--fixed--sub.block--success.center.hide-mobile
a href="/pages/donations" ' Enjoy the site?
' Become a patron or donate! a href="/pages/donations"
.block.block--fixed.block--fixed--sub.center.hide-mobile ' Become a patron or donate!
' Issues? Want to chat? .block.block--fixed.block--fixed--sub.center.hide-mobile
a href="/pages/contact" Contact us! ' Issues? Want to chat?
.block.hide-mobile a href="/pages/contact" Contact us!
a.block__header--single-item.center href="/search?q=first_seen_at.gt:3 days ago&sf=wilson_score&sd=desc" .block.hide-mobile
' Trending Images a.block__header--single-item.center href="/search?q=first_seen_at.gt:3 days ago&sf=wilson_score&sd=desc"
.block__content.flex.flex--centered.flex--wrap.image-flex-grid ' Trending Images
= for image <- @top_scoring do .block__content.flex.flex--centered.flex--wrap.image-flex-grid
= render PhilomenaWeb.ImageView, "_image_box.html", image: image, size: :thumb_small, conn: @conn = for image <- @top_scoring do
a.block__header--single-item.center href="/search?q=*&amp;sf=score&amp;sd=desc" = render PhilomenaWeb.ImageView, "_image_box.html", image: image, size: :thumb_small, conn: @conn
' All Time Top Scoring a.block__header--single-item.center href="/search?q=*&amp;sf=score&amp;sd=desc"
.block.hide-mobile ' All Time Top Scoring
a.block__header--single-item.center href="/channels" .block.hide-mobile
' Streams a.block__header--single-item.center href="/channels"
= for channel <- @streams do ' Streams
= render PhilomenaWeb.ActivityView, "_channel_strip.html", channel: channel, conn: @conn = for channel <- @streams do
.block.hide-mobile = render PhilomenaWeb.ActivityView, "_channel_strip.html", channel: channel, conn: @conn
a.block__header--single-item.center href="/forums" .block.hide-mobile
' Forum Activity a.block__header--single-item.center href="/forums"
= for topic <- @topics do ' Forum Activity
= render PhilomenaWeb.ActivityView, "_topic_strip.html", topic: topic, conn: @conn = for topic <- @topics do
.block.hide-mobile = render PhilomenaWeb.ActivityView, "_topic_strip.html", topic: topic, conn: @conn
a.block__header--single-item.center href="/comments" .block.hide-mobile
' Recent Comments a.block__header--single-item.center href="/comments"
= for comment <- @comments do ' Recent Comments
= render PhilomenaWeb.ActivityView, "_comment_strip.html", comment: comment, conn: @conn = for comment <- @comments do
a.block__header--single-item.center href="/search?q=first_seen_at.gt:3 days ago&amp;sf=comment_count&amp;sd=desc" = render PhilomenaWeb.ActivityView, "_comment_strip.html", comment: comment, conn: @conn
' Most Commented-on Images a.block__header--single-item.center href="/search?q=first_seen_at.gt:3 days ago&amp;sf=comment_count&amp;sd=desc"
' Most Commented-on Images
.column-layout__main .column-layout__main
= render PhilomenaWeb.ImageView, "index.html", conn: @conn, images: @images, size: :thumb = render PhilomenaWeb.ImageView, "index.html", conn: @conn, images: @images, size: :thumb
= if not is_nil(@watched) and Enum.any?(@watched) do = if @show_sidebar and not is_nil(@watched) and Enum.any?(@watched) do
.block .block
.block__header .block__header
span.block__header__title span.block__header__title

View file

@ -39,6 +39,12 @@
= for filter <- @system_filters do = for filter <- @system_filters do
= render PhilomenaWeb.FilterView, "_filter.html", conn: @conn, filter: filter = render PhilomenaWeb.FilterView, "_filter.html", conn: @conn, filter: filter
= if @current_user do
h2 Recent Filters
p
' Clicking this button will clear the recent filters list in the header dropdown.
= button_to "Clear recent filter list", Routes.filter_clear_recent_path(@conn, :delete), method: "delete", class: "button"
h2 Search Filters h2 Search Filters
p p
' Some users maintain custom filters which are publicly shared; you can search these filters with the box below. ' Some users maintain custom filters which are publicly shared; you can search these filters with the box below.
@ -50,7 +56,7 @@
.fieldlabel .fieldlabel
' For more information, see the ' For more information, see the
a href="/pages/search_syntax" search syntax documentation a href="/pages/search_syntax" search syntax documentation
' . Search results are sorted by creation date. ' . Search results are sorted alphabetically.
= if @conn.params["fq"] do = if @conn.params["fq"] do
h2 Search Results h2 Search Results
@ -107,7 +113,7 @@ table.table
td Full Text td Full Text
td Matches the description of this filter. td Matches the description of this filter.
td td
code = link "description:show's rating", to: Routes.filter_path(@conn, :index, fq: "description:the show's rating") code = link "description:the show's rating", to: Routes.filter_path(@conn, :index, fq: "description:the show's rating")
tr tr
td td
code created_at code created_at
@ -171,4 +177,4 @@ table.table
code = link "user_id:307505", to: Routes.filter_path(@conn, :index, fq: "user_id:307505") code = link "user_id:307505", to: Routes.filter_path(@conn, :index, fq: "user_id:307505")
= if @conn.params["fq"] do = if @conn.params["fq"] do
p = link("Back to filters", to: Routes.filter_path(@conn, :index)) p = link("Back to filters", to: Routes.filter_path(@conn, :index))

View file

@ -9,6 +9,8 @@ h1 Content Settings
= if @conn.assigns.current_user do = if @conn.assigns.current_user do
= link "Watch List", to: "#", class: "selected", data: [click_tab: "watched"] = link "Watch List", to: "#", class: "selected", data: [click_tab: "watched"]
= link "Display", to: "#", data: [click_tab: "display"] = link "Display", to: "#", data: [click_tab: "display"]
= link "Comments", to: "#", data: [click_tab: "comments"]
= link "Notifications", to: "#", data: [click_tab: "notifications"]
= link "Metadata", to: "#", data: [click_tab: "metadata"] = link "Metadata", to: "#", data: [click_tab: "metadata"]
= link "Local", to: "#", data: [click_tab: "local"] = link "Local", to: "#", data: [click_tab: "local"]
- else - else
@ -55,14 +57,14 @@ h1 Content Settings
=> label f, :use_centered_layout => label f, :use_centered_layout
=> checkbox f, :use_centered_layout, class: "checkbox" => 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. .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, :show_sidebar_and_watched_images
=> checkbox f, :show_sidebar_and_watched_images, class: "checkbox"
.fieldlabel: i Show the sidebar and new watched images on the homepage (the default) or hide it.
.field .field
=> label f, :hide_vote_counts => label f, :hide_vote_counts
=> checkbox f, :hide_vote_counts, class: "checkbox" => checkbox f, :hide_vote_counts, class: "checkbox"
.fieldlabel: i Hide upvote and downvote counts on images, showing only the overall score .fieldlabel: i Hide upvote and downvote counts on images, showing only the overall score
.field
=> label f, :comments_newest_first, "Newest comments first"
=> checkbox f, :comments_newest_first
.fieldlabel: i Display the newest comments at the top of the page.
.field .field
=> label f, :images_per_page => label f, :images_per_page
=> number_input f, :images_per_page, min: 1, max: 50, step: 1, class: "input" => number_input f, :images_per_page, min: 1, max: 50, step: 1, class: "input"
@ -71,11 +73,6 @@ h1 Content Settings
i i
' This is the number of images per page that are displayed on image listings and searches, up to a maximum of 50. ' 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. ' For 1080p monitors, try 24.
.field
=> label f, :comments_per_page
=> number_input f, :comments_per_page, min: 1, max: 100, step: 1, class: "input"
= error_tag f, :comments_per_page
.fieldlabel: i This is the number of comments per page that are displayed on image pages.
.field .field
=> label f, :scale_large_images => label f, :scale_large_images
=> checkbox f, :scale_large_images, class: "checkbox" => checkbox f, :scale_large_images, class: "checkbox"
@ -86,6 +83,45 @@ h1 Content Settings
= error_tag f, :theme = error_tag f, :theme
.fieldlabel: i Preview themes by selecting one from the dropdown. Saving sets the currently selected theme. .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="comments"
div
.field
=> label f, :comments_newest_first, "Newest comments first"
=> checkbox f, :comments_newest_first
.fieldlabel: i Display the newest comments at the top of the page.
.field
=> label f, :comments_always_jump_to_last, "Show latest comment page"
=> checkbox f, :comments_always_jump_to_last
.fieldlabel
i
' This setting takes effect when the previous is disabled. Always jump to the latest page (enabled) or show the first page if the oldest comments are shown at the top of the page.
br
' Posting will always direct you to the latest page so that you can see your comment in context.
.field
=> label f, :comments_per_page
=> number_input f, :comments_per_page, min: 1, max: 100, step: 1, class: "input"
= error_tag f, :comments_per_page
.fieldlabel: i This is the number of comments per page that are displayed on image pages.
.field
=> label f, :messages_newest_first, "Newest messages first"
=> checkbox f, :messages_newest_first
.fieldlabel: i Show the newest messages first (enabled) or show the oldest messages at the top of a conversation. Enabling this makes it feel more like a top-posted email quote chain.
.block__tab.hidden.flex.flex--maybe-wrap data-tab="notifications"
div
.field
=> label f, :watch_on_reply, "Subscribe on Reply"
=> checkbox f, :watch_on_reply, class: "checkbox"
.fieldlabel: i Subscribe on Reply means you'll be subscribed things (images or topics) automatically as soon as you post a comment or reply, keeping you in the conversation.
.field
=> label f, :watch_on_upload, "Subscribe on Upload"
=> checkbox f, :watch_on_upload, class: "checkbox"
.fieldlabel: i Subscribe on Upload means you'll be subscribed to images automatically as soon as you upload, to help you keep track of comments.
.field
=> label f, :watch_on_new_topic, "Subscribe on New Threads"
=> checkbox f, :watch_on_new_topic, class: "checkbox"
.fieldlabel: i Subscribe on New Threads means you'll be subscribed to threads automatically as soon as you post, to help you keep track of replies.
.block__tab.hidden.flex.flex--maybe-wrap data-tab="metadata" .block__tab.hidden.flex.flex--maybe-wrap data-tab="metadata"
div div
.field .field