mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
settings page
This commit is contained in:
parent
72121ca057
commit
64d1e817d1
25 changed files with 516 additions and 115 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
18
lib/philomena/schema/search.ex
Normal file
18
lib/philomena/schema/search.ex
Normal file
|
@ -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
|
52
lib/philomena/schema/tag_list.ex
Normal file
52
lib/philomena/schema/tag_list.ex
Normal file
|
@ -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
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
||||
|
|
59
lib/philomena_web/controllers/setting_controller.ex
Normal file
59
lib/philomena_web/controllers/setting_controller.ex
Normal file
|
@ -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
|
|
@ -50,7 +50,7 @@ defmodule PhilomenaWeb.TagController do
|
|||
},
|
||||
sort: %{created_at: :desc}
|
||||
},
|
||||
conn.assigns.pagination,
|
||||
conn.assigns.image_pagination,
|
||||
Image |> preload([:tags, :user])
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
118
lib/philomena_web/templates/setting/edit.html.slime
Normal file
118
lib/philomena_web/templates/setting/edit.html.slime
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
18
lib/philomena_web/views/setting_view.ex
Normal file
18
lib/philomena_web/views/setting_view.ex
Normal file
|
@ -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
|
88
test/philomena_web/controllers/setting_controller_test.exs
Normal file
88
test/philomena_web/controllers/setting_controller_test.exs
Normal file
|
@ -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
|
Loading…
Reference in a new issue