mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-30 14:57:59 +01:00
tag description and implication editing
This commit is contained in:
parent
2eb39b43aa
commit
4f3390f728
10 changed files with 233 additions and 17 deletions
|
@ -4,9 +4,13 @@ defmodule Philomena.Tags do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
|
alias Ecto.Multi
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
|
|
||||||
alias Philomena.Tags.Tag
|
alias Philomena.Tags.Tag
|
||||||
|
alias Philomena.Tags.Uploader
|
||||||
|
alias Philomena.Images
|
||||||
|
alias Philomena.Images.Image
|
||||||
|
|
||||||
@spec get_or_create_tags(String.t()) :: List.t()
|
@spec get_or_create_tags(String.t()) :: List.t()
|
||||||
def get_or_create_tags(tag_list) do
|
def get_or_create_tags(tag_list) do
|
||||||
|
@ -96,11 +100,44 @@ defmodule Philomena.Tags do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def update_tag(%Tag{} = tag, attrs) do
|
def update_tag(%Tag{} = tag, attrs) do
|
||||||
|
tag_input = Tag.parse_tag_list(attrs["implied_tag_list"])
|
||||||
|
implied_tags =
|
||||||
|
Tag
|
||||||
|
|> where([t], t.name in ^tag_input)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
tag
|
tag
|
||||||
|> Tag.changeset(attrs)
|
|> Tag.changeset(attrs, implied_tags)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_tag_image(%Tag{} = tag, attrs) do
|
||||||
|
changeset = Uploader.analyze_upload(tag, attrs)
|
||||||
|
|
||||||
|
Multi.new
|
||||||
|
|> Multi.update(:tag, changeset)
|
||||||
|
|> Multi.run(:update_file, fn _repo, %{tag: tag} ->
|
||||||
|
Uploader.persist_upload(tag)
|
||||||
|
Uploader.unpersist_old_upload(tag)
|
||||||
|
|
||||||
|
{:ok, nil}
|
||||||
|
end)
|
||||||
|
|> Repo.isolated_transaction(:serializable)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_tag_image(%Tag{} = tag) do
|
||||||
|
changeset = Tag.remove_image_changeset(tag)
|
||||||
|
|
||||||
|
Multi.new
|
||||||
|
|> Multi.update(:tag, changeset)
|
||||||
|
|> Multi.run(:remove_file, fn _repo, %{tag: tag} ->
|
||||||
|
Uploader.unpersist_old_upload(tag)
|
||||||
|
|
||||||
|
{:ok, nil}
|
||||||
|
end)
|
||||||
|
|> Repo.isolated_transaction(:serializable)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a Tag.
|
Deletes a Tag.
|
||||||
|
|
||||||
|
@ -114,7 +151,19 @@ defmodule Philomena.Tags do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def delete_tag(%Tag{} = tag) do
|
def delete_tag(%Tag{} = tag) do
|
||||||
Repo.delete(tag)
|
image_ids =
|
||||||
|
Image
|
||||||
|
|> join(:inner, [i], _ in assoc(i, :tags))
|
||||||
|
|> where([_i, t], t.id == ^tag.id)
|
||||||
|
|> select([i, _t], i.id)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
{:ok, _tag} = Repo.delete(tag)
|
||||||
|
|
||||||
|
Image
|
||||||
|
|> where([i], i.id in ^image_ids)
|
||||||
|
|> preload(^Images.indexing_preloads())
|
||||||
|
|> Image.reindex()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -50,7 +50,7 @@ defmodule Philomena.Tags.Tag do
|
||||||
schema "tags" do
|
schema "tags" do
|
||||||
belongs_to :aliased_tag, Tag, source: :aliased_tag_id
|
belongs_to :aliased_tag, Tag, source: :aliased_tag_id
|
||||||
has_many :aliases, Tag, foreign_key: :aliased_tag_id
|
has_many :aliases, Tag, foreign_key: :aliased_tag_id
|
||||||
many_to_many :implied_tags, Tag, join_through: "tags_implied_tags", join_keys: [tag_id: :id, implied_tag_id: :id]
|
many_to_many :implied_tags, Tag, join_through: "tags_implied_tags", join_keys: [tag_id: :id, implied_tag_id: :id], on_replace: :delete
|
||||||
many_to_many :implied_by_tags, Tag, join_through: "tags_implied_tags", join_keys: [implied_tag_id: :id, tag_id: :id]
|
many_to_many :implied_by_tags, Tag, join_through: "tags_implied_tags", join_keys: [implied_tag_id: :id, tag_id: :id]
|
||||||
has_many :public_links, UserLink, where: [public: true, aasm_state: "verified"]
|
has_many :public_links, UserLink, where: [public: true, aasm_state: "verified"]
|
||||||
has_many :dnp_entries, DnpEntry, where: [aasm_state: "listed"]
|
has_many :dnp_entries, DnpEntry, where: [aasm_state: "listed"]
|
||||||
|
@ -68,17 +68,42 @@ defmodule Philomena.Tags.Tag do
|
||||||
field :image_mime_type, :string
|
field :image_mime_type, :string
|
||||||
field :mod_notes, :string
|
field :mod_notes, :string
|
||||||
|
|
||||||
|
field :uploaded_image, :string, virtual: true
|
||||||
|
field :removed_image, :string, virtual: true
|
||||||
|
|
||||||
|
field :implied_tag_list, :string, virtual: true
|
||||||
|
|
||||||
timestamps(inserted_at: :created_at)
|
timestamps(inserted_at: :created_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(tag, attrs) do
|
def changeset(tag, attrs) do
|
||||||
tag
|
tag
|
||||||
|> cast(attrs, [])
|
|> cast(attrs, [:category, :description, :short_description, :mod_notes])
|
||||||
|
|> put_change(:implied_tag_list, Enum.map_join(tag.implied_tags, ",", & &1.name))
|
||||||
|> validate_required([])
|
|> validate_required([])
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
def changeset(tag, attrs, implied_tags) do
|
||||||
|
tag
|
||||||
|
|> cast(attrs, [:category, :description, :short_description, :mod_notes])
|
||||||
|
|> put_assoc(:implied_tags, implied_tags)
|
||||||
|
|> validate_required([])
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_changeset(tag, attrs) 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))
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_image_changeset(tag) do
|
||||||
|
change(tag)
|
||||||
|
|> put_change(:removed_image, tag.image)
|
||||||
|
|> put_change(:image, nil)
|
||||||
|
end
|
||||||
|
|
||||||
def creation_changeset(tag, attrs) do
|
def creation_changeset(tag, attrs) do
|
||||||
tag
|
tag
|
||||||
|> cast(attrs, [:name])
|
|> cast(attrs, [:name])
|
||||||
|
@ -112,6 +137,20 @@ defmodule Philomena.Tags.Tag do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def categories do
|
||||||
|
[
|
||||||
|
"error",
|
||||||
|
"rating",
|
||||||
|
"origin",
|
||||||
|
"character",
|
||||||
|
"oc",
|
||||||
|
"species",
|
||||||
|
"content-fanmade",
|
||||||
|
"content-official",
|
||||||
|
"spoiler"
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
def clean_tag_name(name) do
|
def clean_tag_name(name) do
|
||||||
# Downcase, replace extra runs of spaces, replace unicode quotes
|
# Downcase, replace extra runs of spaces, replace unicode quotes
|
||||||
# with ascii quotes, trim space from end
|
# with ascii quotes, trim space from end
|
||||||
|
|
24
lib/philomena/tags/uploader.ex
Normal file
24
lib/philomena/tags/uploader.ex
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule Philomena.Tags.Uploader do
|
||||||
|
@moduledoc """
|
||||||
|
Upload and processing callback logic for Tag images.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Philomena.Tags.Tag
|
||||||
|
alias Philomena.Uploader
|
||||||
|
|
||||||
|
def analyze_upload(tag, params) do
|
||||||
|
Uploader.analyze_upload(tag, "image", params["image"], &Tag.image_changeset/2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def persist_upload(tag) do
|
||||||
|
Uploader.persist_upload(tag, tag_file_root(), "image")
|
||||||
|
end
|
||||||
|
|
||||||
|
def unpersist_old_upload(tag) do
|
||||||
|
Uploader.unpersist_old_upload(tag, tag_file_root(), "image")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp tag_file_root do
|
||||||
|
Application.get_env(:philomena, :tag_file_root)
|
||||||
|
end
|
||||||
|
end
|
|
@ -80,6 +80,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
# Hide topics
|
# Hide topics
|
||||||
def can?(%User{role: "moderator"}, :hide, %Topic{}), do: true
|
def can?(%User{role: "moderator"}, :hide, %Topic{}), do: true
|
||||||
|
|
||||||
|
# Edit tags
|
||||||
|
def can?(%User{role: "moderator"}, :edit, %Tag{}), do: true
|
||||||
|
|
||||||
#
|
#
|
||||||
# Assistants can...
|
# Assistants can...
|
||||||
#
|
#
|
||||||
|
|
|
@ -14,7 +14,7 @@ defmodule PhilomenaWeb.Admin.Report.ClaimController do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:info, "Successfully marked report as in progress")
|
|> put_flash(:info, "Successfully marked report as in progress")
|
||||||
|> redirect(to: Routes.admin_report_path(conn, :show, report))
|
|> redirect(to: Routes.admin_report_path(conn, :index))
|
||||||
|
|
||||||
{:error, _changeset} ->
|
{:error, _changeset} ->
|
||||||
conn
|
conn
|
||||||
|
@ -31,4 +31,4 @@ defmodule PhilomenaWeb.Admin.Report.ClaimController do
|
||||||
|> put_flash(:info, "Successfully released report.")
|
|> put_flash(:info, "Successfully released report.")
|
||||||
|> redirect(to: Routes.admin_report_path(conn, :show, report))
|
|> redirect(to: Routes.admin_report_path(conn, :show, report))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,9 @@ defmodule PhilomenaWeb.TagController do
|
||||||
alias Philomena.{Tags, Tags.Tag}
|
alias Philomena.{Tags, Tags.Tag}
|
||||||
alias Philomena.Textile.Renderer
|
alias Philomena.Textile.Renderer
|
||||||
alias Philomena.Interactions
|
alias Philomena.Interactions
|
||||||
alias Philomena.Repo
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
plug PhilomenaWeb.RecodeParameterPlug, [name: "id"] when action in [:show]
|
plug PhilomenaWeb.RecodeParameterPlug, [name: "id"] when action in [:show]
|
||||||
|
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]
|
||||||
|
|
||||||
def index(conn, params) do
|
def index(conn, params) do
|
||||||
query_string = params["tq"] || "*"
|
query_string = params["tq"] || "*"
|
||||||
|
@ -32,14 +31,9 @@ defmodule PhilomenaWeb.TagController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(conn, %{"id" => slug}) do
|
def show(conn, _params) do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
tag = conn.assigns.tag
|
||||||
tag =
|
|
||||||
Tag
|
|
||||||
|> where(slug: ^slug)
|
|
||||||
|> preload([:aliases, :implied_tags, :implied_by_tags, :dnp_entries, public_links: :user])
|
|
||||||
|> Repo.one()
|
|
||||||
|
|
||||||
{images, _tags} =
|
{images, _tags} =
|
||||||
ImageLoader.query(conn, %{term: %{"namespaced_tags.name" => tag.name}})
|
ImageLoader.query(conn, %{term: %{"namespaced_tags.name" => tag.name}})
|
||||||
|
@ -73,6 +67,35 @@ defmodule PhilomenaWeb.TagController do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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(conn.assigns.tag, tag_params) do
|
||||||
|
{:ok, tag} ->
|
||||||
|
Tags.reindex_tag(tag)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Tag successfully updated.")
|
||||||
|
|> redirect(to: Routes.tag_path(conn, :show, tag))
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
render(conn, "edit.html", changeset: changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, _params) do
|
||||||
|
spawn fn ->
|
||||||
|
Tags.delete_tag(conn.assigns.tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Tag scheduled for deletion.")
|
||||||
|
|> redirect(to: "/")
|
||||||
|
end
|
||||||
|
|
||||||
def escape_name(%{name: name}) do
|
def escape_name(%{name: name}) do
|
||||||
name =
|
name =
|
||||||
name
|
name
|
||||||
|
|
|
@ -208,6 +208,8 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/reject", DuplicateReport.RejectController, only: [:create], singleton: true
|
resources "/reject", DuplicateReport.RejectController, only: [:create], singleton: true
|
||||||
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]
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", PhilomenaWeb do
|
scope "/", PhilomenaWeb do
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
.flex__grow
|
.flex__grow
|
||||||
= render PhilomenaWeb.TagView, "_tag.html", tag: @tag, conn: @conn
|
= render PhilomenaWeb.TagView, "_tag.html", tag: @tag, conn: @conn
|
||||||
= link "Tag changes", to: Routes.tag_tag_change_path(@conn, :index, @tag), class: "detail-link"
|
= link "Tag changes", to: Routes.tag_tag_change_path(@conn, :index, @tag), class: "detail-link"
|
||||||
|
= if manages_tags?(@conn) do
|
||||||
|
= link "Edit details", to: Routes.tag_path(@conn, :edit, @tag), class: "detail-link"
|
||||||
|
|
||||||
br
|
br
|
||||||
|
|
||||||
= if @tag.short_description not in [nil, ""] do
|
= if @tag.short_description not in [nil, ""] do
|
||||||
|
@ -81,4 +84,4 @@
|
||||||
|
|
||||||
| (
|
| (
|
||||||
= link "more info", to: "#"
|
= link "more info", to: "#"
|
||||||
| )
|
| )
|
||||||
|
|
65
lib/philomena_web/templates/tag/edit.html.slime
Normal file
65
lib/philomena_web/templates/tag/edit.html.slime
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
h1 Editing Tag
|
||||||
|
|
||||||
|
= form_for @changeset, Routes.tag_path(@conn, :update, @tag), [class: "form"], fn f ->
|
||||||
|
= if @changeset.action do
|
||||||
|
.alert.alert-danger
|
||||||
|
p Oops, something went wrong! Please check the errors below.
|
||||||
|
|
||||||
|
h2
|
||||||
|
= @tag.name
|
||||||
|
|
||||||
|
.field
|
||||||
|
' Category:
|
||||||
|
= select f, :category, tag_categories(), class: "input"
|
||||||
|
|
||||||
|
h4 Description
|
||||||
|
.field
|
||||||
|
=> label f, :short_description, "Short description:"
|
||||||
|
= text_input f, :short_description, class: "input input--wide"
|
||||||
|
|
||||||
|
.field
|
||||||
|
=> label f, :description, "Long description:"
|
||||||
|
= textarea f, :description, class: "input input--wide"
|
||||||
|
|
||||||
|
.field
|
||||||
|
=> label f, :mod_notes, "Mod notes:"
|
||||||
|
= textarea f, :mod_notes, class: "input input--wide"
|
||||||
|
|
||||||
|
h4 Implied Tags
|
||||||
|
.fieldlabel Tags in this list will be added when this tag is added to an image.
|
||||||
|
.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"
|
||||||
|
|
||||||
|
/ not ready yet
|
||||||
|
br
|
||||||
|
br
|
||||||
|
input.toggle-box#tag-management checked="false" type="checkbox"
|
||||||
|
label for="tag-management" Tag Processing
|
||||||
|
.toggle-box-container
|
||||||
|
.toggle-box-container__content
|
||||||
|
= link_to "Rebuild index", admin_tag_reindex_path(@tag), class: "button", data: { confirm: t("are_you_sure") }, method: :post
|
||||||
|
p Use this if the tag displays the wrong number of images or returns the wrong search results.
|
||||||
|
= link_to "Recreate slug", admin_tag_slug_path(@tag), class: "button", method: :post
|
||||||
|
p
|
||||||
|
| Use this for old tags with invalid slugs (
|
||||||
|
code
|
||||||
|
' /mlp/ →
|
||||||
|
= Tag.generate_slug "/mlp/"
|
||||||
|
| )
|
||||||
|
= link_to "Destroy tag", admin_tag_path(@tag), class: "button button--state-danger", data: { confirm: t("are_you_sure") }, method: :delete
|
||||||
|
p
|
||||||
|
strong Irreversible. Use with extreme caution!
|
||||||
|
ul
|
||||||
|
li Intended use is removing garbage tags.
|
||||||
|
li Will remove tag changes on the tag, but not on images or profiles.
|
||||||
|
li Will fail if the tag is the target of an alias, is implied by other tags, or is a rating tag.
|
|
@ -6,6 +6,14 @@ defmodule PhilomenaWeb.TagView do
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
def tag_categories do
|
||||||
|
[[key: "-", value: ""] | Tag.categories]
|
||||||
|
end
|
||||||
|
|
||||||
|
def manages_tags?(conn) do
|
||||||
|
can?(conn, :edit, %Tag{})
|
||||||
|
end
|
||||||
|
|
||||||
def tag_image(%{image: image}) do
|
def tag_image(%{image: image}) do
|
||||||
tag_url_root() <> "/" <> image
|
tag_url_root() <> "/" <> image
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue