app/helpers/history_helper.rb
# encoding: utf-8
module HistoryHelper
include PostHelper
# :all: By default, some changes are not displayed. When displaying details
# for a single change, set :all=>true to display all changes.
#
# :show_all_tags: Show unchanged tags.
def get_default_field_options
@default_field_options ||= {
:suppress_fields => []
}
end
def get_attribute_options
return @att_options if @att_options
@att_options = {
# :suppress_fields => If this attribute was changed, don't display changes to specified
# fields to the same object in the same change.
#
# :force_show_initial => For initial changes, created when the object itself is created,
# attributes that are set to an explicit :default are omitted from the display. This
# prevents things like "parent:none" being shown for every new post. Set :force_show_initial
# to override this behavior.
#
# :primary_order => Changes are sorted alphabetically by field name. :primary_order
# overrides this sorting with a top-level sort (default 1).
#
# :never_obsolete => Changes that are no longer current or have been reverted are
# given the class "obsolete". Changes in fields named by :never_obsolete are not
# tested.
#
# Some cases:
#
# - When viewing a single object (eg. "post:123"), the display is always changed to
# the appropriate type, so if we're viewing a single object, :specific_table will
# always be true.
#
# - Changes to pool descriptions can be large, and are reduced to "description changed"
# in the "All" view. The diff is displayed if viewing the Pool view or a specific object.
#
# - Adding a post to a pool usually causes the sequence number to change, too, but
# this isn't very interesting and clutters the display. :suppress_fields is used
# to hide these unless viewing the specific change.
:Post => {
:fields => {
:cached_tags => { :primary_order => 2 }, # show tag changes after other things
:source => { :primary_order => 3 }
},
:never_obsolete => { :cached_tags => true } # tags handle obsolete themselves per-tag
},
:Pool => {
:primary_order => 0,
:fields => {
:description => { :primary_order => 5 } # we don't handle commas correctly if this isn't last
},
:never_obsolete => { :description => true } # changes to description aren't obsolete just because the text has changed again
},
:PoolPost => {
:fields => {
:sequence => { :max_to_display => 5 },
:active => {
:max_to_display => 10,
:suppress_fields => [:sequence], # changing active usually changes sequence; this isn't interesting
:primary_order => 2, # show pool post changes after other things
},
:cached_tags => {}
}
},
:Tag => {
},
:Note => {
}
}
@att_options.each_key do |classname|
@att_options[classname] = {
:fields => {},
:primary_order => 1,
:never_obsolete => {},
:force_show_initial => {}
}.merge(@att_options[classname])
c = @att_options[classname][:fields]
c.each_key do |field|
c[field] = get_default_field_options.merge(c[field])
end
end
@att_options
end
def format_changes(history, options = {})
changes = history.history_changes
# Group the changes by class and field.
change_groups = {}
changes.each do |c|
change_groups[c.table_name] ||= {}
change_groups[c.table_name][c.column_name.to_sym] ||= []
change_groups[c.table_name][c.column_name.to_sym] << c
end
att_options = get_attribute_options
# Number of changes hidden (not including suppressions):
hidden = 0
parts = []
change_groups.each do |_table_name, fields|
# Apply supressions.
to_suppress = []
fields.each do |field, group|
class_name = group[0].master_class.to_s.to_sym
table_options = att_options[class_name] ||= {}
field_options = table_options[:fields][field] || get_default_field_options
to_suppress += field_options[:suppress_fields]
end
to_suppress.each { |suppress| fields.delete(suppress) }
fields.each do |field, group|
class_name = group[0].master_class.to_s.to_sym
table_options = att_options[class_name] ||= {}
field_options = table_options[:fields][field] || get_default_field_options
# Check for entry limits.
unless options[:specific_history]
max = field_options[:max_to_display]
if max && group.length > max
hidden += group.length - max
group = group[0, max] || []
end
end
# Format the rest.
group.each do |c|
if !c.previous && c.changes_to_default? && !table_options[:force_show_initial][field]
next
end
part = format_change(history, c, options, table_options)
next unless part
part = part.merge(:primary_order => field_options[:primary_order] || table_options[:primary_order])
parts << part
end
end
end
parts.sort! do |a, b|
comp = 0
[:primary_order, :field, :sort_key].each do |field|
comp = a[field] <=> b[field]
break if comp != 0
end
comp
end
parts.each_index do |idx|
next if idx == 0
next if parts[idx][:field] == parts[idx - 1][:field]
parts[idx - 1][:html] << ", "
end
html = ""
if !options[:show_name] && history.group_by_table == "tags"
tag = history.history_changes.first.obj
html << tag_link(tag.name)
html << ": "
end
if history.aux["note_body"]
body = history.aux["note_body"]
body = body[0, 20] + "..." if body.length > 20
html << "note #{h(body)}: "
end
html << parts.map { |part| part[:html] }.join(" ")
if hidden > 0
html << " (#{link_to("%i more..." % hidden, :search => "change:%i" % history.id)})"
end
# FIXME: more why.jpg on rbx.
html.force_encoding("utf-8").html_safe
end
def format_change(_history, change, options, table_options)
html = ""
classes = []
if !table_options[:never_obsolete][change.column_name.to_sym] && change.is_obsolete?
classes << ["obsolete"]
end
added = %(<span class="added">+</span>)
removed = %(<span class="removed">-</span>)
sort_key = change.remote_id
case change.table_name
when"posts"
case change.column_name
when "rating"
html << %(<span class="changed-post-rating">rating:)
html << change.value
if change.previous
html << %(←)
html << change.previous.value
end
html << %(</span>)
when "parent_id"
html << "parent:"
if change.value
begin
new = Post.find(change.value.to_i)
html << link_to("%i" % new.id, :controller => "post", :action => "show", :id => new.id)
rescue ActiveRecord::RecordNotFound
html << "%i" % change.value.to_i
end
else
html << "none"
end
if change.previous
html << %(←)
if change.previous.value
begin
old = Post.find(change.previous.value.to_i)
html << link_to("%i" % old.id, :controller => "post", :action => "show", :id => old.id)
rescue ActiveRecord::RecordNotFound
html << "%i" % change.previous.value.to_i
end
else
html << "none"
end
end
when "source"
if change.previous
html << "source changed from <span class='name-change'>%s</span> to <span class='name-change'>%s</span>" % [source_link(change.previous.value, false), source_link(change.value, false)]
else
html << "source: <span class='name-change'>%s</span>" % [source_link(change.value, false)]
end
when "frames_pending"
html << "frames changed: #{h(change.value.empty? ? "(none)" : change.value)}"
when "is_rating_locked"
html << (change.value.trueish? ? added : removed)
html << "rating-locked"
when "is_note_locked"
html << (change.value.trueish? ? added : removed)
html << "note-locked"
when "is_shown_in_index"
html << (change.value.trueish? ? added : removed)
html << "shown"
when "cached_tags"
previous = change.previous
changes = Post.tag_changes(change, previous, change.latest)
list = []
list << tag_list(changes[:added_tags], :obsolete => changes[:obsolete_added_tags], :prefix => "+", :class => "added")
list << tag_list(changes[:removed_tags], :obsolete => changes[:obsolete_removed_tags], :prefix => "-", :class => "removed")
if options[:show_all_tags]
list << tag_list(changes[:unchanged_tags], :prefix => "", :class => "unchanged")
end
html << list.join(" ")
html.strip!
end
when "pools"
case change.column_name
when "name"
if change.previous
html << "name changed from <span class='name-change'>%s</span> to <span class='name-change'>%s</span>" % [h(change.previous.value), h(change.value)]
else
html << "name: <span class='name-change'>%s</span>" % [h(change.value)]
end
when "description"
if change.value == ""
html << "description removed"
else
if !change.previous
html << "description: "
elsif change.previous.value == ""
html << "description added: "
else
html << "description changed: "
end
# Show a diff if there's a previous description and it's not blank. Otherwise,
# just show the new text.
show_diff = change.previous && change.previous.value != ""
if show_diff
text = Danbooru.diff(change.previous.value, change.value)
else
text = h(change.value)
end
# If there's only one line in the output, just show it inline. Otherwise, show it
# as a separate block.
multiple_lines = text.include?("<br>")
show_in_detail = options[:specific_history] || options[:specific_object]
if !multiple_lines
display = text
elsif show_diff
display = "<div class='diff text-block'>#{text}</div>"
else
display = "<div class='initial-diff text-block'>#{text}</div>"
end
if multiple_lines && !show_in_detail
html << "<a onclick='$(this).hide(); $(this).next().show()' href='#'>(show changes)</a><div style='display: none;'>#{display}</div>"
else
html << display
end
end
when "is_public"
html << (change.value.trueish? ? added : removed)
html << "public"
when "is_active"
html << (change.value.trueish? ? added : removed)
html << "active"
end
when "pools_posts"
# Sort the output by the post id.
sort_key = change.obj.post.id
case change.column_name
when "active"
html << (change.value.trueish? ? added : removed)
html << link_to("post #%i" % change.obj.post_id, :controller => "post", :action => "show", :id => change.obj.post_id)
when "sequence"
seq = "order:%i:%s" % [change.obj.post_id, change.value]
if change.previous
seq << %(←#{change.previous.value})
end
html << link_to("%s" % seq, :controller => "post", :action => "show", :id => change.obj.post_id)
end
when "tags"
case change.column_name
when "tag_type"
html << "type:"
tag_type = Tag.type_name_from_value(change.value.to_i)
html << %(<span class="tag-type-#{tag_type}">#{tag_type}</span>)
if change.previous
tag_type = Tag.type_name_from_value(change.previous.value.to_i)
html << %(←<span class="tag-type-#{tag_type}">#{tag_type}</span>)
end
when "is_ambiguous"
html << (change.value.trueish? ? added : removed)
html << "ambiguous"
end
when "notes"
case change.column_name
when "body"
if change.previous
html << "body changed from <span class='name-change'>%s</span> to <span class='name-change'>%s</span>" % [h(change.previous.value), h(change.value)]
else
html << "body: <span class='name-change'>%s</span>" % [h(change.value)]
end
when "x"
html << "x:#{h(change.value)}"
when "y"
html << "y:#{h(change.value)}"
when "height"
html << "height:#{h(change.value)}"
when "width"
html << "width:#{h(change.value)}"
when "is_active"
if change.value.trueish?
# Don't show the note initially being set to active.
return nil unless change.previous
html << "undeleted"
else
html << "deleted"
end
end
end
span = ""
span << %(<span class="#{classes.join(" ")}">#{html}</span>)
{
:html => span,
:field => change.column_name,
:sort_key => sort_key
}
end
def tag_list(tags, options = {})
return [] if tags.blank?
html = ""
html << %(<span class="#{options[:class]}">)
tags_html = []
tags.each do |name|
tags_html << tag_link(name, options)
end
return [] if tags_html.empty?
html << tags_html.join(" ")
html << %(</span>)
[html]
end
end