diff --git a/lib/philomena/comments.ex b/lib/philomena/comments.ex index 6a118125..ae55ae10 100644 --- a/lib/philomena/comments.ex +++ b/lib/philomena/comments.ex @@ -11,6 +11,7 @@ defmodule Philomena.Comments do alias Philomena.Images.Image alias Philomena.Images alias Philomena.Notifications + alias Philomena.Versions @doc """ Gets a single comment. @@ -98,10 +99,23 @@ defmodule Philomena.Comments do {:error, %Ecto.Changeset{}} """ - def update_comment(%Comment{} = comment, attrs) do - comment - |> Comment.changeset(attrs) - |> Repo.update() + def update_comment(%Comment{} = comment, editor, attrs) do + now = DateTime.utc_now() |> DateTime.truncate(:second) + current_body = comment.body + current_reason = comment.edit_reason + + comment_changes = + Comment.changeset(comment, attrs, now) + + Multi.new + |> Multi.update(:comment, comment_changes) + |> Multi.run(:version, fn _repo, _changes -> + Versions.create_version("Comment", comment.id, editor.id, %{ + "body" => current_body, + "edit_reason" => current_reason + }) + end) + |> Repo.isolated_transaction(:serializable) end @doc """ diff --git a/lib/philomena/comments/comment.ex b/lib/philomena/comments/comment.ex index e6397738..987ee79f 100644 --- a/lib/philomena/comments/comment.ex +++ b/lib/philomena/comments/comment.ex @@ -23,6 +23,7 @@ defmodule Philomena.Comments.Comment do field :anonymous, :boolean, default: false field :hidden_from_users, :boolean, default: false field :edit_reason, :string + field :edited_at, :utc_datetime field :deletion_reason, :string, default: "" field :destroyed_content, :boolean, default: false field :name_at_post_time, :string @@ -40,9 +41,10 @@ defmodule Philomena.Comments.Comment do |> put_name_at_post_time(attribution[:user]) end - def changeset(comment, attrs) do + def changeset(comment, attrs, edited_at \\ nil) do comment |> cast(attrs, [:body, :edit_reason]) + |> put_change(:edited_at, edited_at) |> validate_required([:body]) |> validate_length(:body, min: 1, max: 300_000, count: :bytes) |> validate_length(:edit_reason, max: 70, count: :bytes) diff --git a/lib/philomena/users/ability.ex b/lib/philomena/users/ability.ex index 50e7c4a9..e75f6a89 100644 --- a/lib/philomena/users/ability.ex +++ b/lib/philomena/users/ability.ex @@ -85,6 +85,14 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do # Comment on images where that is allowed def can?(_user, :create_comment, %Image{hidden_from_users: false, commenting_allowed: true}), do: true + # Edit comments on images + def can?(%User{id: id}, :edit, %Comment{hidden_from_users: false, user_id: id} = comment) do + # comment must have been made no later than 15 minutes ago + time_ago = DateTime.utc_now() |> DateTime.add(-15 * 60) + + DateTime.diff(comment.created_at, time_ago) > 0 + end + # Edit metadata on images where that is allowed def can?(_user, :edit_metadata, %Image{hidden_from_users: false, tag_editing_allowed: true}), do: true def can?(%User{id: id}, :edit_description, %Image{user_id: id, hidden_from_users: false, description_editing_allowed: true}), do: true @@ -101,6 +109,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do def can?(_user, :show, %Topic{hidden_from_users: false}), do: true def can?(_user, :show, %Post{hidden_from_users: false}), do: true + # Edit posts + def can?(%User{id: id}, :edit, %Post{hidden_from_users: false, user_id: id}), do: true + # View profile pages def can?(_user, :show, %User{}), do: true diff --git a/lib/philomena/versions.ex b/lib/philomena/versions.ex index efe222b4..ca1fa6a9 100644 --- a/lib/philomena/versions.ex +++ b/lib/philomena/versions.ex @@ -61,8 +61,12 @@ defmodule Philomena.Versions do """ def create_version(item_type, item_id, whodunnit, attrs \\ %{}) do - %Version{item_type: item_type, item_id: item_id, whodunnit: whodunnit} + %Version{item_type: item_type, item_id: item_id, event: "update", whodunnit: whodunnit(whodunnit)} |> Version.changeset(attrs, item_id) |> Repo.insert() end + + # revolver ocelot + defp whodunnit(user_id) when is_integer(user_id), do: to_string(user_id) + defp whodunnit(nil), do: nil end diff --git a/lib/philomena_web/controllers/image/comment_controller.ex b/lib/philomena_web/controllers/image/comment_controller.ex index a0e5184e..185fbf87 100644 --- a/lib/philomena_web/controllers/image/comment_controller.ex +++ b/lib/philomena_web/controllers/image/comment_controller.ex @@ -12,8 +12,8 @@ defmodule PhilomenaWeb.Image.CommentController do plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true # Undo the previous private parameter screwery - plug PhilomenaWeb.CanaryMapPlug, create: :create, edit: :edit, update: :update - plug :load_and_authorize_resource, model: Comment, only: [:show], preload: [:image, user: [awards: :badge]] + plug PhilomenaWeb.CanaryMapPlug, create: :create, edit: :edit, update: :edit + plug :load_and_authorize_resource, model: Comment, only: [:show, :edit, :update], preload: [:image, user: [awards: :badge]] plug PhilomenaWeb.FilterBannedUsersPlug when action in [:create, :edit, :update] plug PhilomenaWeb.UserAttributionPlug when action in [:create] @@ -92,11 +92,13 @@ defmodule PhilomenaWeb.Image.CommentController do end def update(conn, %{"comment" => comment_params}) do - case Comments.update_comment(conn.assigns.comment, comment_params) do - {:ok, _comment} -> + case Comments.update_comment(conn.assigns.comment, conn.assigns.current_user, comment_params) do + {:ok, %{comment: comment}} -> + Comments.reindex_comment(comment) + conn |> put_flash(:info, "Comment updated successfully.") - |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image) <> "#comment_#{conn.assigns.comment.id}") + |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image) <> "#comment_#{comment.id}") _error -> conn diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 4218f733..8016bfbd 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -100,6 +100,7 @@ defmodule PhilomenaWeb.Router do resources "/hide", Image.HideController, only: [:create, :delete], singleton: true resources "/subscription", Image.SubscriptionController, only: [:create, :delete], singleton: true resources "/read", Image.ReadController, only: [:create], singleton: true + resources "/comments", Image.CommentController, only: [:edit, :update] end resources "/forums", ForumController, only: [] do diff --git a/lib/philomena_web/templates/comment/_comment_options.html.slime b/lib/philomena_web/templates/comment/_comment_options.html.slime index e5d41d30..2a42210b 100644 --- a/lib/philomena_web/templates/comment/_comment_options.html.slime +++ b/lib/philomena_web/templates/comment/_comment_options.html.slime @@ -6,14 +6,16 @@ div i.fa.fa-flag> ' Report - /- if comment.edited_at && can?(:read, comment) - / br - / a href=image_comment_history_path(comment.image, comment) - / | Edited - / =<> friendly_time(comment.edited_at) - / - if comment.edit_reason.present? - / | because: - / =<> comment.edit_reason + = if not is_nil(@comment.edited_at) and can?(@conn, :read, @comment) do + br + a href=Routes.image_comment_history_path(@conn, :index, @comment.image, @comment) + ' Edited + => pretty_time(@comment.edited_at) + + = if @comment.edit_reason not in [nil, ""] do + ' because: + => @comment.edit_reason + div - link_path = Routes.image_path(@conn, :show, @comment.image) <> "#comment_#{@comment.id}" - safe_author = PhilomenaWeb.PostView.textile_safe_author(@comment) @@ -30,7 +32,10 @@ div a.communication__interaction.post-reply href=link_path data-reply-url=link_path data-author=safe_author i.fa.fa-reply> ' Reply - /span.owner-options.hidden - / strong =<> link_to edit_image_comment_path(comment.image, comment), class: 'communication__interaction' do - / i.fas.fa-edit - / ' Edit + + = if can?(@conn, :edit, @comment) do + span.owner-options + strong + a.communication__interaction href=Routes.image_comment_path(@conn, :edit, @comment.image, @comment) + i.fa.fa-pencil> + ' Edit diff --git a/lib/philomena_web/templates/image/comment/edit.html.slime b/lib/philomena_web/templates/image/comment/edit.html.slime new file mode 100644 index 00000000..8ef211bd --- /dev/null +++ b/lib/philomena_web/templates/image/comment/edit.html.slime @@ -0,0 +1,31 @@ += form_for @changeset, Routes.image_comment_path(@conn, :update, @comment.image, @comment), fn f -> + = if @changeset.action do + .alert.alert-danger + p Oops, something went wrong! Please check the errors below. + + .block + .block__header.block__header--js-tabbed + a.selected href="#" data-click-tab="write" + i.fa.fa-pencil> + ' Edit + + a href="#" data-click-tab="preview" + i.fa.fa-eye> + ' Preview + + .block__tab.communication-edit__tab.selected data-tab="write" + = render PhilomenaWeb.TextileView, "_help.html", conn: @conn + = render PhilomenaWeb.TextileView, "_toolbar.html", conn: @conn + + .field + = textarea f, :body, class: "input input--wide input--text js-preview-input js-toolbar-input", placeholder: "Please read the site rules before posting and use [spoiler][/spoiler] for above-rating stuff.", required: true + = error_tag f, :body + + .field + = text_input f, :edit_reason, class: "input input--wide", placeholder: "Reason for edit" + + .block__tab.communication-edit__tab.hidden data-tab="preview" + ' [Loading preview...] + + .block__content.communication-edit__actions + => submit "Edit", class: "button", data: [disable_with: raw("Posting…")] \ No newline at end of file diff --git a/lib/philomena_web/templates/image/comment/history/index.html.slime b/lib/philomena_web/templates/image/comment/history/index.html.slime index 0ac2430a..c44add69 100644 --- a/lib/philomena_web/templates/image/comment/history/index.html.slime +++ b/lib/philomena_web/templates/image/comment/history/index.html.slime @@ -8,39 +8,39 @@ h1 = for version <- @versions do article.block.communication - .block__content.flex.flex--no-wrap - .flex__fixed.spacing-right - = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @comment, conn: @conn + .block__content.flex.flex--no-wrap + .flex__fixed.spacing-right + = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @comment, conn: @conn - .flex__grow.communication__body - span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @comment, awards: true, conn: @conn - br + .flex__grow.communication__body + span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @comment, awards: true, conn: @conn + br - = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @comment, conn: @conn + = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @comment, conn: @conn - .communication__body__text - = for edit <- version.difference do - = case edit do - - {:eq, value} -> - = escape_nl2br(value) + .communication__body__text + = for edit <- version.difference do + = case edit do + - {:eq, value} -> + = escape_nl2br(value) - - {:ins, value} -> - ins.differ = escape_nl2br(value) + - {:ins, value} -> + ins.differ = escape_nl2br(value) - - {:del, value} -> - del.differ = escape_nl2br(value) + - {:del, value} -> + del.differ = escape_nl2br(value) - .block__content.communication__options - .flex.flex--wrap.flex--spaced-out - div - = if version.edit_reason not in [nil, ""] do - ' Reason: - = version.edit_reason - - else - ' No reason given + .block__content.communication__options + .flex.flex--wrap.flex--spaced-out + div + = if version.edit_reason not in [nil, ""] do + ' Reason: + = version.edit_reason + - else + ' No reason given - .flex__right - ' Edited - => pretty_time(version.created_at) - ' by - => render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: version, conn: @conn \ No newline at end of file + .flex__right + ' Edited + => pretty_time(version.created_at) + ' by + => render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: version, conn: @conn \ No newline at end of file