diff --git a/lib/philomena/tags.ex b/lib/philomena/tags.ex index 84113f0f..4a322a81 100644 --- a/lib/philomena/tags.ex +++ b/lib/philomena/tags.ex @@ -174,9 +174,9 @@ defmodule Philomena.Tags do def alias_tag(%Tag{} = tag, attrs) do target_tag = Repo.get_by!(Tag, name: attrs["target_tag"]) - filters_hidden = where(Filter, [f], fragment("? @> ARRAY[?]", f.hidden_tag_ids, ^tag.id)) - filters_spoilered = where(Filter, [f], fragment("? @> ARRAY[?]", f.spoilered_tag_ids, ^tag.id)) - users_watching = where(User, [u], fragment("? @> ARRAY[?]", u.watched_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[?]::integer[]", f.spoilered_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_spoilered, :spoilered_tag_ids, tag.id, target_tag.id) @@ -186,7 +186,7 @@ defmodule Philomena.Tags do Repo.query!( "INSERT INTO image_taggings (image_id, tag_id) " <> "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} " <> "ON CONFLICT DO NOTHING" ) @@ -228,6 +228,12 @@ defmodule Philomena.Tags do |> Image.reindex() end + def unalias_tag(%Tag{} = tag) do + tag + |> Tag.unalias_changeset() + |> Repo.update() + end + defp array_replace(queryable, column, old_value, new_value) do queryable |> update( diff --git a/lib/philomena/tags/tag.ex b/lib/philomena/tags/tag.ex index 22b16135..8eea46dc 100644 --- a/lib/philomena/tags/tag.ex +++ b/lib/philomena/tags/tag.ex @@ -95,7 +95,7 @@ defmodule Philomena.Tags.Tag do tag |> cast(attrs, [:image, :image_format, :image_mime_type, :uploaded_image]) |> 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 def remove_image_changeset(tag) do @@ -104,6 +104,10 @@ defmodule Philomena.Tags.Tag do |> put_change(:image, nil) end + def unalias_changeset(tag) do + change(tag, aliased_tag_id: nil) + end + def creation_changeset(tag, attrs) do tag |> cast(attrs, [:name]) diff --git a/lib/philomena/users/ability.ex b/lib/philomena/users/ability.ex index 4b6c465d..9a102d43 100644 --- a/lib/philomena/users/ability.ex +++ b/lib/philomena/users/ability.ex @@ -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"}, :hide, %Topic{}), do: true - # Edit tags + # Edit and alias tags def can?(%User{role: "moderator"}, :edit, %Tag{}), do: true + def can?(%User{role: "moderator"}, :alias, %Tag{}), do: true # # Assistants can... diff --git a/lib/philomena_web/controllers/tag/alias_controller.ex b/lib/philomena_web/controllers/tag/alias_controller.ex new file mode 100644 index 00000000..6ad40b2c --- /dev/null +++ b/lib/philomena_web/controllers/tag/alias_controller.ex @@ -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 diff --git a/lib/philomena_web/controllers/tag/image_controller.ex b/lib/philomena_web/controllers/tag/image_controller.ex new file mode 100644 index 00000000..52d396b9 --- /dev/null +++ b/lib/philomena_web/controllers/tag/image_controller.ex @@ -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 diff --git a/lib/philomena_web/controllers/tag_controller.ex b/lib/philomena_web/controllers/tag_controller.ex index 1c5dd163..90308f30 100644 --- a/lib/philomena_web/controllers/tag_controller.ex +++ b/lib/philomena_web/controllers/tag_controller.ex @@ -8,7 +8,8 @@ defmodule PhilomenaWeb.TagController do plug PhilomenaWeb.RecodeParameterPlug, [name: "id"] when action in [:show] 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 query_string = params["tq"] || "*" @@ -128,4 +129,16 @@ defmodule PhilomenaWeb.TagController do |> String.replace("\"", "\\\"") 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 diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index fa3a5d65..5c1076a3 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -209,7 +209,10 @@ defmodule PhilomenaWeb.Router do resources "/claim", DuplicateReport.ClaimController, only: [:create, :delete], singleton: true 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 scope "/", PhilomenaWeb do diff --git a/lib/philomena_web/templates/tag/_tag_info_row.html.slime b/lib/philomena_web/templates/tag/_tag_info_row.html.slime index 56ce52cc..5f985d25 100644 --- a/lib/philomena_web/templates/tag/_tag_info_row.html.slime +++ b/lib/philomena_web/templates/tag/_tag_info_row.html.slime @@ -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 = if @tag.image do img src=tag_image(@tag) alt="spoiler image" @@ -18,21 +18,29 @@ = @tag.short_description 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 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 = if Enum.any?(@tag.implied_tags) do 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 = if present?(@tag.public_links) or present?(@tag.implied_by_tags) or present?(@tag.description) do br = link "Toggle detailed information", to: "#", data: [click_toggle: ".tag-info__more"] - .tag-info__more + .tag-info__more.hidden hr = if Enum.any?(@tag.public_links) do @@ -53,7 +61,7 @@ br = 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" ' Implied by (warning: unfiltered) @@ -83,5 +91,5 @@ ==> body | ( - = link "more info", to: "#" + = link "more info", to: Routes.dnp_entry_path(@conn, :show, entry) | ) diff --git a/lib/philomena_web/templates/tag/alias/edit.html.slime b/lib/philomena_web/templates/tag/alias/edit.html.slime new file mode 100644 index 00000000..249c69e6 --- /dev/null +++ b/lib/philomena_web/templates/tag/alias/edit.html.slime @@ -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) diff --git a/lib/philomena_web/templates/tag/edit.html.slime b/lib/philomena_web/templates/tag/edit.html.slime index 093c9c17..5bd3d36f 100644 --- a/lib/philomena_web/templates/tag/edit.html.slime +++ b/lib/philomena_web/templates/tag/edit.html.slime @@ -1,5 +1,8 @@ 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 -> = if @changeset.action do .alert.alert-danger @@ -30,13 +33,6 @@ h1 Editing Tag .field = 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 = submit "Save Tag", class: "button button--state-primary" diff --git a/lib/philomena_web/templates/tag/image/edit.html.slime b/lib/philomena_web/templates/tag/image/edit.html.slime new file mode 100644 index 00000000..c452561f --- /dev/null +++ b/lib/philomena_web/templates/tag/image/edit.html.slime @@ -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) diff --git a/lib/philomena_web/views/tag/alias_view.ex b/lib/philomena_web/views/tag/alias_view.ex new file mode 100644 index 00000000..0dc1545c --- /dev/null +++ b/lib/philomena_web/views/tag/alias_view.ex @@ -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 diff --git a/lib/philomena_web/views/tag/image_view.ex b/lib/philomena_web/views/tag/image_view.ex new file mode 100644 index 00000000..b9d8b1e6 --- /dev/null +++ b/lib/philomena_web/views/tag/image_view.ex @@ -0,0 +1,5 @@ +defmodule PhilomenaWeb.Tag.ImageView do + use PhilomenaWeb, :view + + import PhilomenaWeb.TagView, only: [tag_image: 1] +end diff --git a/lib/philomena_web/views/tag_view.ex b/lib/philomena_web/views/tag_view.ex index bd7e0219..4b6841dd 100644 --- a/lib/philomena_web/views/tag_view.ex +++ b/lib/philomena_web/views/tag_view.ex @@ -14,6 +14,10 @@ defmodule PhilomenaWeb.TagView do can?(conn, :edit, %Tag{}) end + def aliases_tags?(conn) do + can?(conn, :alias, %Tag{}) + end + def tag_image(%{image: image}) do tag_url_root() <> "/" <> image end