Gab Social. All are welcome.
This commit is contained in:
30
app/helpers/admin/account_moderation_notes_helper.rb
Normal file
30
app/helpers/admin/account_moderation_notes_helper.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin::AccountModerationNotesHelper
|
||||
def admin_account_link_to(account)
|
||||
return if account.nil?
|
||||
|
||||
link_to admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
||||
safe_join([
|
||||
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
|
||||
content_tag(:span, account.acct, class: 'username'),
|
||||
], ' ')
|
||||
end
|
||||
end
|
||||
|
||||
def admin_account_inline_link_to(account)
|
||||
return if account.nil?
|
||||
|
||||
link_to admin_account_path(account.id), class: name_tag_classes(account, true), title: account.acct do
|
||||
content_tag(:span, account.acct, class: 'username')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def name_tag_classes(account, inline = false)
|
||||
classes = [inline ? 'inline-name-tag' : 'name-tag']
|
||||
classes << 'suspended' if account.suspended? || (account.local? && account.user.nil?)
|
||||
classes.join(' ')
|
||||
end
|
||||
end
|
||||
114
app/helpers/admin/action_logs_helper.rb
Normal file
114
app/helpers/admin/action_logs_helper.rb
Normal file
@@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin::ActionLogsHelper
|
||||
def log_target(log)
|
||||
if log.target
|
||||
linkable_log_target(log.target)
|
||||
else
|
||||
log_target_from_history(log.target_type, log.recorded_changes)
|
||||
end
|
||||
end
|
||||
|
||||
def relevant_log_changes(log)
|
||||
if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)
|
||||
log.recorded_changes.slice('domain')
|
||||
elsif log.target_type == 'CustomEmoji' && log.action == :update
|
||||
log.recorded_changes.slice('domain', 'visible_in_picker')
|
||||
elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
|
||||
log.recorded_changes.slice('moderator', 'admin')
|
||||
elsif log.target_type == 'User' && [:change_email].include?(log.action)
|
||||
log.recorded_changes.slice('email', 'unconfirmed_email')
|
||||
elsif log.target_type == 'DomainBlock'
|
||||
log.recorded_changes.slice('severity', 'reject_media')
|
||||
elsif log.target_type == 'Status' && log.action == :update
|
||||
log.recorded_changes.slice('sensitive')
|
||||
end
|
||||
end
|
||||
|
||||
def log_extra_attributes(hash)
|
||||
safe_join(hash.to_a.map { |key, value| safe_join([content_tag(:span, key, class: 'diff-key'), '=', log_change(value)]) }, ' ')
|
||||
end
|
||||
|
||||
def log_change(val)
|
||||
return content_tag(:span, val, class: 'diff-neutral') unless val.is_a?(Array)
|
||||
safe_join([content_tag(:span, val.first, class: 'diff-old'), content_tag(:span, val.last, class: 'diff-new')], '→')
|
||||
end
|
||||
|
||||
def icon_for_log(log)
|
||||
case log.target_type
|
||||
when 'Account', 'User'
|
||||
'user'
|
||||
when 'CustomEmoji'
|
||||
'file'
|
||||
when 'Report'
|
||||
'flag'
|
||||
when 'DomainBlock'
|
||||
'lock'
|
||||
when 'EmailDomainBlock'
|
||||
'envelope'
|
||||
when 'Status'
|
||||
'pencil'
|
||||
when 'AccountWarning'
|
||||
'warning'
|
||||
end
|
||||
end
|
||||
|
||||
def class_for_log_icon(log)
|
||||
case log.action
|
||||
when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve
|
||||
'positive'
|
||||
when :create
|
||||
opposite_verbs?(log) ? 'negative' : 'positive'
|
||||
when :update, :reset_password, :disable_2fa, :memorialize, :change_email
|
||||
'neutral'
|
||||
when :demote, :silence, :disable, :suspend, :remove_avatar, :remove_header, :reopen
|
||||
'negative'
|
||||
when :destroy
|
||||
opposite_verbs?(log) ? 'positive' : 'negative'
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def opposite_verbs?(log)
|
||||
%w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type)
|
||||
end
|
||||
|
||||
def linkable_log_target(record)
|
||||
case record.class.name
|
||||
when 'Account'
|
||||
link_to record.acct, admin_account_path(record.id)
|
||||
when 'User'
|
||||
link_to record.account.acct, admin_account_path(record.account_id)
|
||||
when 'CustomEmoji'
|
||||
record.shortcode
|
||||
when 'Report'
|
||||
link_to "##{record.id}", admin_report_path(record)
|
||||
when 'DomainBlock', 'EmailDomainBlock'
|
||||
link_to record.domain, "https://#{record.domain}"
|
||||
when 'Status'
|
||||
link_to record.account.acct, TagManager.instance.url_for(record)
|
||||
when 'AccountWarning'
|
||||
link_to record.target_account.acct, admin_account_path(record.target_account_id)
|
||||
end
|
||||
end
|
||||
|
||||
def log_target_from_history(type, attributes)
|
||||
case type
|
||||
when 'CustomEmoji'
|
||||
attributes['shortcode']
|
||||
when 'DomainBlock', 'EmailDomainBlock'
|
||||
link_to attributes['domain'], "https://#{attributes['domain']}"
|
||||
when 'Status'
|
||||
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
|
||||
|
||||
if tmp_status.account
|
||||
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
|
||||
else
|
||||
I18n.t('admin.action_logs.deleted_status')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
10
app/helpers/admin/dashboard_helper.rb
Normal file
10
app/helpers/admin/dashboard_helper.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin::DashboardHelper
|
||||
def feature_hint(feature, enabled)
|
||||
indicator = safe_join([enabled ? t('simple_form.yes') : t('simple_form.no'), fa_icon('power-off fw')], ' ')
|
||||
class_names = enabled ? 'pull-right positive-hint' : 'pull-right neutral-hint'
|
||||
|
||||
safe_join([feature, content_tag(:span, indicator, class: class_names)])
|
||||
end
|
||||
end
|
||||
46
app/helpers/admin/filter_helper.rb
Normal file
46
app/helpers/admin/filter_helper.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin::FilterHelper
|
||||
ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze
|
||||
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
|
||||
INVITE_FILTER = %i(available expired).freeze
|
||||
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
||||
TAGS_FILTERS = %i(hidden).freeze
|
||||
INSTANCES_FILTERS = %i(limited by_domain).freeze
|
||||
FOLLOWERS_FILTERS = %i(relationship status by_domain activity order).freeze
|
||||
|
||||
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FOLLOWERS_FILTERS
|
||||
|
||||
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
||||
new_url = filtered_url_for(link_to_params)
|
||||
new_class = filtered_url_for(link_class_params)
|
||||
link_to text, new_url, class: filter_link_class(new_class)
|
||||
end
|
||||
|
||||
def table_link_to(icon, text, path, **options)
|
||||
link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link')
|
||||
end
|
||||
|
||||
def selected?(more_params)
|
||||
new_url = filtered_url_for(more_params)
|
||||
filter_link_class(new_url) == 'selected'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_params(more_params)
|
||||
controller_request_params.merge(more_params)
|
||||
end
|
||||
|
||||
def filter_link_class(new_url)
|
||||
filtered_url_for(controller_request_params) == new_url ? 'selected' : ''
|
||||
end
|
||||
|
||||
def filtered_url_for(url_params)
|
||||
url_for filter_params(url_params)
|
||||
end
|
||||
|
||||
def controller_request_params
|
||||
params.permit(FILTERS)
|
||||
end
|
||||
end
|
||||
125
app/helpers/application_helper.rb
Normal file
125
app/helpers/application_helper.rb
Normal file
@@ -0,0 +1,125 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationHelper
|
||||
DANGEROUS_SCOPES = %w(
|
||||
read
|
||||
write
|
||||
follow
|
||||
).freeze
|
||||
|
||||
def active_nav_class(*paths)
|
||||
paths.any? { |path| current_page?(path) } ? 'active' : ''
|
||||
end
|
||||
|
||||
def active_link_to(label, path, **options)
|
||||
link_to label, path, options.merge(class: active_nav_class(path))
|
||||
end
|
||||
|
||||
def show_landing_strip?
|
||||
!user_signed_in? && !single_user_mode?
|
||||
end
|
||||
|
||||
def open_registrations?
|
||||
Setting.registrations_mode == 'open'
|
||||
end
|
||||
|
||||
def approved_registrations?
|
||||
Setting.registrations_mode == 'approved'
|
||||
end
|
||||
|
||||
def closed_registrations?
|
||||
Setting.registrations_mode == 'none'
|
||||
end
|
||||
|
||||
def available_sign_up_path
|
||||
if closed_registrations?
|
||||
'https://gab.com/hosting#getting-started'
|
||||
else
|
||||
new_user_registration_path
|
||||
end
|
||||
end
|
||||
|
||||
def open_deletion?
|
||||
Setting.open_deletion
|
||||
end
|
||||
|
||||
def locale_direction
|
||||
if [:ar, :fa, :he].include?(I18n.locale)
|
||||
'rtl'
|
||||
else
|
||||
'ltr'
|
||||
end
|
||||
end
|
||||
|
||||
def favicon_path
|
||||
env_suffix = Rails.env.production? ? '' : '-dev'
|
||||
"/favicon#{env_suffix}.ico"
|
||||
end
|
||||
|
||||
def title
|
||||
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
||||
end
|
||||
|
||||
def class_for_scope(scope)
|
||||
'scope-danger' if DANGEROUS_SCOPES.include?(scope.to_s)
|
||||
end
|
||||
|
||||
def can?(action, record)
|
||||
return false if record.nil?
|
||||
policy(record).public_send("#{action}?")
|
||||
end
|
||||
|
||||
def fa_icon(icon, attributes = {})
|
||||
class_names = attributes[:class]&.split(' ') || []
|
||||
class_names << 'fa'
|
||||
class_names += icon.split(' ').map { |cl| "fa-#{cl}" }
|
||||
|
||||
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
||||
end
|
||||
|
||||
def custom_emoji_tag(custom_emoji)
|
||||
image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
|
||||
end
|
||||
|
||||
def opengraph(property, content)
|
||||
tag(:meta, content: content, property: property)
|
||||
end
|
||||
|
||||
def react_component(name, props = {}, &block)
|
||||
if block.nil?
|
||||
content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) })
|
||||
else
|
||||
content_tag(:div, data: { component: name.to_s.camelcase, props: Oj.dump(props) }, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def body_classes
|
||||
output = (@body_classes || '').split(' ')
|
||||
output << "theme-#{current_theme.parameterize}"
|
||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
|
||||
output << 'rtl' if locale_direction == 'rtl'
|
||||
output.reject(&:blank?).join(' ')
|
||||
end
|
||||
|
||||
def cdn_host
|
||||
Rails.configuration.action_controller.asset_host
|
||||
end
|
||||
|
||||
def cdn_host?
|
||||
cdn_host.present?
|
||||
end
|
||||
|
||||
def storage_host
|
||||
"https://#{ENV['S3_ALIAS_HOST'].presence || ENV['S3_CLOUDFRONT_HOST']}"
|
||||
end
|
||||
|
||||
def storage_host?
|
||||
ENV['S3_ALIAS_HOST'].present? || ENV['S3_CLOUDFRONT_HOST'].present?
|
||||
end
|
||||
|
||||
def quote_wrap(text, line_width: 80, break_sequence: "\n")
|
||||
text = word_wrap(text, line_width: line_width - 2, break_sequence: break_sequence)
|
||||
text.split("\n").map { |line| '> ' + line }.join("\n")
|
||||
end
|
||||
end
|
||||
7
app/helpers/flashes_helper.rb
Normal file
7
app/helpers/flashes_helper.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module FlashesHelper
|
||||
def user_facing_flashes
|
||||
flash.to_hash.slice('alert', 'error', 'notice', 'success')
|
||||
end
|
||||
end
|
||||
77
app/helpers/home_helper.rb
Normal file
77
app/helpers/home_helper.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module HomeHelper
|
||||
def default_props
|
||||
{
|
||||
locale: I18n.locale,
|
||||
}
|
||||
end
|
||||
|
||||
def account_link_to(account, button = '', size: 36, path: nil)
|
||||
content_tag(:div, class: 'account') do
|
||||
content_tag(:div, class: 'account__wrapper') do
|
||||
section = if account.nil?
|
||||
content_tag(:div, class: 'account__display-name') do
|
||||
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})")
|
||||
end +
|
||||
content_tag(:span, class: 'display-name') do
|
||||
content_tag(:strong, t('about.contact_missing')) +
|
||||
content_tag(:span, t('about.contact_unavailable'), class: 'display-name__account')
|
||||
end
|
||||
end
|
||||
else
|
||||
link_to(path || TagManager.instance.url_for(account), class: 'account__display-name') do
|
||||
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)})")
|
||||
end +
|
||||
content_tag(:span, class: 'display-name') do
|
||||
content_tag(:bdi) do
|
||||
content_tag(:strong, display_name(account, custom_emojify: true), class: 'display-name__html emojify')
|
||||
end +
|
||||
content_tag(:span, "@#{account.acct}", class: 'display-name__account')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
section + button
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def obscured_counter(count)
|
||||
if count <= 0
|
||||
0
|
||||
elsif count == 1
|
||||
1
|
||||
else
|
||||
'1+'
|
||||
end
|
||||
end
|
||||
|
||||
def custom_field_classes(field)
|
||||
if field.verified?
|
||||
'verified'
|
||||
else
|
||||
'emojify'
|
||||
end
|
||||
end
|
||||
|
||||
def optional_link_to(condition, path, options = {}, &block)
|
||||
if condition
|
||||
link_to(path, options, &block)
|
||||
else
|
||||
content_tag(:div, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def sign_up_message
|
||||
if closed_registrations?
|
||||
t('auth.registration_closed', instance: site_hostname)
|
||||
elsif open_registrations?
|
||||
t('auth.register')
|
||||
elsif approved_registrations?
|
||||
t('auth.apply_for_account')
|
||||
end
|
||||
end
|
||||
end
|
||||
11
app/helpers/instance_helper.rb
Normal file
11
app/helpers/instance_helper.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module InstanceHelper
|
||||
def site_title
|
||||
Setting.site_title
|
||||
end
|
||||
|
||||
def site_hostname
|
||||
@site_hostname ||= Addressable::URI.parse("//#{Rails.configuration.x.local_domain}").display_uri.host
|
||||
end
|
||||
end
|
||||
139
app/helpers/jsonld_helper.rb
Normal file
139
app/helpers/jsonld_helper.rb
Normal file
@@ -0,0 +1,139 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module JsonLdHelper
|
||||
def equals_or_includes?(haystack, needle)
|
||||
haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle
|
||||
end
|
||||
|
||||
def equals_or_includes_any?(haystack, needles)
|
||||
needles.any? { |needle| equals_or_includes?(haystack, needle) }
|
||||
end
|
||||
|
||||
def first_of_value(value)
|
||||
value.is_a?(Array) ? value.first : value
|
||||
end
|
||||
|
||||
# The url attribute can be a string, an array of strings, or an array of objects.
|
||||
# The objects could include a mimeType. Not-included mimeType means it's text/html.
|
||||
def url_to_href(value, preferred_type = nil)
|
||||
single_value = if value.is_a?(Array) && !value.first.is_a?(String)
|
||||
value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
|
||||
elsif value.is_a?(Array)
|
||||
value.first
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
if single_value.nil? || single_value.is_a?(String)
|
||||
single_value
|
||||
else
|
||||
single_value['href']
|
||||
end
|
||||
end
|
||||
|
||||
def as_array(value)
|
||||
value.is_a?(Array) ? value : [value]
|
||||
end
|
||||
|
||||
def value_or_id(value)
|
||||
value.is_a?(String) || value.nil? ? value : value['id']
|
||||
end
|
||||
|
||||
def supported_context?(json)
|
||||
!json.nil? && equals_or_includes?(json['@context'], ActivityPub::TagManager::CONTEXT)
|
||||
end
|
||||
|
||||
def unsupported_uri_scheme?(uri)
|
||||
!uri.start_with?('http://', 'https://')
|
||||
end
|
||||
|
||||
def invalid_origin?(url)
|
||||
return true if unsupported_uri_scheme?(url)
|
||||
|
||||
needle = Addressable::URI.parse(url).host
|
||||
haystack = Addressable::URI.parse(@account.uri).host
|
||||
|
||||
!haystack.casecmp(needle).zero?
|
||||
end
|
||||
|
||||
def canonicalize(json)
|
||||
graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
|
||||
graph.dump(:normalize)
|
||||
end
|
||||
|
||||
def fetch_resource(uri, id, on_behalf_of = nil)
|
||||
unless id
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||
return unless json
|
||||
uri = json['id']
|
||||
end
|
||||
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||
json.present? && json['id'] == uri ? json : nil
|
||||
end
|
||||
|
||||
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false)
|
||||
build_request(uri, on_behalf_of).perform do |response|
|
||||
unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
|
||||
raise GabSocial::UnexpectedResponseError, response
|
||||
end
|
||||
return body_to_json(response.body_with_limit) if response.code == 200
|
||||
end
|
||||
# If request failed, retry without doing it on behalf of a user
|
||||
return if on_behalf_of.nil?
|
||||
build_request(uri).perform do |response|
|
||||
unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
|
||||
raise GabSocial::UnexpectedResponseError, response
|
||||
end
|
||||
response.code == 200 ? body_to_json(response.body_with_limit) : nil
|
||||
end
|
||||
end
|
||||
|
||||
def body_to_json(body, compare_id: nil)
|
||||
json = body.is_a?(String) ? Oj.load(body, mode: :strict) : body
|
||||
return if compare_id.present? && json['id'] != compare_id
|
||||
json
|
||||
rescue Oj::ParseError
|
||||
nil
|
||||
end
|
||||
|
||||
def merge_context(context, new_context)
|
||||
if context.is_a?(Array)
|
||||
context << new_context
|
||||
else
|
||||
[context, new_context]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def response_successful?(response)
|
||||
(200...300).cover?(response.code)
|
||||
end
|
||||
|
||||
def response_error_unsalvageable?(response)
|
||||
(400...500).cover?(response.code) && response.code != 429
|
||||
end
|
||||
|
||||
def build_request(uri, on_behalf_of = nil)
|
||||
request = Request.new(:get, uri)
|
||||
request.on_behalf_of(on_behalf_of) if on_behalf_of
|
||||
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
|
||||
request
|
||||
end
|
||||
|
||||
def load_jsonld_context(url, _options = {}, &_block)
|
||||
json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do
|
||||
request = Request.new(:get, url)
|
||||
request.add_headers('Accept' => 'application/ld+json')
|
||||
|
||||
request.perform do |res|
|
||||
raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json'
|
||||
res.body_with_limit
|
||||
end
|
||||
end
|
||||
|
||||
doc = JSON::LD::API::RemoteDocument.new(url, json)
|
||||
block_given? ? yield(doc) : doc
|
||||
end
|
||||
end
|
||||
30
app/helpers/routing_helper.rb
Normal file
30
app/helpers/routing_helper.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RoutingHelper
|
||||
extend ActiveSupport::Concern
|
||||
include Rails.application.routes.url_helpers
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Webpacker::Helper
|
||||
|
||||
included do
|
||||
def default_url_options
|
||||
ActionMailer::Base.default_url_options
|
||||
end
|
||||
end
|
||||
|
||||
def full_asset_url(source, **options)
|
||||
source = ActionController::Base.helpers.asset_url(source, options) unless use_storage?
|
||||
|
||||
URI.join(root_url, source).to_s
|
||||
end
|
||||
|
||||
def full_pack_url(source, **options)
|
||||
full_asset_url(asset_pack_path(source, options))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def use_storage?
|
||||
Rails.configuration.x.use_s3 || Rails.configuration.x.use_swift
|
||||
end
|
||||
end
|
||||
89
app/helpers/settings_helper.rb
Normal file
89
app/helpers/settings_helper.rb
Normal file
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SettingsHelper
|
||||
HUMAN_LOCALES = {
|
||||
en: 'English',
|
||||
ar: 'العربية',
|
||||
ast: 'Asturianu',
|
||||
bg: 'Български',
|
||||
bn: 'বাংলা',
|
||||
ca: 'Català',
|
||||
co: 'Corsu',
|
||||
cs: 'Čeština',
|
||||
cy: 'Cymraeg',
|
||||
da: 'Dansk',
|
||||
de: 'Deutsch',
|
||||
el: 'Ελληνικά',
|
||||
eo: 'Esperanto',
|
||||
es: 'Español',
|
||||
eu: 'Euskara',
|
||||
fa: 'فارسی',
|
||||
fi: 'Suomi',
|
||||
fr: 'Français',
|
||||
ga: 'Gaeilge',
|
||||
gl: 'Galego',
|
||||
he: 'עברית',
|
||||
hi: 'हिन्दी',
|
||||
hr: 'Hrvatski',
|
||||
hu: 'Magyar',
|
||||
hy: 'Հայերեն',
|
||||
id: 'Bahasa Indonesia',
|
||||
io: 'Ido',
|
||||
it: 'Italiano',
|
||||
ja: '日本語',
|
||||
ka: 'ქართული',
|
||||
kk: 'Қазақша',
|
||||
ko: '한국어',
|
||||
lt: 'Lietuvių',
|
||||
lv: 'Latviešu',
|
||||
ml: 'മലയാളം',
|
||||
ms: 'Bahasa Melayu',
|
||||
nl: 'Nederlands',
|
||||
no: 'Norsk',
|
||||
oc: 'Occitan',
|
||||
pl: 'Polski',
|
||||
pt: 'Português',
|
||||
'pt-BR': 'Português do Brasil',
|
||||
ro: 'Română',
|
||||
ru: 'Русский',
|
||||
sk: 'Slovenčina',
|
||||
sl: 'Slovenščina',
|
||||
sq: 'Shqip',
|
||||
sr: 'Српски',
|
||||
'sr-Latn': 'Srpski (latinica)',
|
||||
sv: 'Svenska',
|
||||
ta: 'தமிழ்',
|
||||
te: 'తెలుగు',
|
||||
th: 'ไทย',
|
||||
tr: 'Türkçe',
|
||||
uk: 'Українська',
|
||||
zh: '中文',
|
||||
'zh-CN': '简体中文',
|
||||
'zh-HK': '繁體中文(香港)',
|
||||
'zh-TW': '繁體中文(臺灣)',
|
||||
}.freeze
|
||||
|
||||
def human_locale(locale)
|
||||
HUMAN_LOCALES[locale]
|
||||
end
|
||||
|
||||
def filterable_languages
|
||||
LanguageDetector.instance.language_names.select(&HUMAN_LOCALES.method(:key?))
|
||||
end
|
||||
|
||||
def hash_to_object(hash)
|
||||
HashObject.new(hash)
|
||||
end
|
||||
|
||||
def session_device_icon(session)
|
||||
device = session.detection.device
|
||||
|
||||
if device.mobile?
|
||||
'mobile'
|
||||
elsif device.tablet?
|
||||
'tablet'
|
||||
else
|
||||
'desktop'
|
||||
end
|
||||
end
|
||||
end
|
||||
216
app/helpers/stream_entries_helper.rb
Normal file
216
app/helpers/stream_entries_helper.rb
Normal file
@@ -0,0 +1,216 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module StreamEntriesHelper
|
||||
EMBEDDED_CONTROLLER = 'statuses'
|
||||
EMBEDDED_ACTION = 'embed'
|
||||
|
||||
def display_name(account, **options)
|
||||
if options[:custom_emojify]
|
||||
Formatter.instance.format_display_name(account, options)
|
||||
else
|
||||
account.display_name.presence || account.username
|
||||
end
|
||||
end
|
||||
|
||||
def account_action_button(account)
|
||||
if user_signed_in?
|
||||
if account.id == current_user.account_id
|
||||
link_to settings_profile_url, class: 'button logo-button' do
|
||||
safe_join([svg_logo, t('settings.edit_profile')])
|
||||
end
|
||||
elsif current_account.following?(account) || current_account.requested?(account)
|
||||
link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do
|
||||
safe_join([svg_logo, t('accounts.unfollow')])
|
||||
end
|
||||
elsif !(account.memorial? || account.moved?)
|
||||
link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
|
||||
safe_join([svg_logo, t('accounts.follow')])
|
||||
end
|
||||
end
|
||||
elsif !(account.memorial? || account.moved?)
|
||||
link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do
|
||||
safe_join([svg_logo, t('accounts.follow')])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def svg_logo
|
||||
content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976')
|
||||
end
|
||||
|
||||
def account_badge(account, all: false)
|
||||
if account.bot?
|
||||
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
|
||||
elsif (Setting.show_staff_badge && account.user_staff?) || all
|
||||
content_tag(:div, class: 'roles') do
|
||||
if all && !account.user_staff?
|
||||
content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role')
|
||||
elsif account.user_admin?
|
||||
content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin')
|
||||
elsif account.user_moderator?
|
||||
content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_more(url)
|
||||
link_to t('statuses.show_more'), url, class: 'load-more load-gap'
|
||||
end
|
||||
|
||||
def nothing_here(extra_classes = '')
|
||||
content_tag(:div, class: "nothing-here #{extra_classes}") do
|
||||
t('accounts.nothing_here')
|
||||
end
|
||||
end
|
||||
|
||||
def account_description(account)
|
||||
prepend_str = [
|
||||
[
|
||||
number_to_human(account.statuses_count, strip_insignificant_zeros: true),
|
||||
I18n.t('accounts.posts', count: account.statuses_count),
|
||||
].join(' '),
|
||||
|
||||
[
|
||||
number_to_human(account.following_count, strip_insignificant_zeros: true),
|
||||
I18n.t('accounts.following', count: account.following_count),
|
||||
].join(' '),
|
||||
|
||||
[
|
||||
number_to_human(account.followers_count, strip_insignificant_zeros: true),
|
||||
I18n.t('accounts.followers', count: account.followers_count),
|
||||
].join(' '),
|
||||
].join(', ')
|
||||
|
||||
[prepend_str, account.note].join(' · ')
|
||||
end
|
||||
|
||||
def media_summary(status)
|
||||
attachments = { image: 0, video: 0 }
|
||||
|
||||
status.media_attachments.each do |media|
|
||||
if media.video?
|
||||
attachments[:video] += 1
|
||||
else
|
||||
attachments[:image] += 1
|
||||
end
|
||||
end
|
||||
|
||||
text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| I18n.t("statuses.attached.#{key}", count: value) }.join(' · ')
|
||||
|
||||
return if text.blank?
|
||||
|
||||
I18n.t('statuses.attached.description', attached: text)
|
||||
end
|
||||
|
||||
def status_text_summary(status)
|
||||
return if status.spoiler_text.blank?
|
||||
I18n.t('statuses.content_warning', warning: status.spoiler_text)
|
||||
end
|
||||
|
||||
def poll_summary(status)
|
||||
return unless status.preloadable_poll
|
||||
status.preloadable_poll.options.map { |o| "[ ] #{o}" }.join("\n")
|
||||
end
|
||||
|
||||
def status_description(status)
|
||||
components = [[media_summary(status), status_text_summary(status)].reject(&:blank?).join(' · ')]
|
||||
|
||||
if status.spoiler_text.blank?
|
||||
components << status.text
|
||||
components << poll_summary(status)
|
||||
end
|
||||
|
||||
components.reject(&:blank?).join("\n\n")
|
||||
end
|
||||
|
||||
def stream_link_target
|
||||
embedded_view? ? '_blank' : nil
|
||||
end
|
||||
|
||||
def acct(account)
|
||||
if account.local?
|
||||
"@#{account.acct}@#{Rails.configuration.x.local_domain}"
|
||||
else
|
||||
"@#{account.acct}"
|
||||
end
|
||||
end
|
||||
|
||||
def style_classes(status, is_predecessor, is_successor, include_threads)
|
||||
classes = ['entry']
|
||||
classes << 'entry-predecessor' if is_predecessor
|
||||
classes << 'entry-reblog' if status.reblog?
|
||||
classes << 'entry-successor' if is_successor
|
||||
classes << 'entry-center' if include_threads
|
||||
classes.join(' ')
|
||||
end
|
||||
|
||||
def microformats_classes(status, is_direct_parent, is_direct_child)
|
||||
classes = []
|
||||
classes << 'p-in-reply-to' if is_direct_parent
|
||||
classes << 'p-repost-of' if status.reblog? && is_direct_parent
|
||||
classes << 'p-comment' if is_direct_child
|
||||
classes.join(' ')
|
||||
end
|
||||
|
||||
def microformats_h_class(status, is_predecessor, is_successor, include_threads)
|
||||
if is_predecessor || status.reblog? || is_successor
|
||||
'h-cite'
|
||||
elsif include_threads
|
||||
''
|
||||
else
|
||||
'h-entry'
|
||||
end
|
||||
end
|
||||
|
||||
def rtl_status?(status)
|
||||
status.local? ? rtl?(status.text) : rtl?(strip_tags(status.text))
|
||||
end
|
||||
|
||||
def rtl?(text)
|
||||
text = simplified_text(text)
|
||||
rtl_words = text.scan(/[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}]+/m)
|
||||
|
||||
if rtl_words.present?
|
||||
total_size = text.size.to_f
|
||||
rtl_size(rtl_words) / total_size > 0.3
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def fa_visibility_icon(status)
|
||||
case status.visibility
|
||||
when 'public'
|
||||
fa_icon 'globe fw'
|
||||
when 'unlisted'
|
||||
fa_icon 'unlock fw'
|
||||
when 'private'
|
||||
fa_icon 'lock fw'
|
||||
when 'direct'
|
||||
fa_icon 'envelope fw'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def simplified_text(text)
|
||||
text.dup.tap do |new_text|
|
||||
URI.extract(new_text).each do |url|
|
||||
new_text.gsub!(url, '')
|
||||
end
|
||||
|
||||
new_text.gsub!(Account::MENTION_RE, '')
|
||||
new_text.gsub!(Tag::HASHTAG_RE, '')
|
||||
new_text.gsub!(/\s+/, '')
|
||||
end
|
||||
end
|
||||
|
||||
def rtl_size(words)
|
||||
words.reduce(0) { |acc, elem| acc + elem.size }.to_f
|
||||
end
|
||||
|
||||
def embedded_view?
|
||||
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user