Files
danbooru/app/helpers/application_helper.rb
evazion ee638f976f Add /user_actions page.
Add a /user_actions page. This page shows you a global timeline of
(almost) all activity on the site, including uploads, comments, votes,
edits, forum posts, and so on.

The main things it doesn't include are post edits, pool edits, and
favorites (posts and pools live in a separate database, and favorites
don't have the timestamps we need for ordering).

This page is useful for moderation purposes because it lets you see a
history of almost all of a user's activity on a single page.

Currently this page is mod-only. In the future it will be open to all
users, so you can view the history of your own site activity, or the
activity of others.
2022-09-16 05:39:25 -05:00

445 lines
13 KiB
Ruby

# frozen_string_literal: true
module ApplicationHelper
USER_DATA_ATTRIBUTES = %i[
id name level level_string theme comment_threshold default_image_size time_zone per_page
] + User::ACTIVE_BOOLEAN_ATTRIBUTES + User::Roles.map { |role| :"is_#{role}?" }
COOKIE_DATA_ATTRIBUTES = %i[
news-ticker hide_upgrade_account_notice hide_verify_account_notice
hide_dmail_notice dab show-relationship-previews post_preview_size
post_preview_show_votes
]
def listing_type(*fields, member_check: true, types: [:revert, :standard])
(fields.reduce(false) { |acc, field| acc || params.dig(:search, field).present? } && (!member_check || CurrentUser.is_member?) ? types[0] : types[1])
end
def diff_list_html(this_list, other_list, ul_class: ["diff-list"], li_class: [], show_unchanged: true)
diff = SetDiff.new(this_list, other_list)
render "diff_list", diff: diff, ul_class: ul_class, li_class: li_class, show_unchanged: show_unchanged
end
def diff_name_html(this_name, other_name)
DiffBuilder.new(this_name, other_name, /./).build
end
def diff_body_html(record, other, field)
if record.blank? || other.blank?
diff_record = other.presence || record
return h(diff_record[field]).gsub(/\r?\n/, '<span class="paragraph-mark">¶</span><br>').html_safe
end
pattern = /(?:<.+?>)|(?:\w+)|(?:[ \t]+)|(?:\r?\n)|(?:.+?)/
DiffBuilder.new(record[field], other[field], pattern).build
end
def status_diff_html(record, type)
other = record.send(type)
if other.blank?
return type == "previous" ? "New" : ""
end
changed_fields = record.class.status_fields.select do |field, _status|
(record.has_attribute?(field) && record[field] != other[field]) ||
(!record.has_attribute?(field) && record.send(field, type))
end
statuses = changed_fields.map { |field, status| status }
altered = record.updater_id != other.updater_id
tag.div(class: "version-statuses", "data-altered": altered) do
safe_join(statuses, tag.br)
end
end
def wordbreakify(string)
lines = string.scan(/.{1,10}/)
wordbreaked_string = lines.map {|str| h(str)}.join("<wbr>")
raw(wordbreaked_string)
end
def version_type_links(params)
html = []
%w[previous subsequent current].each do |type|
if type == params[:type]
html << %{<span>#{type}</span>}
else
html << tag.li(link_to(type, params.except(:controller, :action).merge(type: type).permit!))
end
end
html.join(" | ").html_safe
end
def current_page_path(**params)
url_for(request.query_parameters.merge(params))
end
def nav_link_to(text, url, **options)
klass = options.delete(:class)
if nav_link_match(params[:controller], url)
klass = "#{klass} current"
end
li_link_to(text, url, id_prefix: "nav-", class: klass, **options)
end
def subnav_link_to(text, url, **options)
li_link_to(text, url, id_prefix: "subnav-", **options)
end
def li_link_to(text, url, id_prefix: "", **options)
klass = options.delete(:class)
id = id_prefix + text.downcase.gsub(/[^a-z ]/, "").parameterize
tag.li(link_to(text, url, id: "#{id}-link", **options), id: id, class: klass)
end
def format_text(text, **options)
raw DText.format_text(text, **options)
end
def strip_dtext(text)
DText.strip_dtext(text)
end
def time_tag(content, time, **options)
datetime = time.strftime("%Y-%m-%dT%H:%M%:z")
tag.time content || datetime, datetime: datetime, title: time.to_formatted_s, **options
end
def humanized_duration(duration)
if duration >= 100.years
"forever"
else
duration.inspect
end
end
def duration_to_hhmmss(seconds)
seconds = seconds.round
hh = seconds.div(1.hour).to_s
mm = seconds.div(1.minute).to_s
ss = "%.2d" % (seconds % 1.minute)
if seconds >= 1.hour
"#{hh}:#{mm}:#{ss}"
elsif seconds >= 1.second
"#{mm}:#{ss}"
else
"0:01"
end
end
def humanized_number(number, million: "M", thousand: "k")
if number >= 1_000_000
format("%.1f#{million}", number / 1_000_000.0)
elsif number >= 10_000
"#{number / 1_000}#{thousand}"
elsif number >= 1_000
format("%.1f#{thousand}", number / 1_000.0)
else
number.to_s
end
end
def time_ago_in_words_tagged(time, compact: false)
if time.nil?
tag.em(tag.time("unknown"))
elsif time.past?
if compact
text = time_ago_in_words(time)
text = text.gsub(/almost|about|over/, "").strip
text = text.gsub(/less than a/, "<1")
text = text.gsub(/ minutes?/, "m")
text = text.gsub(/ hours?/, "h")
text = text.gsub(/ days?/, "d")
text = text.gsub(/ months?/, "mo")
text = text.gsub(/ years?/, "y")
klass = "compact-timestamp"
else
text = time_ago_in_words(time) + " ago"
klass = ""
end
time_tag(text, time, class: klass)
elsif time.future?
if compact
text = distance_of_time_in_words(Time.now, time)
text = text.gsub(/almost|about|over/, "").gsub(/less than a/, "<1").strip
time_tag(text, time)
else
time_tag("in " + distance_of_time_in_words(Time.now, time), time)
end
end
end
def compact_time(time)
time_tag(time.strftime("%Y-%m-%d %H:%M"), time)
end
def external_link_to(url, text = url, truncate: nil, strip: false, **link_options)
text = text.gsub(%r{\Ahttps?://}i, "") if strip == :scheme
text = text.gsub(%r{\Ahttps?://(?:www\.)?}i, "") if strip == :subdomain
text = text.truncate(truncate) if truncate
if url =~ %r{\Ahttps?://}i
link_to text, url, rel: "external noreferrer nofollow", **link_options
else
url
end
end
def link_to_ip(ip, shorten: false, **options)
ip_addr = IPAddr.new(ip.to_s)
ip_addr.prefix = 64 if ip_addr.ipv6? && shorten
link_to ip_addr.to_s, ip_addresses_path(search: { ip_addr: ip, group_by: "user" }), **options
end
def link_to_search(tag, **options)
link_to tag.pretty_name, posts_path(tags: tag.name), class: tag_class(tag), **options
end
def link_to_wiki(text, title = text, **options)
title = "~#{title}" if title =~ /\A\d+\z/
link_to text, wiki_page_path(title), class: "wiki-link", **options
end
def link_to_wikis(*wiki_titles, **options)
links = wiki_titles.map do |title|
link_to_wiki title.tr("_", " "), title
end
to_sentence(links, **options)
end
def link_to_user(user, text = nil, classes: nil, **options)
return "anonymous" if user.blank?
user_class = "user user-#{user.level_string.downcase} #{classes}"
user_class += " user-post-approver" if user.can_approve_posts?
user_class += " user-post-uploader" if user.can_upload_free?
user_class += " user-banned" if user.is_banned?
text = user.pretty_name if text.blank?
data = { "user-id": user.id, "user-name": user.name, "user-level": user.level }
link_to(text, user, class: user_class, data: data)
end
def embed_wiki(title, **options)
wiki = WikiPage.find_by(title: title)
text = format_text(wiki&.body)
tag.div(text, class: "prose", **options)
end
def dtext_preview_button(preview_field)
tag.input value: "Preview", type: "button", class: "dtext-preview-button", "data-preview-field": preview_field
end
def quick_search_form_for(attribute, url, name, autocomplete: nil, redirect: false, &block)
tag.li do
search_form_for(url, classes: "quick-search-form one-line-form") do |f|
out = f.input attribute, label: false, placeholder: "Search #{name}", input_html: { id: nil, "data-autocomplete": autocomplete }
out += tag.input type: :hidden, name: :redirect, value: redirect
out += capture { yield f } if block_given?
out
end
end
end
def search_form_for(url, classes: "inline-form", method: :get, &block)
defaults = { required: false }
html_options = { autocomplete: "off", class: "search-form #{classes}" }
simple_form_for(:search, method: method, url: url, defaults: defaults, html: html_options) do |f|
out = "".html_safe
out += tag.input(type: :hidden, name: :limit, value: params[:limit]) if params[:limit].present?
out += capture { yield f } if block_given?
out
end
end
def edit_form_for(model, **options, &block)
options[:html] = { autocomplete: "off", **options[:html].to_h }
options[:authenticity_token] = true if options[:remote] == true
simple_form_for(model, **options) do |form|
if model.try(:errors).try(:any?)
concat tag.div(format_text(model.errors.full_messages.join("; ")), class: "notice notice-error notice-small prose")
end
if model.try(:warnings).try(:any?)
concat tag.div(format_text(model.warnings.full_messages.join("; ")), class: "notice notice-info notice-small prose")
end
block.call(form)
end
end
def table_for(...)
table = TableBuilder.new(...)
render "table_builder/table", table: table
end
def body_attributes(current_user, params, current_item, exception)
if exception
controller_param = "static"
action_param = "error"
layout = nil
extra_attributes = {}
else
controller_param = params[:controller].parameterize.dasherize
action_param = params[:action].parameterize.dasherize
layout = controller.class.send(:_layout)
extra_attributes = current_item_data_attributes(current_item)
end
{
lang: "en",
class: "c-#{controller_param} a-#{action_param} flex flex-col",
spellcheck: "false",
data: {
controller: controller_param,
action: action_param,
layout: layout,
"current-user-ip-addr": request.remote_ip,
"current-user-save-data": CurrentUser.save_data,
**data_attributes_for(current_user, "current-user", USER_DATA_ATTRIBUTES),
**data_attributes_for(cookies, "cookie", COOKIE_DATA_ATTRIBUTES),
**extra_attributes,
}
}
end
def current_item_data_attributes(current_item)
if current_item.present? && current_item.respond_to?(:html_data_attributes) && current_item.respond_to?(:model_name)
model_name = current_item.model_name.singular.dasherize
model_attributes = current_item.html_data_attributes
data_attributes_for(current_item, model_name, model_attributes)
else
{}
end
end
def data_attributes_for(record, prefix = "data", attributes = record.html_data_attributes)
attributes.map do |attr|
if attr.is_a?(Array)
name = attr.map {|sym| sym.to_s.dasherize.delete("?")}.join('-')
value = record
attr.each do |sym|
value = value.send(sym)
if value.nil?
break
end
end
elsif record.respond_to?(attr)
name = attr.to_s.dasherize.delete("?")
value = record.send(attr)
else
name = attr.to_s.dasherize.delete("?")
value = record[attr]
end
if value.nil?
value = "null"
end
if prefix.blank?
[:"#{name}", value]
else
[:"#{prefix}-#{name}", value]
end
end.to_h
end
def page_title(title = nil, suffix: "| #{Danbooru.config.app_name}")
if title.present?
content_for(:page_title) { "#{title} #{suffix}".strip }
elsif content_for(:page_title).present?
content_for(:page_title)
elsif params[:action] == "index"
"#{params[:controller].titleize} #{suffix}"
elsif params[:action] == "show"
"#{params[:controller].singularize.titleize} #{suffix}"
elsif params[:action] == "new"
"New #{params[:controller].singularize.titleize} #{suffix}"
elsif params[:action] == "edit"
"Edit #{params[:controller].singularize.titleize} #{suffix}"
elsif params[:action] == "search"
"Search #{params[:controller].titleize} #{suffix}"
else
"#{Danbooru.config.app_name}/#{params[:controller]}"
end
end
def meta_description(description = nil)
if description.present?
content_for(:meta_description) { description }
elsif content_for(:meta_description).present?
content_for(:meta_description)
end
end
def canonical_url(url = nil)
if url.present?
content_for(:canonical_url) { url }
elsif content_for(:canonical_url).present?
content_for(:canonical_url)
else
request_params = request.params.sort.to_h.with_indifferent_access
request_params.delete(:page) if request_params[:page].to_i == 1
request_params.delete(:limit)
url_for(**request_params, host: Danbooru.config.hostname, only_path: false)
end
end
def noindex
content_for(:html_header, tag.meta(name: "robots", content: "noindex"))
end
def atom_feed_tag(title, url = {})
content_for(:html_header, auto_discovery_link_tag(:atom, url, title: title))
end
protected
def nav_link_match(controller, url)
url =~ case controller
when "sessions", "users", "admin/users"
%r{^/(session|users)}
when "comments"
%r{^/comments}
when "notes", "note_versions"
%r{^/notes}
when "posts", "uploads", "post_versions", "explore/posts", "moderator/post/dashboards", "favorites"
%r{^/post}
when "artists", "artist_versions"
%r{^/artist}
when "tags", "tag_aliases", "tag_implications"
%r{^/tags}
when "pools", "pool_versions"
%r{^/pools}
when "moderator/dashboards"
%r{^/moderator}
when "wiki_pages", "wiki_page_versions"
%r{^/wiki_pages}
when "forum_topics", "forum_posts"
%r{^/forum_topics}
else
%r{^/static}
end
end
end