tag aliasing and image editing

This commit is contained in:
byte[] 2019-12-14 18:04:23 -05:00
parent b7c8b95003
commit 482f1dfe5e
14 changed files with 196 additions and 21 deletions

View file

@ -174,9 +174,9 @@ defmodule Philomena.Tags do
def alias_tag(%Tag{} = tag, attrs) do def alias_tag(%Tag{} = tag, attrs) do
target_tag = Repo.get_by!(Tag, name: attrs["target_tag"]) target_tag = Repo.get_by!(Tag, name: attrs["target_tag"])
filters_hidden = where(Filter, [f], fragment("? @> ARRAY[?]", f.hidden_tag_ids, ^tag.id)) filters_hidden = where(Filter, [f], fragment("? @> ARRAY[?]::integer[]", f.hidden_tag_ids, ^tag.id))
filters_spoilered = where(Filter, [f], fragment("? @> ARRAY[?]", f.spoilered_tag_ids, ^tag.id)) filters_spoilered = where(Filter, [f], fragment("? @> ARRAY[?]::integer[]", f.spoilered_tag_ids, ^tag.id))
users_watching = where(User, [u], fragment("? @> ARRAY[?]", u.watched_tag_ids, ^tag.id)) users_watching = where(User, [u], fragment("? @> ARRAY[?]::integer[]", u.watched_tag_ids, ^tag.id))
array_replace(filters_hidden, :hidden_tag_ids, tag.id, target_tag.id) array_replace(filters_hidden, :hidden_tag_ids, tag.id, target_tag.id)
array_replace(filters_spoilered, :spoilered_tag_ids, tag.id, target_tag.id) array_replace(filters_spoilered, :spoilered_tag_ids, tag.id, target_tag.id)
@ -186,7 +186,7 @@ defmodule Philomena.Tags do
Repo.query!( Repo.query!(
"INSERT INTO image_taggings (image_id, tag_id) " <> "INSERT INTO image_taggings (image_id, tag_id) " <>
"SELECT i.id, #{target_tag.id} FROM images i " <> "SELECT i.id, #{target_tag.id} FROM images i " <>
"INNER JOIN image_tagging it on it.image_id = i.id " <> "INNER JOIN image_taggings it on it.image_id = i.id " <>
"WHERE it.tag_id = #{tag.id} " <> "WHERE it.tag_id = #{tag.id} " <>
"ON CONFLICT DO NOTHING" "ON CONFLICT DO NOTHING"
) )
@ -228,6 +228,12 @@ defmodule Philomena.Tags do
|> Image.reindex() |> Image.reindex()
end end
def unalias_tag(%Tag{} = tag) do
tag
|> Tag.unalias_changeset()
|> Repo.update()
end
defp array_replace(queryable, column, old_value, new_value) do defp array_replace(queryable, column, old_value, new_value) do
queryable queryable
|> update( |> update(

View file

@ -95,7 +95,7 @@ defmodule Philomena.Tags.Tag do
tag tag
|> cast(attrs, [:image, :image_format, :image_mime_type, :uploaded_image]) |> cast(attrs, [:image, :image_format, :image_mime_type, :uploaded_image])
|> validate_required([:image, :image_format, :image_mime_type]) |> validate_required([:image, :image_format, :image_mime_type])
|> validate_inclusion(:image_mime_type, ~W(image/gif image/jpeg image/png)) |> validate_inclusion(:image_mime_type, ~W(image/gif image/jpeg image/png image/svg+xml))
end end
def remove_image_changeset(tag) do def remove_image_changeset(tag) do
@ -104,6 +104,10 @@ defmodule Philomena.Tags.Tag do
|> put_change(:image, nil) |> put_change(:image, nil)
end end
def unalias_changeset(tag) do
change(tag, aliased_tag_id: nil)
end
def creation_changeset(tag, attrs) do def creation_changeset(tag, attrs) do
tag tag
|> cast(attrs, [:name]) |> cast(attrs, [:name])

View file

@ -88,8 +88,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{role: "moderator"}, :show, %Topic{}), do: true def can?(%User{role: "moderator"}, :show, %Topic{}), do: true
def can?(%User{role: "moderator"}, :hide, %Topic{}), do: true def can?(%User{role: "moderator"}, :hide, %Topic{}), do: true
# Edit tags # Edit and alias tags
def can?(%User{role: "moderator"}, :edit, %Tag{}), do: true def can?(%User{role: "moderator"}, :edit, %Tag{}), do: true
def can?(%User{role: "moderator"}, :alias, %Tag{}), do: true
# #
# Assistants can... # Assistants can...

View file

@ -0,0 +1,32 @@
defmodule PhilomenaWeb.Tag.AliasController do
use PhilomenaWeb, :controller
alias Philomena.Tags.Tag
alias Philomena.Tags
plug PhilomenaWeb.CanaryMapPlug, edit: :alias, update: :alias, delete: :alias
plug :load_and_authorize_resource, model: Tag, id_name: "tag_id", id_field: "slug", preload: [:implied_tags, :aliased_tag], persisted: true
def edit(conn, _params) do
changeset = Tags.change_tag(conn.assigns.tag)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"tag" => tag_params}) do
spawn fn ->
Tags.alias_tag(conn.assigns.tag, tag_params)
end
conn
|> put_flash(:info, "Tag alias queued.")
|> redirect(to: Routes.tag_path(conn, :show, conn.assigns.tag))
end
def delete(conn, _params) do
{:ok, _tag} = Tags.unalias_tag(conn.assigns.tag)
conn
|> put_flash(:info, "Tag alias removed.")
|> redirect(to: Routes.tag_path(conn, :show, conn.assigns.tag))
end
end

