settings page

This commit is contained in:
byte[] 2019-11-30 17:40:53 -05:00
parent 72121ca057
commit 64d1e817d1
25 changed files with 516 additions and 115 deletions

View file

@ -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

View file

@ -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

View 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

View 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

View file

@ -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.

View file

@ -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))

View file

@ -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

View file

@ -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}),

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)
)

View 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

View file

@ -50,7 +50,7 @@ defmodule PhilomenaWeb.TagController do
},
sort: %{created_at: :desc}
},
conn.assigns.pagination,
conn.assigns.image_pagination,
Image |> preload([:tags, :user])
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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:

View 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

View file

@ -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

View file

@ -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)

View 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

View 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