post editing

This commit is contained in:
byte[] 2019-12-06 10:14:25 -05:00
parent d22ab9aa58
commit ae6e282424
9 changed files with 153 additions and 65 deletions

View file

@ -12,6 +12,7 @@ defmodule Philomena.Posts do
alias Philomena.Posts.Post alias Philomena.Posts.Post
alias Philomena.Forums.Forum alias Philomena.Forums.Forum
alias Philomena.Notifications alias Philomena.Notifications
alias Philomena.Versions
@doc """ @doc """
Gets a single post. Gets a single post.
@ -122,10 +123,23 @@ defmodule Philomena.Posts do
{:error, %Ecto.Changeset{}} {:error, %Ecto.Changeset{}}
""" """
def update_post(%Post{} = post, attrs) do def update_post(%Post{} = post, editor, attrs) do
post now = DateTime.utc_now() |> DateTime.truncate(:second)
|> Post.changeset(attrs) current_body = post.body
|> Repo.update() 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 end
@doc """ @doc """

View file

@ -24,7 +24,7 @@ defmodule Philomena.Posts.Post do
field :topic_position, :integer field :topic_position, :integer
field :hidden_from_users, :boolean, default: false field :hidden_from_users, :boolean, default: false
field :anonymous, :boolean, default: false field :anonymous, :boolean, default: false
field :edited_at, :naive_datetime field :edited_at, :utc_datetime
field :deletion_reason, :string, default: "" field :deletion_reason, :string, default: ""
field :destroyed_content, :boolean, default: false field :destroyed_content, :boolean, default: false
field :name_at_post_time, :string field :name_at_post_time, :string
@ -33,10 +33,13 @@ defmodule Philomena.Posts.Post do
end end
@doc false @doc false
def changeset(post, attrs) do def changeset(post, attrs, edited_at \\ nil) do
post post
|> cast(attrs, []) |> cast(attrs, [:body, :edit_reason])
|> validate_required([]) |> 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 end
@doc false @doc false

View file

@ -100,10 +100,8 @@ defmodule PhilomenaWeb.Image.CommentController do
|> put_flash(:info, "Comment updated successfully.") |> put_flash(:info, "Comment updated successfully.")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image) <> "#comment_#{comment.id}") |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image) <> "#comment_#{comment.id}")
_error -> {:error, :comment, changeset, _changes} ->
conn render(conn, "edit.html", comment: conn.assigns.comment, changeset: changeset)
|> put_flash(:error, "There was an error editing your comment")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
end end
end end
end end

View file

@ -1,15 +1,23 @@
defmodule PhilomenaWeb.Topic.PostController do defmodule PhilomenaWeb.Topic.PostController do
use PhilomenaWeb, :controller 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.UserStatistics
alias Philomena.Repo alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CanaryMapPlug, create: :show, edit: :show, update: :show 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_and_authorize_resource, model: Forum, id_field: "short_name", id_name: "forum_id", persisted: true
plug :load_topic 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 def create(conn, %{"post" => post_params}) do
attributes = conn.assigns.attributes attributes = conn.assigns.attributes
@ -33,20 +41,46 @@ defmodule PhilomenaWeb.Topic.PostController do
end end
end end
defp load_topic(%{params: %{"topic_id" => slug}} = conn, _args) do def edit(conn, _params) do
forum = conn.assigns.forum 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 user = conn.assigns.current_user
with topic when not is_nil(topic) <- Repo.get_by(Topic, slug: slug, forum_id: forum.id), case Posts.update_post(post, user, post_params) do
true <- Canada.Can.can?(user, :show, topic) {:ok, _post} ->
do Posts.reindex_post(post)
conn
|> assign(:topic, topic)
else
_ ->
conn conn
|> put_flash(:error, "Couldn't access that topic") |> put_flash(:info, "Post successfully edited.")
|> redirect(external: conn.assigns.referrer) |> 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 end
end end

View file