View file

@ -0,0 +1,35 @@
defmodule PhilomenaWeb.Tag.ImageController do
use PhilomenaWeb, :controller
alias Philomena.Tags.Tag
alias Philomena.Tags
import Ecto.Query
plug PhilomenaWeb.CanaryMapPlug, update: :edit, delete: :edit
plug :load_and_authorize_resource, model: Tag, id_name: "tag_id", id_field: "slug", preload: [:implied_tags], persisted: true
def edit(conn, _params) do
changeset = Tags.change_tag(conn.assigns.tag)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"tag" => tag_params}) do
case Tags.update_tag_image(conn.assigns.tag, tag_params) do
{:ok, %{tag: tag}} ->
conn
|> put_flash(:info, "Tag image successfully updated.")
|> redirect(to: Routes.tag_path(conn, :show, tag))
{:error, :tag, changeset, _changes} ->
render(conn, "edit.html", changeset: changeset)
end
end
def delete(conn, _params) do
{:ok, _tag} = Tags.remove_tag_image(conn.assigns.tag)
conn
|> put_flash(:info, "Tag image successfully removed.")
|> redirect(to: Routes.tag_path(conn, :show, conn.assigns.tag))
end
end

View file

@ -8,7 +8,8 @@ defmodule PhilomenaWeb.TagController do
plug PhilomenaWeb.RecodeParameterPlug, [name: "id"] when action in [:show] plug PhilomenaWeb.RecodeParameterPlug, [name: "id"] when action in [:show]
plug PhilomenaWeb.CanaryMapPlug, update: :edit plug PhilomenaWeb.CanaryMapPlug, update: :edit
plug :load_and_authorize_resource, model: Tag, id_field: "slug", only: [:show, :edit, :update, :delete], preload: [:aliases, :implied_tags, :implied_by_tags, :dnp_entries, public_links: :user] plug :load_and_authorize_resource, model: Tag, id_field: "slug", only: [:show, :edit, :update, :delete], preload: [:aliases, :aliased_tag, :implied_tags, :implied_by_tags, :dnp_entries, public_links: :user]
plug :redirect_alias
def index(conn, params) do def index(conn, params) do
query_string = params["tq"] || "*" query_string = params["tq"] || "*"
@ -128,4 +129,16 @@ defmodule PhilomenaWeb.TagController do
|> String.replace("\"", "\\\"") |> String.replace("\"", "\\\"")
end end
end end
defp redirect_alias(conn, _opts) do
case conn.assigns.tag do
%{aliased_tag: nil} ->
conn
%{aliased_tag: tag} ->
conn
|> put_flash(:info, "This tag (`#{conn.assigns.tag.name}') has been aliased into the tag `#{tag.name}'.")
|> redirect(to: Routes.tag_path(conn, :show, tag))
end
end
end end

