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.Versions
alias Philomena.Reports
alias Philomena.Users.User
@doc """
Gets a single comment.
@ -59,12 +60,21 @@ defmodule Philomena.Comments do
Multi.new()
|> Multi.insert(:comment, comment)
|> Multi.update_all(:image, image_query, inc: [comments_count: 1])
|> Multi.run(:subscribe, fn _repo, _changes ->
Images.create_subscription(image, attribution[:user])
end)
|> maybe_create_subscription_on_reply(image, attribution[:user])
|> Repo.transaction()
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
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Comments", comment.id])
end

View file

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

View file

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

View file

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

View file

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

View file

@ -314,7 +314,13 @@ defmodule Philomena.Users.User do
:watched_images_exclude_str,
:use_centered_layout,
: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([
:images_per_page,
@ -326,7 +332,13 @@ defmodule Philomena.Users.User do
:theme,
:no_spoilered_in_watched,
: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)
|> validate_inclusion(:theme, ~W(default dark red))
@ -511,6 +523,13 @@ defmodule Philomena.Users.User do
)
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
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
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(_user), do: :desc

View file

@ -116,7 +116,8 @@ defmodule PhilomenaWeb.ActivityController do
streams: streams,
topics: topics,
interactions: interactions,
layout_class: "layout--wide"
layout_class: "layout--wide",
show_sidebar: show_sidebar?(user) || !index_page?(conn)
)
end
@ -165,4 +166,12 @@ defmodule PhilomenaWeb.ActivityController do
]
)
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

View file

@ -61,11 +61,12 @@ defmodule PhilomenaWeb.ConversationController do
def show(conn, _params) do
conversation = conn.assigns.conversation
user = conn.assigns.current_user
pref = load_direction(user)
messages =
Message
|> where(conversation_id: ^conversation.id)
|> order_by(asc: :created_at)
|> order_by([{^pref, :created_at}])
|> preload([:from])
|> Repo.paginate(conn.assigns.scrivener)
@ -115,4 +116,7 @@ defmodule PhilomenaWeb.ConversationController do
|> render("new.html", changeset: changeset)
end
end
defp load_direction(%{messages_newest_first: false}), do: :asc
defp load_direction(_user), do: :desc
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
conn = NotificationCountPlug.call(conn)
conn = maybe_skip_to_last_comment_page(conn, image, user)
comments = CommentLoader.load_comments(conn, image)
rendered = TextileRenderer.render_collection(comments.entries, conn)
@ -132,6 +134,14 @@ defmodule PhilomenaWeb.ImageController do
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, user) do

View file

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

View file

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

View file

@ -39,6 +39,12 @@
= for filter <- @system_filters do
= 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
p
' Some users maintain custom filters which are publicly shared; you can search these filters with the box below.
@ -50,7 +56,7 @@
.fieldlabel
' For more information, see the
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
h2 Search Results
@ -107,7 +113,7 @@ table.table
td Full Text
td Matches the description of this filter.
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
td
code created_at

View file

@ -9,6 +9,8 @@ h1 Content Settings
= if @conn.assigns.current_user do
= link "Watch List", to: "#", class: "selected", data: [click_tab: "watched"]
= 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 "Local", to: "#", data: [click_tab: "local"]
- else
@ -55,14 +57,14 @@ h1 Content Settings
=> 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, :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
=> 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, :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, :images_per_page
=> number_input f, :images_per_page, min: 1, max: 50, step: 1, class: "input"
@ -71,11 +73,6 @@ h1 Content Settings
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: 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, :scale_large_images
=> checkbox f, :scale_large_images, class: "checkbox"
@ -86,6 +83,45 @@ h1 Content Settings
= error_tag f, :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"
div
.field