@ -107,6 +107,7 @@ defmodule PhilomenaWeb.Router do
resources "/topics", TopicController, only: [:new, :create] do resources "/topics", TopicController, only: [:new, :create] do
resources "/subscription", Topic.SubscriptionController, only: [:create, :delete], singleton: true resources "/subscription", Topic.SubscriptionController, only: [:create, :delete], singleton: true
resources "/read", Topic.ReadController, only: [:create], singleton: true resources "/read", Topic.ReadController, only: [:create], singleton: true
resources "/posts", Topic.PostController, only: [:edit, :update]
end end
resources "/subscription", Forum.SubscriptionController, only: [:create, :delete], singleton: true resources "/subscription", Forum.SubscriptionController, only: [:create, :delete], singleton: true

View file

@ -23,6 +23,7 @@
.field .field
= text_input f, :edit_reason, class: "input input--wide", placeholder: "Reason for edit" = 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" .block__tab.communication-edit__tab.hidden data-tab="preview"
' [Loading preview...] ' [Loading preview...]

View file

@ -6,14 +6,16 @@ div
i.fa.fa-flag> i.fa.fa-flag>
' Report ' Report
/- if post.edited_at && can?(:read, post) = if not is_nil(@post.edited_at) and can?(@conn, :read, @post) do
/ br br
/ a href=forum_topic_post_history_path(post.topic.forum, post.topic, post) a href=Routes.forum_topic_post_history_path(@conn, :index, @post.topic.forum, @post.topic, @post)
/ | Edited ' Edited
/ =<> friendly_time(post.edited_at) => pretty_time(@post.edited_at)
/ - if post.edit_reason.present?
/ | because: = if @post.edit_reason not in [nil, ""] do
/ =<> post.edit_reason ' because:
=> @post.edit_reason
div div
- link_path = Routes.forum_topic_path(@conn, :show, @post.topic.forum, @post.topic, post_id: @post.id) <> "#post_#{@post.id}" - 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) - 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 a.communication__interaction.post-reply href=link_path data-reply-url=link_path data-author=safe_author
i.fa.fa-reply> i.fa.fa-reply>
' Reply ' Reply
/span.owner-options.hidden
/ strong =<> link_to edit_forum_topic_post_path(post.topic.forum, post.topic, post), class: 'communication__interaction' do = if can?(@conn, :edit, @post) do
/ i.fas.fa-edit span.owner-options
/ ' Edit strong
a.communication__interaction href=Routes.forum_topic_post_path(@conn, :edit, @post.topic.forum, @post.topic, @post)
i.fa.fa-pencil>
' Edit

View file

@ -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&hellip;")]

View file

@ -7,39 +7,39 @@ h1
= for version <- @versions do = for version <- @versions do
article.block.communication article.block.communication
.block__content.flex.flex--no-wrap .block__content.flex.flex--no-wrap
.flex__fixed.spacing-right .flex__fixed.spacing-right
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn
.flex__grow.communication__body .flex__grow.communication__body
span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @post, awards: true, conn: @conn span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @post, awards: true, conn: @conn
br 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 .communication__body__text
= for edit <- version.difference do = for edit <- version.difference do
= case edit do = case edit do
- {:eq, value} -> - {:eq, value} ->
= escape_nl2br(value) = escape_nl2br(value)
- {:ins, value} -> - {:ins, value} ->
ins.differ = escape_nl2br(value) ins.differ = escape_nl2br(value)
- {:del, value} -> - {:del, value} ->
del.differ = escape_nl2br(value) del.differ = escape_nl2br(value)
.block__content.communication__options .block__content.communication__options
.flex.flex--wrap.flex--spaced-out .flex.flex--wrap.flex--spaced-out
div div
= if version.edit_reason not in [nil, ""] do = if version.edit_reason not in [nil, ""] do
' Reason: ' Reason:
= version.edit_reason = version.edit_reason
- else - else
' No reason given ' No reason given
.flex__right .flex__right
' Edited ' Edited
=> pretty_time(version.created_at) => pretty_time(version.created_at)
' by ' by
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: version, conn: @conn => render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: version, conn: @conn