View file

@ -209,7 +209,10 @@ defmodule PhilomenaWeb.Router do
resources "/claim", DuplicateReport.ClaimController, only: [:create, :delete], singleton: true resources "/claim", DuplicateReport.ClaimController, only: [:create, :delete], singleton: true
end end
resources "/tags", TagController, only: [:edit, :update, :delete] resources "/tags", TagController, only: [:edit, :update, :delete] do
resources "/image", Tag.ImageController, only: [:edit, :update, :delete], singleton: true
resources "/alias", Tag.AliasController, only: [:edit, :update, :delete], singleton: true
end
end end
scope "/", PhilomenaWeb do scope "/", PhilomenaWeb do

View file

@ -1,4 +1,4 @@
.block__content.js-imagelist-info.flex .block__content.js-imagelist-info.flex class=tags_row_class(@conn)
.flex__fixed.tag-info__image.thumb-tiny-container.spacing-right .flex__fixed.tag-info__image.thumb-tiny-container.spacing-right
= if @tag.image do = if @tag.image do
img src=tag_image(@tag) alt="spoiler image" img src=tag_image(@tag) alt="spoiler image"
@ -18,21 +18,29 @@
= @tag.short_description = @tag.short_description
br br
= if manages_tags?(@conn) and present?(@tag.mod_notes) do
strong.comment_deleted> Mod notes:
= @tag.mod_notes
br
= if Enum.any?(@tag.aliases) do = if Enum.any?(@tag.aliases) do
strong> Aliases: strong> Aliases:
= Enum.map_join(@tag.aliases, ", ", & &1.name) = if aliases_tags?(@conn) do
= map_join(@tag.aliases, ", ", &link(&1.name, to: Routes.tag_alias_path(@conn, :edit, &1)))
- else
= map_join(@tag.aliases, ", ", & &1.name)
br br
= if Enum.any?(@tag.implied_tags) do = if Enum.any?(@tag.implied_tags) do
strong> Implies: strong> Implies:
= Enum.map_join(@tag.implied_tags, ", ", & &1.name) = map_join(@tag.implied_tags, ", ", &link(&1.name, to: Routes.tag_path(@conn, :show, &1)))
br br
= if present?(@tag.public_links) or present?(@tag.implied_by_tags) or present?(@tag.description) do = if present?(@tag.public_links) or present?(@tag.implied_by_tags) or present?(@tag.description) do
br br
= link "Toggle detailed information", to: "#", data: [click_toggle: ".tag-info__more"] = link "Toggle detailed information", to: "#", data: [click_toggle: ".tag-info__more"]
.tag-info__more .tag-info__more.hidden
hr hr
= if Enum.any?(@tag.public_links) do = if Enum.any?(@tag.public_links) do
@ -53,7 +61,7 @@
br br
= if Enum.any?(@tag.implied_by_tags) do = if Enum.any?(@tag.implied_by_tags) do
input.toggle-box id="implied_by" type="checkbox" checked="false" input.toggle-box id="implied_by" type="checkbox"
label for="implied_by" label for="implied_by"
' Implied by (warning: unfiltered) ' Implied by (warning: unfiltered)
@ -83,5 +91,5 @@
==> body ==> body
| ( | (
= link "more info", to: "#" = link "more info", to: Routes.dnp_entry_path(@conn, :show, entry)
| ) | )

View file

