class PostRevisionSerializer < ApplicationSerializer attributes :created_at, :post_id, # which revision is hidden :previous_hidden, :current_hidden, # dynamic & based on the current scope :first_revision, :previous_revision, :current_revision, :next_revision, :last_revision, # used for display :current_version, :version_count, # from the user :username, :display_username, :avatar_template, # all the changes :edit_reason, :body_changes, :title_changes, :user_changes, :tags_changes, :wiki, :can_edit # Creates a field called field_name_changes with previous and # current members if a field has changed in this revision def self.add_compared_field(field) changes_name = "#{field}_changes".to_sym self.attributes changes_name define_method(changes_name) do { previous: previous[field], current: current[field] } end define_method("include_#{changes_name}?") do previous[field] != current[field] end end add_compared_field :wiki def previous_hidden previous["hidden"] end def current_hidden current["hidden"] end def first_revision revisions.first["revision"] end def previous_revision @previous_revision ||= revisions.select { |r| r["revision"] >= first_revision } .select { |r| r["revision"] < current_revision } .last.try(:[], "revision") end def current_revision object.number end def next_revision @next_revision ||= revisions.select { |r| r["revision"] <= last_revision } .select { |r| r["revision"] > current_revision } .first.try(:[], "revision") end def last_revision @last_revision ||= revisions.select { |r| r["revision"] <= post.version }.last["revision"] end def current_version @current_version ||= revisions.select { |r| r["revision"] <= current_revision }.count + 1 end def version_count revisions.count end def username user.username_lower end def display_username user.username end def avatar_template user.avatar_template end def wiki object.post.wiki end def can_edit scope.can_edit?(object.post) end def edit_reason # only show 'edit_reason' when revisions are consecutive current["edit_reason"] if scope.can_view_hidden_post_revisions? || current["revision"] == previous["revision"] + 1 end def body_changes cooked_diff = DiscourseDiff.new(previous["cooked"], current["cooked"]) raw_diff = DiscourseDiff.new(previous["raw"], current["raw"]) { inline: cooked_diff.inline_html, side_by_side: cooked_diff.side_by_side_html, side_by_side_markdown: raw_diff.side_by_side_markdown } end def title_changes prev = "<div>#{previous["title"] && CGI::escapeHTML(previous["title"])}</div>" cur = "<div>#{current["title"] && CGI::escapeHTML(current["title"])}</div>" # always show the title for post_number == 1 return if object.post.post_number > 1 && prev == cur diff = DiscourseDiff.new(prev, cur) { inline: diff.inline_html, side_by_side: diff.side_by_side_html } end def user_changes prev = previous["user_id"] cur = current["user_id"] return if prev == cur # if stuff is messed up, default to system previous = User.find_by(id: prev) || Discourse.system_user current = User.find_by(id: cur) || Discourse.system_user { previous: { username: previous.username_lower, display_username: previous.username, avatar_template: previous.avatar_template }, current: { username: current.username_lower, display_username: current.username, avatar_template: current.avatar_template } } end def tags_changes { previous: previous["tags"], current: current["tags"] } end def include_tags_changes? SiteSetting.tagging_enabled && previous["tags"] != current["tags"] end protected def post @post ||= object.post end def topic @topic ||= object.post.topic end def revisions @revisions ||= all_revisions.select { |r| scope.can_view_hidden_post_revisions? || !r["hidden"] } end def all_revisions return @all_revisions if @all_revisions post_revisions = PostRevision.where(post_id: object.post_id).order(:number).to_a latest_modifications = { "raw" => [post.raw], "cooked" => [post.cooked], "edit_reason" => [post.edit_reason], "wiki" => [post.wiki], "post_type" => [post.post_type], "user_id" => [post.user_id] } # Retrieve any `tracked_topic_fields` PostRevisor.tracked_topic_fields.each_key do |field| if topic.respond_to?(field) latest_modifications[field.to_s] = [topic.send(field)] end end if SiteSetting.topic_featured_link_enabled latest_modifications["featured_link"] = [post.topic.featured_link] end if SiteSetting.tagging_enabled latest_modifications["tags"] = [post.topic.tags.map(&:name)] end post_revisions << PostRevision.new( number: post_revisions.last.number + 1, hidden: post.hidden, modifications: latest_modifications ) @all_revisions = [] # backtrack post_revisions.each do |pr| revision = HashWithIndifferentAccess.new revision[:revision] = pr.number revision[:hidden] = pr.hidden pr.modifications.each_key do |field| revision[field] = pr.modifications[field][0] end @all_revisions << revision end # waterfall (@all_revisions.count - 1).downto(1).each do |r| cur = @all_revisions[r] prev = @all_revisions[r - 1] cur.each_key do |field| prev[field] = prev.has_key?(field) ? prev[field] : cur[field] end end @all_revisions end def previous @previous ||= revisions.select { |r| r["revision"] <= current_revision }.last end def current @current ||= revisions.select { |r| r["revision"] > current_revision }.first end def user # if stuff goes pear shape attribute to system object.user || Discourse.system_user end end