diff --git a/lib/philomena/posts.ex b/lib/philomena/posts.ex index 96f7f254..cf0c79b8 100644 --- a/lib/philomena/posts.ex +++ b/lib/philomena/posts.ex @@ -12,6 +12,7 @@ defmodule Philomena.Posts do alias Philomena.Posts.Post alias Philomena.Forums.Forum alias Philomena.Notifications + alias Philomena.Versions @doc """ Gets a single post. @@ -122,10 +123,23 @@ defmodule Philomena.Posts do {:error, %Ecto.Changeset{}} """ - def update_post(%Post{} = post, attrs) do - post - |> Post.changeset(attrs) - |> Repo.update() + def update_post(%Post{} = post, editor, attrs) do + now = DateTime.utc_now() |> DateTime.truncate(:second) + current_body = post.body + current_reason = post.edit_reason + + post_changes = + Post.changeset(post, attrs, now) + + Multi.new + |> Multi.update(:post, post_changes) + |> Multi.run(:version, fn _repo, _changes -> + Versions.create_version("Post", post.id, editor.id, %{ + "body" => current_body, + "edit_reason" => current_reason + }) + end) + |> Repo.isolated_transaction(:serializable) end @doc """ diff --git a/lib/philomena/posts/post.ex b/lib/philomena/posts/post.ex index d463df28..393906e7 100644 --- a/lib/philomena/posts/post.ex +++ b/lib/philomena/posts/post.ex @@ -24,7 +24,7 @@ defmodule Philomena.Posts.Post do field :topic_position, :integer field :hidden_from_users, :boolean, default: false field :anonymous, :boolean, default: false - field :edited_at, :naive_datetime + field :edited_at, :utc_datetime field :deletion_reason, :string, default: "" field :destroyed_content, :boolean, default: false field :name_at_post_time, :string @@ -33,10 +33,13 @@ defmodule Philomena.Posts.Post do end @doc false - def changeset(post, attrs) do + def changeset(post, attrs, edited_at \\ nil) do post - |> cast(attrs, []) - |> validate_required([]) + |> 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) end @doc false diff --git a/lib/philomena_web/controllers/image/comment_controller.ex b/lib/philomena_web/controllers/image/comment_controller.ex index 185fbf87..4dca629c 100644 --- a/lib/philomena_web/controllers/image/comment_controller.ex +++ b/lib/philomena_web/controllers/image/comment_controller.ex @@ -100,10 +100,8 @@ defmodule PhilomenaWeb.Image.CommentController do |> put_flash(:info, "Comment updated successfully.") |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image) <> "#comment_#{comment.id}") - _error -> - conn - |> put_flash(:error, "There was an error editing your comment") - |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image)) + {:error, :comment, changeset, _changes} -> + render(conn, "edit.html", comment: conn.assigns.comment, changeset: changeset) end end end diff --git a/lib/philomena_web/controllers/topic/post_controller.ex b/lib/philomena_web/controllers/topic/post_controller.ex index a90e2d4f..f91c10c3 100644 --- a/lib/philomena_web/controllers/topic/post_controller.ex +++ b/lib/philomena_web/controllers/topic/post_controller.ex @@ -1,15 +1,23 @@ defmodule PhilomenaWeb.Topic.PostController do use PhilomenaWeb, :controller - alias Philomena.{Forums.Forum, Topics.Topic, Posts} + alias Philomena.{Forums.Forum, Topics.Topic, Posts.Post} + alias Philomena.Posts alias Philomena.UserStatistics alias Philomena.Repo + import Ecto.Query plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug plug PhilomenaWeb.CanaryMapPlug, create: :show, edit: :show, update: :show plug :load_and_authorize_resource, model: Forum, id_field: "short_name", id_name: "forum_id", persisted: true + plug :load_topic + plug PhilomenaWeb.CanaryMapPlug, create: :show, edit: :show, update: :show + plug :authorize_resource, model: Topic, id_field: "slug", id_name: "topic_id", persisted: true + + plug PhilomenaWeb.CanaryMapPlug, edit: :edit, update: :edit + plug :load_and_authorize_resource, model: Post, only: [:edit, :update], preload: [topic: :forum] def create(conn, %{"post" => post_params}) do attributes = conn.assigns.attributes @@ -33,20 +41,46 @@ defmodule PhilomenaWeb.Topic.PostController do end end - defp load_topic(%{params: %{"topic_id" => slug}} = conn, _args) do - forum = conn.assigns.forum + def edit(conn, _params) do + changeset = Posts.change_post(conn.assigns.post) + render(conn, "edit.html", changeset: changeset) + end + + def update(conn, %{"post" => post_params}) do + post = conn.assigns.post user = conn.assigns.current_user - with topic when not is_nil(topic) <- Repo.get_by(Topic, slug: slug, forum_id: forum.id), - true <- Canada.Can.can?(user, :show, topic) - do - conn - |> assign(:topic, topic) - else - _ -> + case Posts.update_post(post, user, post_params) do + {:ok, _post} -> + Posts.reindex_post(post) + conn - |> put_flash(:error, "Couldn't access that topic") - |> redirect(external: conn.assigns.referrer) + |> put_flash(:info, "Post successfully edited.") + |> redirect(to: Routes.forum_topic_path(conn, :show, post.topic.forum, post.topic, post_id: post.id) <> "#post_#{post.id}") + + {:error, :post, changeset, _changes} -> + render(conn, "edit.html", post: conn.assigns.post, changeset: changeset) + end + end + + defp load_topic(conn, _opts) do + user = conn.assigns.current_user + forum = conn.assigns.forum + topic = + Topic + |> where(forum_id: ^forum.id, slug: ^conn.params["topic_id"]) + |> preload(:forum) + |> Repo.one() + + cond do + is_nil(topic) -> + PhilomenaWeb.NotFoundPlug.call(conn) + + not Canada.Can.can?(user, :show, topic) -> + PhilomenaWeb.NotAuthorizedPlug.call(conn) + + true -> + Plug.Conn.assign(conn, :topic, topic) end end end \ No newline at end of file diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 8016bfbd..0b4aca0b 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -107,6 +107,7 @@ defmodule PhilomenaWeb.Router do resources "/topics", TopicController, only: [:new, :create] do resources "/subscription", Topic.SubscriptionController, only: [:create, :delete], singleton: true resources "/read", Topic.ReadController, only: [:create], singleton: true + resources "/posts", Topic.PostController, only: [:edit, :update] end resources "/subscription", Forum.SubscriptionController, only: [:create, :delete], singleton: true diff --git a/lib/philomena_web/templates/image/comment/edit.html.slime b/lib/philomena_web/templates/image/comment/edit.html.slime index 8ef211bd..01a7d7a3 100644 --- a/lib/philomena_web/templates/image/comment/edit.html.slime +++ b/lib/philomena_web/templates/image/comment/edit.html.slime @@ -23,6 +23,7 @@ .field = text_input f, :edit_reason, class: "input input--wide", placeholder: "Reason for edit" + = error_tag f, :edit_reason .block__tab.communication-edit__tab.hidden data-tab="preview" ' [Loading preview...] diff --git a/lib/philomena_web/templates/post/_post_options.html.slime b/lib/philomena_web/templates/post/_post_options.html.slime index f0bc1374..cd83745c 100644 --- a/lib/philomena_web/templates/post/_post_options.html.slime +++ b/lib/philomena_web/templates/post/_post_options.html.slime @@ -6,14 +6,16 @@ div i.fa.fa-flag> ' Report - /- if post.edited_at && can?(:read, post) - / br - / a href=forum_topic_post_history_path(post.topic.forum, post.topic, post) - / | Edited - / =<> friendly_time(post.edited_at) - / - if post.edit_reason.present? - / | because: - / =<> post.edit_reason + = if not is_nil(@post.edited_at) and can?(@conn, :read, @post) do + br + a href=Routes.forum_topic_post_history_path(@conn, :index, @post.topic.forum, @post.topic, @post) + ' Edited + => pretty_time(@post.edited_at) + + = if @post.edit_reason not in [nil, ""] do + ' because: + => @post.edit_reason + div - link_path = Routes.forum_topic_path(@conn, :show, @post.topic.forum, @post.topic, post_id: @post.id) <> "#post_#{@post.id}" - safe_author = textile_safe_author(@post) @@ -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_forum_topic_post_path(post.topic.forum, post.topic, post), class: 'communication__interaction' do - / i.fas.fa-edit - / ' Edit + + = if can?(@conn, :edit, @post) do + span.owner-options + strong + a.communication__interaction href=Routes.forum_topic_post_path(@conn, :edit, @post.topic.forum, @post.topic, @post) + i.fa.fa-pencil> + ' Edit diff --git a/lib/philomena_web/templates/topic/post/edit.html.slime b/lib/philomena_web/templates/topic/post/edit.html.slime new file mode 100644 index 00000000..89faf4c2 --- /dev/null +++ b/lib/philomena_web/templates/topic/post/edit.html.slime @@ -0,0 +1,32 @@ += form_for @changeset, Routes.forum_topic_post_path(@conn, :update, @post.topic.forum, @post.topic, @post), 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" + = error_tag f, :edit_reason + + .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/topic/post/history/index.html.slime b/lib/philomena_web/templates/topic/post/history/index.html.slime index 96be8d41..87c13ae4 100644 --- a/lib/philomena_web/templates/topic/post/history/index.html.slime +++ b/lib/philomena_web/templates/topic/post/history/index.html.slime @@ -7,39 +7,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: @post, conn: @conn + .block__content.flex.flex--no-wrap + .flex__fixed.spacing-right + = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn - .flex__grow.communication__body - span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @post, awards: true, conn: @conn - br + .flex__grow.communication__body + span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @post, awards: true, conn: @conn + br - = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @post, conn: @conn + = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @post, 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