@ -0,0 +1,21 @@
h1
' Aliasing tag
= @tag.name
= form_for @changeset, Routes.tag_alias_path(@conn, :update, @tag), [method: "put"], fn f ->
= if @changeset.action do
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.
.field
=> label f, "Alias target:"
= text_input f, :target_tag, value: alias_target(@tag), class: "input"
.field
=> submit "Alias tag", class: "button"
br
= button_to "Remove tag alias", Routes.tag_alias_path(@conn, :delete, @tag), method: "delete", class: "button", data: [confirm: "Are you really, really sure?"]
br
= link "Back", to: Routes.tag_path(@conn, :show, @tag)

View file

@ -1,5 +1,8 @@
h1 Editing Tag h1 Editing Tag
p = link "Edit image", to: Routes.tag_image_path(@conn, :edit, @tag)
p = link "Edit aliases", to: Routes.tag_alias_path(@conn, :edit, @tag)
= form_for @changeset, Routes.tag_path(@conn, :update, @tag), [class: "form"], fn f -> = form_for @changeset, Routes.tag_path(@conn, :update, @tag), [class: "form"], fn f ->
= if @changeset.action do = if @changeset.action do
.alert.alert-danger .alert.alert-danger
@ -30,13 +33,6 @@ h1 Editing Tag
.field .field
= render PhilomenaWeb.TagView, "_tag_editor.html", f: f, name: :implied_tag_list, type: :edit, conn: @conn = render PhilomenaWeb.TagView, "_tag_editor.html", f: f, name: :implied_tag_list, type: :edit, conn: @conn
/- if can? :manage, Tag
h4 Tag Merging (Aliasing)
.fieldlabel Merge with target tag for searches, user links, filters, etc.; soft keeps the tag around for redirection purposes as an "alias"
.field
= select_tag :merge_mode, options_for_select({ Soft: :alias, Hard: :hard_merge }, :alias), class: "input"
= text_input f, :target_tag_name, class: "input", placeholder: "Target tag name", autocapitalize: "none", value: @tag.aliased_tag_name
br br
= submit "Save Tag", class: "button button--state-primary" = submit "Save Tag", class: "button button--state-primary"

View file

@ -0,0 +1,41 @@
.profile-top
.profile-top__avatar
.tag-info__image.thumb-tiny-container
= if @tag.image do
img src=tag_image(@tag) alt="spoiler image" width=50 height=50
- else
| no spoiler image
.profile-top__name-and-links
div
h1 Tag image
p Add a new image or remove the existing one here.
p SVG is preferred.
= form_for @changeset, Routes.tag_image_path(@conn, :update, @tag), [method: "put", multipart: true], fn f ->
= if @changeset.action do
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.
h4 Select an image
.image-other
#js-image-upload-previews
p Upload a file from your computer
.field
= file_input f, :image, class: "input js-scraper"
= error_tag f, :image_mime_type
.field-error-js.hidden.js-scraper
br
=> submit "Update tag image", class: "button"
br
= button_to "Remove tag image", Routes.tag_image_path(@conn, :delete, @tag), method: "delete", class: "button", data: [confirm: "Are you really, really sure?"]
br
= link "Back", to: Routes.tag_path(@conn, :show, @tag)

View file

@ -0,0 +1,6 @@
defmodule PhilomenaWeb.Tag.AliasView do
use PhilomenaWeb, :view
def alias_target(%{aliased_tag: nil}), do: ""
def alias_target(%{aliased_tag: tag}), do: tag.name
end

View file

@ -0,0 +1,5 @@
defmodule PhilomenaWeb.Tag.ImageView do
use PhilomenaWeb, :view
import PhilomenaWeb.TagView, only: [tag_image: 1]
end

View file

@ -14,6 +14,10 @@ defmodule PhilomenaWeb.TagView do
can?(conn, :edit, %Tag{}) can?(conn, :edit, %Tag{})
end end
def aliases_tags?(conn) do
can?(conn, :alias, %Tag{})
end
def tag_image(%{image: image}) do def tag_image(%{image: image}) do
tag_url_root() <> "/" <> image tag_url_root() <> "/" <> image
end end