Remove Atom feeds and old URLs in the form of GET /:username/updates/:id
(#11247)
This commit is contained in:
parent
406b46395d
commit
b851456139
|
@ -31,13 +31,6 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
format.atom do
|
||||
mark_cacheable!
|
||||
|
||||
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
|
||||
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
|
||||
end
|
||||
|
||||
format.rss do
|
||||
mark_cacheable!
|
||||
|
||||
|
|
|
@ -33,12 +33,10 @@ class StatusesController < ApplicationController
|
|||
|
||||
set_ancestors
|
||||
set_descendants
|
||||
|
||||
render 'stream_entries/show'
|
||||
end
|
||||
|
||||
format.json do
|
||||
render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||
render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: @status.distributable?) do
|
||||
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
end
|
||||
|
@ -46,7 +44,7 @@ class StatusesController < ApplicationController
|
|||
end
|
||||
|
||||
def activity
|
||||
render_cached_json(['activitypub', 'activity', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||
render_cached_json(['activitypub', 'activity', @status], content_type: 'application/activity+json', public: @status.distributable?) do
|
||||
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
end
|
||||
|
@ -58,7 +56,7 @@ class StatusesController < ApplicationController
|
|||
response.headers['X-Frame-Options'] = 'ALLOWALL'
|
||||
@autoplay = ActiveModel::Type::Boolean.new.cast(params[:autoplay])
|
||||
|
||||
render 'stream_entries/embed', layout: 'embedded'
|
||||
render layout: 'embedded'
|
||||
end
|
||||
|
||||
def replies
|
||||
|
@ -92,11 +90,20 @@ class StatusesController < ApplicationController
|
|||
|
||||
def create_descendant_thread(starting_depth, statuses)
|
||||
depth = starting_depth + statuses.size
|
||||
|
||||
if depth < DESCENDANTS_DEPTH_LIMIT
|
||||
{ statuses: statuses, starting_depth: starting_depth }
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
}
|
||||
else
|
||||
next_status = statuses.pop
|
||||
{ statuses: statuses, starting_depth: starting_depth, next_status: next_status }
|
||||
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
next_status: next_status,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -164,22 +171,13 @@ class StatusesController < ApplicationController
|
|||
end
|
||||
|
||||
def set_link_headers
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[
|
||||
[account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]],
|
||||
[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]],
|
||||
]
|
||||
)
|
||||
response.headers['Link'] = LinkHeader.new([[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]]])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:id])
|
||||
@stream_entry = @status.stream_entry
|
||||
@type = @stream_entry.activity_type.downcase
|
||||
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
|
@ -192,7 +190,7 @@ class StatusesController < ApplicationController
|
|||
end
|
||||
|
||||
def redirect_to_original
|
||||
redirect_to ::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||
redirect_to ActivityPub::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StreamEntriesController < ApplicationController
|
||||
include Authorization
|
||||
include SignatureVerification
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :set_account
|
||||
before_action :set_stream_entry
|
||||
before_action :set_link_headers
|
||||
before_action :check_account_suspension
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 5.minutes, public: true unless @stream_entry.hidden?
|
||||
|
||||
redirect_to short_account_status_url(params[:account_username], @stream_entry.activity)
|
||||
end
|
||||
|
||||
format.atom do
|
||||
expires_in 3.minutes, public: true unless @stream_entry.hidden?
|
||||
|
||||
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def embed
|
||||
redirect_to embed_short_account_status_url(@account, @stream_entry.activity), status: 301
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def set_link_headers
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[
|
||||
[account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]],
|
||||
[ActivityPub::TagManager.instance.uri_for(@stream_entry.activity), [%w(rel alternate), %w(type application/activity+json)]],
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def set_stream_entry
|
||||
@stream_entry = @account.stream_entries.where(activity_type: 'Status').find(params[:id])
|
||||
@type = 'status'
|
||||
|
||||
raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil?
|
||||
authorize @stream_entry.activity, :show? if @stream_entry.hidden?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
gone if @account.suspended?
|
||||
end
|
||||
end
|
|
@ -89,7 +89,7 @@ module Admin::ActionLogsHelper
|
|||
when 'DomainBlock', 'EmailDomainBlock'
|
||||
link_to record.domain, "https://#{record.domain}"
|
||||
when 'Status'
|
||||
link_to record.account.acct, TagManager.instance.url_for(record)
|
||||
link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
|
||||
when 'AccountWarning'
|
||||
link_to record.target_account.acct, admin_account_path(record.target_account_id)
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ module HomeHelper
|
|||
end
|
||||
end
|
||||
else
|
||||
link_to(path || TagManager.instance.url_for(account), class: 'account__display-name') do
|
||||
link_to(path || ActivityPub::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 +
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module StreamEntriesHelper
|
||||
module StatusesHelper
|
||||
EMBEDDED_CONTROLLER = 'statuses'
|
||||
EMBEDDED_ACTION = 'embed'
|
||||
|
||||
|
@ -109,11 +109,13 @@ module StreamEntriesHelper
|
|||
|
||||
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
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
@import 'mastodon/widgets';
|
||||
@import 'mastodon/forms';
|
||||
@import 'mastodon/accounts';
|
||||
@import 'mastodon/stream_entries';
|
||||
@import 'mastodon/statuses';
|
||||
@import 'mastodon/boost';
|
||||
@import 'mastodon/components';
|
||||
@import 'mastodon/polls';
|
||||
|
|
|
@ -295,6 +295,6 @@ class Formatter
|
|||
end
|
||||
|
||||
def mention_html(account)
|
||||
"<span class=\"h-card\"><a href=\"#{encode(TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
|
||||
"<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,376 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class OStatus::AtomSerializer
|
||||
include RoutingHelper
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
class << self
|
||||
def render(element)
|
||||
document = Ox::Document.new(version: '1.0')
|
||||
document << element
|
||||
('<?xml version="1.0"?>' + Ox.dump(element, effort: :tolerant)).force_encoding('UTF-8')
|
||||
end
|
||||
end
|
||||
|
||||
def author(account)
|
||||
author = Ox::Element.new('author')
|
||||
|
||||
uri = OStatus::TagManager.instance.uri_for(account)
|
||||
|
||||
append_element(author, 'id', uri)
|
||||
append_element(author, 'activity:object-type', OStatus::TagManager::TYPES[:person])
|
||||
append_element(author, 'uri', uri)
|
||||
append_element(author, 'name', account.username)
|
||||
append_element(author, 'email', account.local? ? account.local_username_and_domain : account.acct)
|
||||
append_element(author, 'summary', Formatter.instance.simplified_format(account).to_str, type: :html) if account.note?
|
||||
append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(account))
|
||||
append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original))) if account.avatar?
|
||||
append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original))) if account.header?
|
||||
account.emojis.each do |emoji|
|
||||
append_element(author, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
|
||||
end
|
||||
append_element(author, 'poco:preferredUsername', account.username)
|
||||
append_element(author, 'poco:displayName', account.display_name) if account.display_name?
|
||||
append_element(author, 'poco:note', account.local? ? account.note : strip_tags(account.note)) if account.note?
|
||||
append_element(author, 'mastodon:scope', account.locked? ? :private : :public)
|
||||
|
||||
author
|
||||
end
|
||||
|
||||
def feed(account, stream_entries)
|
||||
feed = Ox::Element.new('feed')
|
||||
|
||||
add_namespaces(feed)
|
||||
|
||||
append_element(feed, 'id', account_url(account, format: 'atom'))
|
||||
append_element(feed, 'title', account.display_name.presence || account.username)
|
||||
append_element(feed, 'subtitle', account.note)
|
||||
append_element(feed, 'updated', account.updated_at.iso8601)
|
||||
append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
|
||||
|
||||
feed << author(account)
|
||||
|
||||
append_element(feed, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(account))
|
||||
append_element(feed, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_url(account, format: 'atom'))
|
||||
append_element(feed, 'link', nil, rel: :next, type: 'application/atom+xml', href: account_url(account, format: 'atom', max_id: stream_entries.last.id)) if stream_entries.size == 20
|
||||
|
||||
stream_entries.each do |stream_entry|
|
||||
feed << entry(stream_entry)
|
||||
end
|
||||
|
||||
feed
|
||||
end
|
||||
|
||||
def entry(stream_entry, root = false)
|
||||
entry = Ox::Element.new('entry')
|
||||
|
||||
add_namespaces(entry) if root
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.uri_for(stream_entry.status))
|
||||
append_element(entry, 'published', stream_entry.created_at.iso8601)
|
||||
append_element(entry, 'updated', stream_entry.updated_at.iso8601)
|
||||
append_element(entry, 'title', stream_entry&.status&.title || "#{stream_entry.account.acct} deleted status")
|
||||
|
||||
entry << author(stream_entry.account) if root
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[stream_entry.object_type])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[stream_entry.verb])
|
||||
|
||||
entry << object(stream_entry.target) if stream_entry.targeted?
|
||||
|
||||
if stream_entry.status.nil?
|
||||
append_element(entry, 'content', 'Deleted status')
|
||||
elsif stream_entry.status.destroyed?
|
||||
append_element(entry, 'content', 'Deleted status')
|
||||
append_element(entry, 'link', nil, rel: :alternate, type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(stream_entry.status)) if stream_entry.account.local?
|
||||
else
|
||||
serialize_status_attributes(entry, stream_entry.status)
|
||||
end
|
||||
|
||||
append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(stream_entry.status))
|
||||
append_element(entry, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom'))
|
||||
append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(stream_entry.thread), href: ::TagManager.instance.url_for(stream_entry.thread)) if stream_entry.threaded?
|
||||
append_element(entry, 'ostatus:conversation', nil, ref: conversation_uri(stream_entry.status.conversation)) unless stream_entry&.status&.conversation_id.nil?
|
||||
|
||||
entry
|
||||
end
|
||||
|
||||
def object(status)
|
||||
object = Ox::Element.new('activity:object')
|
||||
|
||||
append_element(object, 'id', OStatus::TagManager.instance.uri_for(status))
|
||||
append_element(object, 'published', status.created_at.iso8601)
|
||||
append_element(object, 'updated', status.updated_at.iso8601)
|
||||
append_element(object, 'title', status.title)
|
||||
|
||||
object << author(status.account)
|
||||
|
||||
append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[status.object_type])
|
||||
append_element(object, 'activity:verb', OStatus::TagManager::VERBS[status.verb])
|
||||
|
||||
serialize_status_attributes(object, status)
|
||||
|
||||
append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(status))
|
||||
append_element(object, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(status.thread), href: ::TagManager.instance.url_for(status.thread)) unless status.thread.nil?
|
||||
append_element(object, 'ostatus:conversation', nil, ref: conversation_uri(status.conversation)) unless status.conversation_id.nil?
|
||||
|
||||
object
|
||||
end
|
||||
|
||||
def follow_salmon(follow)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
description = "#{follow.account.acct} started following #{follow.target_account.acct}"
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(follow.created_at, follow.id, 'Follow'))
|
||||
append_element(entry, 'title', description)
|
||||
append_element(entry, 'content', description, type: :html)
|
||||
|
||||
entry << author(follow.account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:follow])
|
||||
|
||||
object = author(follow.target_account)
|
||||
object.value = 'activity:object'
|
||||
|
||||
entry << object
|
||||
entry
|
||||
end
|
||||
|
||||
def follow_request_salmon(follow_request)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(follow_request.created_at, follow_request.id, 'FollowRequest'))
|
||||
append_element(entry, 'title', "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}")
|
||||
|
||||
entry << author(follow_request.account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:request_friend])
|
||||
|
||||
object = author(follow_request.target_account)
|
||||
object.value = 'activity:object'
|
||||
|
||||
entry << object
|
||||
entry
|
||||
end
|
||||
|
||||
def authorize_follow_request_salmon(follow_request)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
|
||||
append_element(entry, 'title', "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}")
|
||||
|
||||
entry << author(follow_request.target_account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:authorize])
|
||||
|
||||
object = Ox::Element.new('activity:object')
|
||||
object << author(follow_request.account)
|
||||
|
||||
append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(object, 'activity:verb', OStatus::TagManager::VERBS[:request_friend])
|
||||
|
||||
inner_object = author(follow_request.target_account)
|
||||
inner_object.value = 'activity:object'
|
||||
|
||||
object << inner_object
|
||||
entry << object
|
||||
entry
|
||||
end
|
||||
|
||||
def reject_follow_request_salmon(follow_request)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
|
||||
append_element(entry, 'title', "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}")
|
||||
|
||||
entry << author(follow_request.target_account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:reject])
|
||||
|
||||
object = Ox::Element.new('activity:object')
|
||||
object << author(follow_request.account)
|
||||
|
||||
append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(object, 'activity:verb', OStatus::TagManager::VERBS[:request_friend])
|
||||
|
||||
inner_object = author(follow_request.target_account)
|
||||
inner_object.value = 'activity:object'
|
||||
|
||||
object << inner_object
|
||||
entry << object
|
||||
entry
|
||||
end
|
||||
|
||||
def unfollow_salmon(follow)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, follow.id, 'Follow'))
|
||||
append_element(entry, 'title', description)
|
||||
append_element(entry, 'content', description, type: :html)
|
||||
|
||||
entry << author(follow.account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unfollow])
|
||||
|
||||
object = author(follow.target_account)
|
||||
object.value = 'activity:object'
|
||||
|
||||
entry << object
|
||||
entry
|
||||
end
|
||||
|
||||
def block_salmon(block)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
description = "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}"
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
|
||||
append_element(entry, 'title', description)
|
||||
|
||||
entry << author(block.account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:block])
|
||||
|
||||
object = author(block.target_account)
|
||||
object.value = 'activity:object'
|
||||
|
||||
entry << object
|
||||
entry
|
||||
end
|
||||
|
||||
def unblock_salmon(block)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
description = "#{block.account.acct} no longer blocks #{block.target_account.acct}"
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
|
||||
append_element(entry, 'title', description)
|
||||
|
||||
entry << author(block.account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unblock])
|
||||
|
||||
object = author(block.target_account)
|
||||
object.value = 'activity:object'
|
||||
|
||||
entry << object
|
||||
entry
|
||||
end
|
||||
|
||||
def favourite_salmon(favourite)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(favourite.created_at, favourite.id, 'Favourite'))
|
||||
append_element(entry, 'title', description)
|
||||
append_element(entry, 'content', description, type: :html)
|
||||
|
||||
entry << author(favourite.account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:favorite])
|
||||
|
||||
entry << object(favourite.status)
|
||||
|
||||
append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(favourite.status), href: ::TagManager.instance.url_for(favourite.status))
|
||||
|
||||
entry
|
||||
end
|
||||
|
||||
def unfavourite_salmon(favourite)
|
||||
entry = Ox::Element.new('entry')
|
||||
add_namespaces(entry)
|
||||
|
||||
description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
|
||||
|
||||
append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, favourite.id, 'Favourite'))
|
||||
append_element(entry, 'title', description)
|
||||
append_element(entry, 'content', description, type: :html)
|
||||
|
||||
entry << author(favourite.account)
|
||||
|
||||
append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
|
||||
append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unfavorite])
|
||||
|
||||
entry << object(favourite.status)
|
||||
|
||||
append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(favourite.status), href: ::TagManager.instance.url_for(favourite.status))
|
||||
|
||||
entry
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def append_element(parent, name, content = nil, **attributes)
|
||||
element = Ox::Element.new(name)
|
||||
attributes.each { |k, v| element[k] = sanitize_str(v) }
|
||||
element << sanitize_str(content) unless content.nil?
|
||||
parent << element
|
||||
end
|
||||
|
||||
def sanitize_str(raw_str)
|
||||
raw_str.to_s
|
||||
end
|
||||
|
||||
def conversation_uri(conversation)
|
||||
return conversation.uri if conversation.uri?
|
||||
OStatus::TagManager.instance.unique_tag(conversation.created_at, conversation.id, 'Conversation')
|
||||
end
|
||||
|
||||
def add_namespaces(parent)
|
||||
parent['xmlns'] = OStatus::TagManager::XMLNS
|
||||
parent['xmlns:thr'] = OStatus::TagManager::THR_XMLNS
|
||||
parent['xmlns:activity'] = OStatus::TagManager::AS_XMLNS
|
||||
parent['xmlns:poco'] = OStatus::TagManager::POCO_XMLNS
|
||||
parent['xmlns:media'] = OStatus::TagManager::MEDIA_XMLNS
|
||||
parent['xmlns:ostatus'] = OStatus::TagManager::OS_XMLNS
|
||||
parent['xmlns:mastodon'] = OStatus::TagManager::MTDN_XMLNS
|
||||
end
|
||||
|
||||
def serialize_status_attributes(entry, status)
|
||||
append_element(entry, 'link', nil, rel: :alternate, type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(status)) if status.account.local?
|
||||
|
||||
append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text?
|
||||
append_element(entry, 'content', Formatter.instance.format(status, inline_poll_options: true).to_str || '.', type: 'html', 'xml:lang': status.language)
|
||||
|
||||
status.active_mentions.sort_by(&:id).each do |mentioned|
|
||||
append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::TagManager.instance.uri_for(mentioned.account))
|
||||
end
|
||||
|
||||
append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:collection], href: OStatus::TagManager::COLLECTIONS[:public]) if status.public_visibility?
|
||||
|
||||
status.tags.each do |tag|
|
||||
append_element(entry, 'category', nil, term: tag.name)
|
||||
end
|
||||
|
||||
status.media_attachments.each do |media|
|
||||
append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false)))
|
||||
end
|
||||
|
||||
append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive? && status.media_attachments.any?
|
||||
append_element(entry, 'mastodon:scope', status.visibility)
|
||||
|
||||
status.emojis.each do |emoji|
|
||||
append_element(entry, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,8 +13,6 @@ class StatusFinder
|
|||
raise ActiveRecord::RecordNotFound unless TagManager.instance.local_url?(url)
|
||||
|
||||
case recognized_params[:controller]
|
||||
when 'stream_entries'
|
||||
StreamEntry.find(recognized_params[:id]).status
|
||||
when 'statuses'
|
||||
Status.find(recognized_params[:id])
|
||||
else
|
||||
|
|
|
@ -33,15 +33,4 @@ class TagManager
|
|||
domain = uri.host + (uri.port ? ":#{uri.port}" : '')
|
||||
TagManager.instance.web_domain?(domain)
|
||||
end
|
||||
|
||||
def url_for(target)
|
||||
return target.url if target.respond_to?(:local?) && !target.local?
|
||||
|
||||
case target.object_type
|
||||
when :person
|
||||
short_account_url(target)
|
||||
when :note, :comment, :activity
|
||||
short_account_status_url(target.account, target)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class AdminMailer < ApplicationMailer
|
||||
layout 'plain_mailer'
|
||||
|
||||
helper :stream_entries
|
||||
helper :statuses
|
||||
|
||||
def new_report(recipient, report)
|
||||
@report = report
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class NotificationMailer < ApplicationMailer
|
||||
helper :stream_entries
|
||||
helper :statuses
|
||||
|
||||
add_template_helper RoutingHelper
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ module AccountAssociations
|
|||
has_many :identity_proofs, class_name: 'AccountIdentityProof', dependent: :destroy, inverse_of: :account
|
||||
|
||||
# Timelines
|
||||
has_many :stream_entries, inverse_of: :account, dependent: :destroy
|
||||
has_many :statuses, inverse_of: :account, dependent: :destroy
|
||||
has_many :favourites, inverse_of: :account, dependent: :destroy
|
||||
has_many :mentions, inverse_of: :account, dependent: :destroy
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Streamable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one :stream_entry, as: :activity
|
||||
|
||||
after_create do
|
||||
account.stream_entries.create!(activity: self, hidden: hidden?) if needs_stream_entry?
|
||||
end
|
||||
end
|
||||
|
||||
def title
|
||||
super
|
||||
end
|
||||
|
||||
def content
|
||||
title
|
||||
end
|
||||
|
||||
def target
|
||||
super
|
||||
end
|
||||
|
||||
def object_type
|
||||
:activity
|
||||
end
|
||||
|
||||
def thread
|
||||
super
|
||||
end
|
||||
|
||||
def hidden?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def needs_stream_entry?
|
||||
account.local?
|
||||
end
|
||||
end
|
|
@ -1,57 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoteProfile
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_reader :document
|
||||
|
||||
def initialize(body)
|
||||
@document = Nokogiri::XML.parse(body, nil, 'utf-8')
|
||||
end
|
||||
|
||||
def root
|
||||
@root ||= document.at_xpath('/atom:feed|/atom:entry', atom: OStatus::TagManager::XMLNS)
|
||||
end
|
||||
|
||||
def author
|
||||
@author ||= root.at_xpath('./atom:author|./dfrn:owner', atom: OStatus::TagManager::XMLNS, dfrn: OStatus::TagManager::DFRN_XMLNS)
|
||||
end
|
||||
|
||||
def hub_link
|
||||
@hub_link ||= link_href_from_xml(root, 'hub')
|
||||
end
|
||||
|
||||
def display_name
|
||||
@display_name ||= author.at_xpath('./poco:displayName', poco: OStatus::TagManager::POCO_XMLNS)&.content
|
||||
end
|
||||
|
||||
def note
|
||||
@note ||= author.at_xpath('./atom:summary|./poco:note', atom: OStatus::TagManager::XMLNS, poco: OStatus::TagManager::POCO_XMLNS)&.content
|
||||
end
|
||||
|
||||
def scope
|
||||
@scope ||= author.at_xpath('./mastodon:scope', mastodon: OStatus::TagManager::MTDN_XMLNS)&.content
|
||||
end
|
||||
|
||||
def avatar
|
||||
@avatar ||= link_href_from_xml(author, 'avatar')
|
||||
end
|
||||
|
||||
def header
|
||||
@header ||= link_href_from_xml(author, 'header')
|
||||
end
|
||||
|
||||
def emojis
|
||||
@emojis ||= author.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS)
|
||||
end
|
||||
|
||||
def locked?
|
||||
scope == 'private'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def link_href_from_xml(xml, type)
|
||||
xml.at_xpath(%(./atom:link[@rel="#{type}"]/@href), atom: OStatus::TagManager::XMLNS)&.content
|
||||
end
|
||||
end
|
|
@ -28,7 +28,6 @@ class Status < ApplicationRecord
|
|||
before_destroy :unlink_from_conversations
|
||||
|
||||
include Paginable
|
||||
include Streamable
|
||||
include Cacheable
|
||||
include StatusThreadingConcern
|
||||
|
||||
|
@ -61,7 +60,6 @@ class Status < ApplicationRecord
|
|||
has_and_belongs_to_many :preview_cards
|
||||
|
||||
has_one :notification, as: :activity, dependent: :destroy
|
||||
has_one :stream_entry, as: :activity, inverse_of: :status
|
||||
has_one :status_stat, inverse_of: :status
|
||||
has_one :poll, inverse_of: :status, dependent: :destroy
|
||||
|
||||
|
@ -106,13 +104,11 @@ class Status < ApplicationRecord
|
|||
:status_stat,
|
||||
:tags,
|
||||
:preview_cards,
|
||||
:stream_entry,
|
||||
:preloadable_poll,
|
||||
account: :account_stat,
|
||||
active_mentions: { account: :account_stat },
|
||||
reblog: [
|
||||
:application,
|
||||
:stream_entry,
|
||||
:tags,
|
||||
:preview_cards,
|
||||
:media_attachments,
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: stream_entries
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# activity_id :bigint(8)
|
||||
# activity_type :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# hidden :boolean default(FALSE), not null
|
||||
# account_id :bigint(8)
|
||||
#
|
||||
|
||||
class StreamEntry < ApplicationRecord
|
||||
include Paginable
|
||||
|
||||
belongs_to :account, inverse_of: :stream_entries
|
||||
belongs_to :activity, polymorphic: true
|
||||
belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id', inverse_of: :stream_entry
|
||||
|
||||
validates :account, :activity, presence: true
|
||||
|
||||
STATUS_INCLUDES = [:account, :stream_entry, :conversation, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :conversation, :media_attachments, :tags, mentions: :account], thread: [:stream_entry, :account]].freeze
|
||||
|
||||
default_scope { where(activity_type: 'Status') }
|
||||
scope :recent, -> { reorder(id: :desc) }
|
||||
scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) }
|
||||
|
||||
delegate :target, :title, :content, :thread,
|
||||
to: :status,
|
||||
allow_nil: true
|
||||
|
||||
def object_type
|
||||
orphaned? || targeted? ? :activity : status.object_type
|
||||
end
|
||||
|
||||
def verb
|
||||
orphaned? ? :delete : status.verb
|
||||
end
|
||||
|
||||
def targeted?
|
||||
[:follow, :request_friend, :authorize, :reject, :unfollow, :block, :unblock, :share, :favorite].include? verb
|
||||
end
|
||||
|
||||
def threaded?
|
||||
(verb == :favorite || object_type == :comment) && !thread.nil?
|
||||
end
|
||||
|
||||
def mentions
|
||||
orphaned? ? [] : status.active_mentions.map(&:account)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def orphaned?
|
||||
status.nil?
|
||||
end
|
||||
end
|
|
@ -29,7 +29,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def url
|
||||
TagManager.instance.url_for(object)
|
||||
ActivityPub::TagManager.instance.url_for(object)
|
||||
end
|
||||
|
||||
def avatar
|
||||
|
|
|
@ -58,7 +58,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def uri
|
||||
OStatus::TagManager.instance.uri_for(object)
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
end
|
||||
|
||||
def content
|
||||
|
@ -66,7 +66,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def url
|
||||
TagManager.instance.url_for(object)
|
||||
ActivityPub::TagManager.instance.url_for(object)
|
||||
end
|
||||
|
||||
def favourited
|
||||
|
@ -132,7 +132,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def url
|
||||
TagManager.instance.url_for(object.account)
|
||||
ActivityPub::TagManager.instance.url_for(object.account)
|
||||
end
|
||||
|
||||
def acct
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class RSS::AccountSerializer
|
||||
include ActionView::Helpers::NumberHelper
|
||||
include StreamEntriesHelper
|
||||
include StatusesHelper
|
||||
include RoutingHelper
|
||||
|
||||
def render(account, statuses)
|
||||
|
@ -10,7 +10,7 @@ class RSS::AccountSerializer
|
|||
|
||||
builder.title("#{display_name(account)} (@#{account.local_username_and_domain})")
|
||||
.description(account_description(account))
|
||||
.link(TagManager.instance.url_for(account))
|
||||
.link(ActivityPub::TagManager.instance.url_for(account))
|
||||
.logo(full_pack_url('media/images/logo.svg'))
|
||||
.accent_color('2b90d9')
|
||||
|
||||
|
@ -20,7 +20,7 @@ class RSS::AccountSerializer
|
|||
statuses.each do |status|
|
||||
builder.item do |item|
|
||||
item.title(status.title)
|
||||
.link(TagManager.instance.url_for(status))
|
||||
.link(ActivityPub::TagManager.instance.url_for(status))
|
||||
.pub_date(status.created_at)
|
||||
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class RSS::TagSerializer
|
||||
include ActionView::Helpers::NumberHelper
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
include StreamEntriesHelper
|
||||
include StatusesHelper
|
||||
include RoutingHelper
|
||||
|
||||
def render(tag, statuses)
|
||||
|
@ -18,7 +18,7 @@ class RSS::TagSerializer
|
|||
statuses.each do |status|
|
||||
builder.item do |item|
|
||||
item.title(status.title)
|
||||
.link(TagManager.instance.url_for(status))
|
||||
.link(ActivityPub::TagManager.instance.url_for(status))
|
||||
.pub_date(status.created_at)
|
||||
.description(status.spoiler_text.presence || Formatter.instance.format(status).to_str)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class BatchedRemoveStatusService < BaseService
|
|||
# @param [Hash] options
|
||||
# @option [Boolean] :skip_side_effects
|
||||
def call(statuses, **options)
|
||||
statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a }
|
||||
statuses = Status.where(id: statuses.map(&:id)).includes(:account).flat_map { |status| [status] + status.reblogs.includes(:account).to_a }
|
||||
|
||||
@mentions = statuses.each_with_object({}) { |s, h| h[s.id] = s.active_mentions.includes(:account).to_a }
|
||||
@tags = statuses.each_with_object({}) { |s, h| h[s.id] = s.tags.pluck(:name) }
|
||||
|
|
|
@ -84,7 +84,7 @@ class FetchLinkCardService < BaseService
|
|||
|
||||
def mention_link?(a)
|
||||
@status.mentions.any? do |mention|
|
||||
a['href'] == TagManager.instance.url_for(mention.account)
|
||||
a['href'] == ActivityPub::TagManager.instance.url_for(mention.account)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class ProcessMentionsService < BaseService
|
|||
private
|
||||
|
||||
def mention_undeliverable?(mentioned_account)
|
||||
mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus? && @status.stream_entry.hidden?)
|
||||
mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus?)
|
||||
end
|
||||
|
||||
def create_notification(mention)
|
||||
|
|
|
@ -11,7 +11,6 @@ class RemoveStatusService < BaseService
|
|||
@tags = status.tags.pluck(:name).to_a
|
||||
@mentions = status.active_mentions.includes(:account).to_a
|
||||
@reblogs = status.reblogs.includes(:account).to_a
|
||||
@stream_entry = status.stream_entry
|
||||
@options = options
|
||||
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
|
|
|
@ -73,10 +73,7 @@ class ResolveURLService < BaseService
|
|||
|
||||
return unless recognized_params[:action] == 'show'
|
||||
|
||||
if recognized_params[:controller] == 'stream_entries'
|
||||
status = StreamEntry.find_by(id: recognized_params[:id])&.status
|
||||
check_local_status(status)
|
||||
elsif recognized_params[:controller] == 'statuses'
|
||||
if recognized_params[:controller] == 'statuses'
|
||||
status = Status.find_by(id: recognized_params[:id])
|
||||
check_local_status(status)
|
||||
elsif recognized_params[:controller] == 'accounts'
|
||||
|
|
|
@ -24,7 +24,6 @@ class SuspendAccountService < BaseService
|
|||
report_notes
|
||||
scheduled_statuses
|
||||
status_pins
|
||||
stream_entries
|
||||
subscriptions
|
||||
).freeze
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
.moved-account-widget
|
||||
.moved-account-widget__message
|
||||
= fa_icon 'suitcase'
|
||||
= t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), TagManager.instance.url_for(moved_to_account), class: 'mention'))
|
||||
= t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
|
||||
|
||||
.moved-account-widget__card
|
||||
= link_to TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener' do
|
||||
= link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener' do
|
||||
.detailed-status__display-avatar
|
||||
.account__avatar-overlay
|
||||
.account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" }
|
||||
|
|
|
@ -39,12 +39,12 @@
|
|||
- else
|
||||
.activity-stream.activity-stream--under-tabs
|
||||
- if params[:page].to_i.zero?
|
||||
= render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
|
||||
= render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
|
||||
|
||||
- if @newer_url
|
||||
.entry= link_to_more @newer_url
|
||||
|
||||
= render partial: 'stream_entries/status', collection: @statuses, as: :status
|
||||
= render partial: 'statuses/status', collection: @statuses, as: :status
|
||||
|
||||
- if @older_url
|
||||
.entry= link_to_more @older_url
|
||||
|
|
|
@ -19,4 +19,4 @@
|
|||
= table_link_to 'times', t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:reject, account.user)
|
||||
- else
|
||||
= table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
|
||||
= table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account)
|
||||
= table_link_to 'globe', t('admin.accounts.public'), ActivityPub::TagManager.instance.url_for(account)
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
||||
|
||||
.detailed-status__meta
|
||||
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
|
||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||
·
|
||||
- if status.reblog?
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account)
|
||||
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account)
|
||||
|
||||
.card.h-card
|
||||
= link_to account_url, target: '_blank', rel: 'noopener' do
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.post-follow-actions
|
||||
%div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block'
|
||||
%div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@resource), class: 'button button--block'
|
||||
%div= link_to t('authorize_follow.post_follow.return'), ActivityPub::TagManager.instance.url_for(@resource), class: 'button button--block'
|
||||
%div= t('authorize_follow.post_follow.close')
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
.public-layout
|
||||
.activity-stream.activity-stream--highlighted
|
||||
= render 'stream_entries/status', status: @status
|
||||
= render 'statuses/status', status: @status
|
||||
|
||||
= simple_form_for @remote_follow, as: :remote_follow, url: remote_interaction_path(@status) do |f|
|
||||
= render 'shared/error_messages', object: @remote_follow
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
= image_tag account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar'
|
||||
|
||||
%span.display-name
|
||||
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account)
|
||||
- account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account)
|
||||
= link_to account_url, class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do
|
||||
%strong.emojify= display_name(account, custom_emojify: true)
|
||||
%span @#{account.acct}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.post-follow-actions
|
||||
%div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block'
|
||||
%div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block'
|
||||
%div= link_to t('authorize_follow.post_follow.return'), ActivityPub::TagManager.instance.url_for(@account), class: 'button button--block'
|
||||
%div= t('authorize_follow.post_follow.close')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.detailed-status.detailed-status--flex
|
||||
.p-author.h-card
|
||||
= link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
|
||||
.detailed-status__display-avatar
|
||||
- if current_account&.user&.setting_auto_play_gif || autoplay
|
||||
= image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
|
||||
|
@ -24,23 +24,23 @@
|
|||
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
|
||||
- if status.preloadable_poll
|
||||
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
|
||||
= render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
|
||||
= render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
|
||||
|
||||
- if !status.media_attachments.empty?
|
||||
- if status.media_attachments.first.audio_or_video?
|
||||
- video = status.media_attachments.first
|
||||
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
|
||||
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
||||
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
|
||||
- else
|
||||
= react_component :media_gallery, height: 380, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
|
||||
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
||||
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
|
||||
- elsif status.preview_card
|
||||
= react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
|
||||
|
||||
.detailed-status__meta
|
||||
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
||||
|
||||
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
|
||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||
·
|
||||
- if status.application && @account.user&.setting_show_application
|
|
@ -1,11 +1,11 @@
|
|||
.status
|
||||
.status__info
|
||||
= link_to TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener' do
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener' do
|
||||
%time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
||||
|
||||
.p-author.h-card
|
||||
= link_to TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener' do
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener' do
|
||||
.status__avatar
|
||||
%div
|
||||
- if current_account&.user&.setting_auto_play_gif || autoplay
|
||||
|
@ -28,16 +28,16 @@
|
|||
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
|
||||
- if status.preloadable_poll
|
||||
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
|
||||
= render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
|
||||
= render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
|
||||
|
||||
- if !status.media_attachments.empty?
|
||||
- if status.media_attachments.first.audio_or_video?
|
||||
- video = status.media_attachments.first
|
||||
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
|
||||
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
||||
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
|
||||
- else
|
||||
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
|
||||
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
||||
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
|
||||
- elsif status.preview_card
|
||||
= react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
|
||||
|
|
@ -17,9 +17,9 @@
|
|||
- if status.reply? && include_threads
|
||||
- if @next_ancestor
|
||||
.entry{ class: entry_classes }
|
||||
= link_to_more TagManager.instance.url_for(@next_ancestor)
|
||||
= link_to_more ActivityPub::TagManager.instance.url_for(@next_ancestor)
|
||||
|
||||
= render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id }, autoplay: autoplay
|
||||
= render partial: 'statuses/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id }, autoplay: autoplay
|
||||
|
||||
.entry{ class: entry_classes }
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
|||
.status__prepend-icon-wrapper
|
||||
%i.status__prepend-icon.fa.fa-fw.fa-retweet
|
||||
%span
|
||||
= link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
|
||||
%bdi
|
||||
%strong.emojify= display_name(status.account, custom_emojify: true)
|
||||
= t('stream_entries.reblogged')
|
||||
|
@ -39,18 +39,18 @@
|
|||
%span
|
||||
= t('stream_entries.pinned')
|
||||
|
||||
= render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper, autoplay: autoplay
|
||||
= render (centered ? 'statuses/detailed_status' : 'statuses/simple_status'), status: status.proper, autoplay: autoplay
|
||||
|
||||
- if include_threads
|
||||
- if @since_descendant_thread_id
|
||||
.entry{ class: entry_classes }
|
||||
= link_to_more short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1)
|
||||
- @descendant_threads.each do |thread|
|
||||
= render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id }, autoplay: autoplay
|
||||
= render partial: 'statuses/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id }, autoplay: autoplay
|
||||
|
||||
- if thread[:next_status]
|
||||
.entry{ class: entry_classes }
|
||||
= link_to_more TagManager.instance.url_for(thread[:next_status])
|
||||
= link_to_more ActivityPub::TagManager.instance.url_for(thread[:next_status])
|
||||
- if @next_descendant_thread
|
||||
.entry{ class: entry_classes }
|
||||
= link_to_more short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1)
|
3
app/views/statuses/embed.html.haml
Normal file
3
app/views/statuses/embed.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
- cache @status do
|
||||
.activity-stream.activity-stream--headless
|
||||
= render 'status', status: @status, centered: true, autoplay: @autoplay
|
24
app/views/statuses/show.html.haml
Normal file
24
app/views/statuses/show.html.haml
Normal file
|
@ -0,0 +1,24 @@
|
|||
- content_for :page_title do
|
||||
= t('statuses.title', name: display_name(@account), quote: truncate(@status.spoiler_text.presence || @status.text, length: 50, omission: '…', escape: false))
|
||||
|
||||
- content_for :header_tags do
|
||||
- if @account.user&.setting_noindex
|
||||
%meta{ name: 'robots', content: 'noindex' }/
|
||||
|
||||
%link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: short_account_status_url(@account, @status), format: 'json') }/
|
||||
%link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@status) }/
|
||||
|
||||
= opengraph 'og:site_name', site_title
|
||||
= opengraph 'og:type', 'article'
|
||||
= opengraph 'og:title', "#{display_name(@account)} (@#{@account.local_username_and_domain})"
|
||||
= opengraph 'og:url', short_account_status_url(@account, @status)
|
||||
|
||||
= render 'og_description', activity: @status
|
||||
= render 'og_image', activity: @status, account: @account
|
||||
|
||||
.grid
|
||||
.column-0
|
||||
.activity-stream.h-entry
|
||||
= render partial: 'status', locals: { status: @status, include_threads: true }
|
||||
.column-1
|
||||
= render 'application/sidebar'
|
|
@ -1,3 +0,0 @@
|
|||
- cache @stream_entry.activity do
|
||||
.activity-stream.activity-stream--headless
|
||||
= render "stream_entries/#{@type}", @type.to_sym => @stream_entry.activity, centered: true, autoplay: @autoplay
|
|
@ -1,25 +0,0 @@
|
|||
- content_for :page_title do
|
||||
= t('statuses.title', name: display_name(@account), quote: truncate(@stream_entry.activity.spoiler_text.presence || @stream_entry.activity.text, length: 50, omission: '…', escape: false))
|
||||
|
||||
- content_for :header_tags do
|
||||
- if @account.user&.setting_noindex
|
||||
%meta{ name: 'robots', content: 'noindex' }/
|
||||
|
||||
%link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/
|
||||
%link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: account_stream_entry_url(@account, @stream_entry), format: 'json') }/
|
||||
%link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@stream_entry.activity) }/
|
||||
|
||||
= opengraph 'og:site_name', site_title
|
||||
= opengraph 'og:type', 'article'
|
||||
= opengraph 'og:title', "#{display_name(@account)} (@#{@account.local_username_and_domain})"
|
||||
= opengraph 'og:url', short_account_status_url(@account, @stream_entry.activity)
|
||||
|
||||
= render 'stream_entries/og_description', activity: @stream_entry.activity
|
||||
= render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
|
||||
|
||||
.grid
|
||||
.column-0
|
||||
.activity-stream.h-entry
|
||||
= render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }
|
||||
.column-1
|
||||
= render 'application/sidebar'
|
|
@ -45,12 +45,6 @@ Rails.application.routes.draw do
|
|||
get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
|
||||
|
||||
resources :accounts, path: 'users', only: [:show], param: :username do
|
||||
resources :stream_entries, path: 'updates', only: [:show] do
|
||||
member do
|
||||
get :embed
|
||||
end
|
||||
end
|
||||
|
||||
get :remote_follow, to: 'remote_follow#new'
|
||||
post :remote_follow, to: 'remote_follow#create'
|
||||
|
||||
|
|
13
db/post_migrate/20190706233204_drop_stream_entries.rb
Normal file
13
db/post_migrate/20190706233204_drop_stream_entries.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropStreamEntries < ActiveRecord::Migration[5.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
drop_table :stream_entries
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_06_27_222826) do
|
||||
ActiveRecord::Schema.define(version: 2019_07_06_233204) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -647,17 +647,6 @@ ActiveRecord::Schema.define(version: 2019_06_27_222826) do
|
|||
t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
|
||||
end
|
||||
|
||||
create_table "stream_entries", force: :cascade do |t|
|
||||
t.bigint "activity_id"
|
||||
t.string "activity_type"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "hidden", default: false, null: false
|
||||
t.bigint "account_id"
|
||||
t.index ["account_id", "activity_type", "id"], name: "index_stream_entries_on_account_id_and_activity_type_and_id"
|
||||
t.index ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type"
|
||||
end
|
||||
|
||||
create_table "subscriptions", force: :cascade do |t|
|
||||
t.string "callback_url", default: "", null: false
|
||||
t.string "secret"
|
||||
|
@ -831,7 +820,6 @@ ActiveRecord::Schema.define(version: 2019_06_27_222826) do
|
|||
add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade
|
||||
add_foreign_key "statuses_tags", "statuses", on_delete: :cascade
|
||||
add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade
|
||||
add_foreign_key "stream_entries", "accounts", name: "fk_5659b17554", on_delete: :cascade
|
||||
add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade
|
||||
add_foreign_key "tombstones", "accounts", on_delete: :cascade
|
||||
add_foreign_key "user_invite_requests", "users", on_delete: :cascade
|
||||
|
|
|
@ -48,37 +48,6 @@ RSpec.describe AccountsController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
context 'atom' do
|
||||
let(:format) { 'atom' }
|
||||
let(:content_type) { 'application/atom+xml' }
|
||||
|
||||
shared_examples 'responsed streams' do
|
||||
it 'assigns @entries' do
|
||||
entries = assigns(:entries).to_a
|
||||
expect(entries.size).to eq expected_statuses.size
|
||||
entries.each.zip(expected_statuses.each) do |entry, expected_status|
|
||||
expect(entry.status).to eq expected_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'responses'
|
||||
|
||||
context 'without max_id nor since_id' do
|
||||
let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
|
||||
|
||||
include_examples 'responsed streams'
|
||||
end
|
||||
|
||||
context 'with max_id and since_id' do
|
||||
let(:max_id) { status4.stream_entry.id }
|
||||
let(:since_id) { status1.stream_entry.id }
|
||||
let(:expected_statuses) { [status3, status2] }
|
||||
|
||||
include_examples 'responsed streams'
|
||||
end
|
||||
end
|
||||
|
||||
context 'activitystreams2' do
|
||||
let(:format) { 'json' }
|
||||
let(:content_type) { 'application/activity+json' }
|
||||
|
|
|
@ -9,7 +9,7 @@ RSpec.describe Api::OEmbedController, type: :controller do
|
|||
describe 'GET #show' do
|
||||
before do
|
||||
request.host = Rails.configuration.x.local_domain
|
||||
get :show, params: { url: account_stream_entry_url(alice, status.stream_entry) }, format: :json
|
||||
get :show, params: { url: short_account_status_url(alice, status) }, format: :json
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
|
|
|
@ -360,9 +360,5 @@ describe ApplicationController, type: :controller do
|
|||
context 'Status' do
|
||||
include_examples 'cacheable', :status, Status
|
||||
end
|
||||
|
||||
context 'StreamEntry' do
|
||||
include_examples 'receives :with_includes', :stream_entry, StreamEntry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,18 +55,6 @@ describe StatusesController do
|
|||
expect(assigns(:status)).to eq status
|
||||
end
|
||||
|
||||
it 'assigns @stream_entry' do
|
||||
status = Fabricate(:status)
|
||||
get :show, params: { account_username: status.account.username, id: status.id }
|
||||
expect(assigns(:stream_entry)).to eq status.stream_entry
|
||||
end
|
||||
|
||||
it 'assigns @type' do
|
||||
status = Fabricate(:status)
|
||||
get :show, params: { account_username: status.account.username, id: status.id }
|
||||
expect(assigns(:type)).to eq 'status'
|
||||
end
|
||||
|
||||
it 'assigns @ancestors for ancestors of the status if it is a reply' do
|
||||
ancestor = Fabricate(:status)
|
||||
status = Fabricate(:status, in_reply_to_id: ancestor.id)
|
||||
|
@ -135,10 +123,10 @@ describe StatusesController do
|
|||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'renders stream_entries/show' do
|
||||
it 'renders statuses/show' do
|
||||
status = Fabricate(:status)
|
||||
get :show, params: { account_username: status.account.username, id: status.id }
|
||||
expect(response).to render_template 'stream_entries/show'
|
||||
expect(response).to render_template 'statuses/show'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe StreamEntriesController, type: :controller do
|
||||
render_views
|
||||
|
||||
shared_examples 'before_action' do |route|
|
||||
context 'when account is not suspended and stream_entry is available' do
|
||||
it 'assigns instance variables' do
|
||||
status = Fabricate(:status)
|
||||
|
||||
get route, params: { account_username: status.account.username, id: status.stream_entry.id }
|
||||
|
||||
expect(assigns(:account)).to eq status.account
|
||||
expect(assigns(:stream_entry)).to eq status.stream_entry
|
||||
expect(assigns(:type)).to eq 'status'
|
||||
end
|
||||
|
||||
it 'sets Link headers' do
|
||||
alice = Fabricate(:account, username: 'alice')
|
||||
status = Fabricate(:status, account: alice)
|
||||
|
||||
get route, params: { account_username: alice.username, id: status.stream_entry.id }
|
||||
|
||||
expect(response.headers['Link'].to_s).to eq "<http://test.host/users/alice/updates/#{status.stream_entry.id}.atom>; rel=\"alternate\"; type=\"application/atom+xml\", <https://cb6e6126.ngrok.io/users/alice/statuses/#{status.id}>; rel=\"alternate\"; type=\"application/activity+json\""
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is suspended' do
|
||||
it 'returns http status 410' do
|
||||
account = Fabricate(:account, suspended: true)
|
||||
status = Fabricate(:status, account: account)
|
||||
|
||||
get route, params: { account_username: account.username, id: status.stream_entry.id }
|
||||
|
||||
expect(response).to have_http_status(410)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when activity is nil' do
|
||||
it 'raises ActiveRecord::RecordNotFound' do
|
||||
account = Fabricate(:account)
|
||||
stream_entry = Fabricate.build(:stream_entry, account: account, activity: nil, activity_type: 'Status')
|
||||
stream_entry.save!(validate: false)
|
||||
|
||||
get route, params: { account_username: account.username, id: stream_entry.id }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is hidden and it is not permitted' do
|
||||
it 'raises ActiveRecord::RecordNotFound' do
|
||||
status = Fabricate(:status)
|
||||
user = Fabricate(:user)
|
||||
status.account.block!(user.account)
|
||||
status.stream_entry.update!(hidden: true)
|
||||
|
||||
sign_in(user)
|
||||
get route, params: { account_username: status.account.username, id: status.stream_entry.id }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
include_examples 'before_action', :show
|
||||
|
||||
it 'redirects to status page' do
|
||||
status = Fabricate(:status)
|
||||
|
||||
get :show, params: { account_username: status.account.username, id: status.stream_entry.id }
|
||||
|
||||
expect(response).to redirect_to(short_account_status_url(status.account, status))
|
||||
end
|
||||
|
||||
it 'returns http success with Atom' do
|
||||
status = Fabricate(:status)
|
||||
get :show, params: { account_username: status.account.username, id: status.stream_entry.id }, format: 'atom'
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #embed' do
|
||||
include_examples 'before_action', :embed
|
||||
|
||||
it 'redirects to new embed page' do
|
||||
status = Fabricate(:status)
|
||||
|
||||
get :embed, params: { account_username: status.account.username, id: status.stream_entry.id }
|
||||
|
||||
expect(response).to redirect_to(embed_short_account_status_url(status.account, status))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
Fabricator(:stream_entry) do
|
||||
account
|
||||
activity { Fabricate(:status) }
|
||||
hidden { [true, false].sample }
|
||||
end
|
|
@ -3,7 +3,7 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do
|
||||
include StreamEntriesHelper
|
||||
include StatusesHelper
|
||||
|
||||
describe '#admin_account_link_to' do
|
||||
context 'account is nil' do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe StreamEntriesHelper, type: :helper do
|
||||
RSpec.describe StatusesHelper, type: :helper do
|
||||
describe '#display_name' do
|
||||
it 'uses the display name when it exists' do
|
||||
account = Account.new(display_name: "Display", username: "Username")
|
||||
|
@ -70,13 +70,13 @@ RSpec.describe StreamEntriesHelper, type: :helper do
|
|||
end
|
||||
|
||||
def set_not_embedded_view
|
||||
params[:controller] = "not_#{StreamEntriesHelper::EMBEDDED_CONTROLLER}"
|
||||
params[:action] = "not_#{StreamEntriesHelper::EMBEDDED_ACTION}"
|
||||
params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}"
|
||||
params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}"
|
||||
end
|
||||
|
||||
def set_embedded_view
|
||||
params[:controller] = StreamEntriesHelper::EMBEDDED_CONTROLLER
|
||||
params[:action] = StreamEntriesHelper::EMBEDDED_ACTION
|
||||
params[:controller] = StatusesHelper::EMBEDDED_CONTROLLER
|
||||
params[:action] = StatusesHelper::EMBEDDED_ACTION
|
||||
end
|
||||
|
||||
describe '#style_classes' do
|
|
@ -143,12 +143,6 @@ RSpec.describe ActivityPub::TagManager do
|
|||
expect(subject.uri_to_resource(OStatus::TagManager.instance.uri_for(status), Status)).to eq status
|
||||
end
|
||||
|
||||
it 'returns the local status for OStatus StreamEntry URL' do
|
||||
status = Fabricate(:status)
|
||||
stream_entry_url = account_stream_entry_url(status.account, status.stream_entry)
|
||||
expect(subject.uri_to_resource(stream_entry_url, Status)).to eq status
|
||||
end
|
||||
|
||||
it 'returns the remote status by matching URI without fragment part' do
|
||||
status = Fabricate(:status, uri: 'https://example.com/123')
|
||||
expect(subject.uri_to_resource('https://example.com/123#456', Status)).to eq status
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,15 +25,6 @@ describe StatusFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a stream entry url' do
|
||||
let(:stream_entry) { Fabricate(:stream_entry) }
|
||||
let(:url) { account_stream_entry_url(stream_entry.account, stream_entry) }
|
||||
|
||||
it 'finds the stream entry' do
|
||||
expect(subject.status).to eq(stream_entry.status)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a remote url even if id exists on local' do
|
||||
let(:status) { Fabricate(:status) }
|
||||
let(:url) { "https://example.com/users/test/statuses/#{status.id}" }
|
||||
|
|
|
@ -119,46 +119,4 @@ RSpec.describe TagManager do
|
|||
expect(TagManager.instance.same_acct?('username', 'incorrect@Cb6E6126.nGrOk.Io')).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#url_for' do
|
||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
|
||||
subject { TagManager.instance.url_for(target) }
|
||||
|
||||
context 'activity object' do
|
||||
let(:target) { Fabricate(:status, account: alice, reblog: Fabricate(:status)).stream_entry }
|
||||
|
||||
it 'returns the unique tag for status' do
|
||||
expect(target.object_type).to eq :activity
|
||||
is_expected.to eq "https://cb6e6126.ngrok.io/@alice/#{target.id}"
|
||||
end
|
||||
end
|
||||
|
||||
context 'comment object' do
|
||||
let(:target) { Fabricate(:status, account: alice, reply: true) }
|
||||
|
||||
it 'returns the unique tag for status' do
|
||||
expect(target.object_type).to eq :comment
|
||||
is_expected.to eq "https://cb6e6126.ngrok.io/@alice/#{target.id}"
|
||||
end
|
||||
end
|
||||
|
||||
context 'note object' do
|
||||
let(:target) { Fabricate(:status, account: alice, reply: false, thread: nil) }
|
||||
|
||||
it 'returns the unique tag for status' do
|
||||
expect(target.object_type).to eq :note
|
||||
is_expected.to eq "https://cb6e6126.ngrok.io/@alice/#{target.id}"
|
||||
end
|
||||
end
|
||||
|
||||
context 'person object' do
|
||||
let(:target) { alice }
|
||||
|
||||
it 'returns the URL for account' do
|
||||
expect(target.object_type).to eq :person
|
||||
is_expected.to eq 'https://cb6e6126.ngrok.io/@alice'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Streamable do
|
||||
class Parent
|
||||
def title; end
|
||||
|
||||
def target; end
|
||||
|
||||
def thread; end
|
||||
|
||||
def self.has_one(*); end
|
||||
|
||||
def self.after_create; end
|
||||
end
|
||||
|
||||
class Child < Parent
|
||||
include Streamable
|
||||
end
|
||||
|
||||
child = Child.new
|
||||
|
||||
describe '#title' do
|
||||
it 'calls Parent#title' do
|
||||
expect_any_instance_of(Parent).to receive(:title)
|
||||
child.title
|
||||
end
|
||||
end
|
||||
|
||||
describe '#content' do
|
||||
it 'calls #title' do
|
||||
expect_any_instance_of(Parent).to receive(:title)
|
||||
child.content
|
||||
end
|
||||
end
|
||||
|
||||
describe '#target' do
|
||||
it 'calls Parent#target' do
|
||||
expect_any_instance_of(Parent).to receive(:target)
|
||||
child.target
|
||||
end
|
||||
end
|
||||
|
||||
describe '#object_type' do
|
||||
it 'returns :activity' do
|
||||
expect(child.object_type).to eq :activity
|
||||
end
|
||||
end
|
||||
|
||||
describe '#thread' do
|
||||
it 'calls Parent#thread' do
|
||||
expect_any_instance_of(Parent).to receive(:thread)
|
||||
child.thread
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hidden?' do
|
||||
it 'returns false' do
|
||||
expect(child.hidden?).to be false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,143 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe RemoteProfile do
|
||||
let(:remote_profile) { RemoteProfile.new(body) }
|
||||
let(:body) do
|
||||
<<-XML
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<author>John</author>
|
||||
XML
|
||||
end
|
||||
|
||||
describe '.initialize' do
|
||||
it 'calls Nokogiri::XML.parse' do
|
||||
expect(Nokogiri::XML).to receive(:parse).with(body, nil, 'utf-8')
|
||||
RemoteProfile.new(body)
|
||||
end
|
||||
|
||||
it 'sets document' do
|
||||
remote_profile = RemoteProfile.new(body)
|
||||
expect(remote_profile).not_to be nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#root' do
|
||||
let(:document) { remote_profile.document }
|
||||
|
||||
it 'callse document.at_xpath' do
|
||||
expect(document).to receive(:at_xpath).with(
|
||||
'/atom:feed|/atom:entry',
|
||||
atom: OStatus::TagManager::XMLNS
|
||||
)
|
||||
|
||||
remote_profile.root
|
||||
end
|
||||
end
|
||||
|
||||
describe '#author' do
|
||||
let(:root) { remote_profile.root }
|
||||
|
||||
it 'calls root.at_xpath' do
|
||||
expect(root).to receive(:at_xpath).with(
|
||||
'./atom:author|./dfrn:owner',
|
||||
atom: OStatus::TagManager::XMLNS,
|
||||
dfrn: OStatus::TagManager::DFRN_XMLNS
|
||||
)
|
||||
|
||||
remote_profile.author
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hub_link' do
|
||||
let(:root) { remote_profile.root }
|
||||
|
||||
it 'calls #link_href_from_xml' do
|
||||
expect(remote_profile).to receive(:link_href_from_xml).with(root, 'hub')
|
||||
remote_profile.hub_link
|
||||
end
|
||||
end
|
||||
|
||||
describe '#display_name' do
|
||||
let(:author) { remote_profile.author }
|
||||
|
||||
it 'calls author.at_xpath.content' do
|
||||
expect(author).to receive_message_chain(:at_xpath, :content).with(
|
||||
'./poco:displayName',
|
||||
poco: OStatus::TagManager::POCO_XMLNS
|
||||
).with(no_args)
|
||||
|
||||
remote_profile.display_name
|
||||
end
|
||||
end
|
||||
|
||||
describe '#note' do
|
||||
let(:author) { remote_profile.author }
|
||||
|
||||
it 'calls author.at_xpath.content' do
|
||||
expect(author).to receive_message_chain(:at_xpath, :content).with(
|
||||
'./atom:summary|./poco:note',
|
||||
atom: OStatus::TagManager::XMLNS,
|
||||
poco: OStatus::TagManager::POCO_XMLNS
|
||||
).with(no_args)
|
||||
|
||||
remote_profile.note
|
||||
end
|
||||
end
|
||||
|
||||
describe '#scope' do
|
||||
let(:author) { remote_profile.author }
|
||||
|
||||
it 'calls author.at_xpath.content' do
|
||||
expect(author).to receive_message_chain(:at_xpath, :content).with(
|
||||
'./mastodon:scope',
|
||||
mastodon: OStatus::TagManager::MTDN_XMLNS
|
||||
).with(no_args)
|
||||
|
||||
remote_profile.scope
|
||||
end
|
||||
end
|
||||
|
||||
describe '#avatar' do
|
||||
let(:author) { remote_profile.author }
|
||||
|
||||
it 'calls #link_href_from_xml' do
|
||||
expect(remote_profile).to receive(:link_href_from_xml).with(author, 'avatar')
|
||||
remote_profile.avatar
|
||||
end
|
||||
end
|
||||
|
||||
describe '#header' do
|
||||
let(:author) { remote_profile.author }
|
||||
|
||||
it 'calls #link_href_from_xml' do
|
||||
expect(remote_profile).to receive(:link_href_from_xml).with(author, 'header')
|
||||
remote_profile.header
|
||||
end
|
||||
end
|
||||
|
||||
describe '#locked?' do
|
||||
before do
|
||||
allow(remote_profile).to receive(:scope).and_return(scope)
|
||||
end
|
||||
|
||||
subject { remote_profile.locked? }
|
||||
|
||||
context 'scope is private' do
|
||||
let(:scope) { 'private' }
|
||||
|
||||
it 'returns true' do
|
||||
is_expected.to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'scope is not private' do
|
||||
let(:scope) { 'public' }
|
||||
|
||||
it 'returns false' do
|
||||
is_expected.to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,192 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe StreamEntry, type: :model do
|
||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let(:bob) { Fabricate(:account, username: 'bob') }
|
||||
let(:status) { Fabricate(:status, account: alice) }
|
||||
let(:reblog) { Fabricate(:status, account: bob, reblog: status) }
|
||||
let(:reply) { Fabricate(:status, account: bob, thread: status) }
|
||||
let(:stream_entry) { Fabricate(:stream_entry, activity: activity) }
|
||||
let(:activity) { reblog }
|
||||
|
||||
describe '#object_type' do
|
||||
before do
|
||||
allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
|
||||
allow(stream_entry).to receive(:targeted?).and_return(targeted)
|
||||
end
|
||||
|
||||
subject { stream_entry.object_type }
|
||||
|
||||
context 'orphaned? is true' do
|
||||
let(:orphaned) { true }
|
||||
let(:targeted) { false }
|
||||
|
||||
it 'returns :activity' do
|
||||
is_expected.to be :activity
|
||||
end
|
||||
end
|
||||
|
||||
context 'targeted? is true' do
|
||||
let(:orphaned) { false }
|
||||
let(:targeted) { true }
|
||||
|
||||
it 'returns :activity' do
|
||||
is_expected.to be :activity
|
||||
end
|
||||
end
|
||||
|
||||
context 'orphaned? and targeted? are false' do
|
||||
let(:orphaned) { false }
|
||||
let(:targeted) { false }
|
||||
|
||||
context 'activity is reblog' do
|
||||
let(:activity) { reblog }
|
||||
|
||||
it 'returns :note' do
|
||||
is_expected.to be :note
|
||||
end
|
||||
end
|
||||
|
||||
context 'activity is reply' do
|
||||
let(:activity) { reply }
|
||||
|
||||
it 'returns :comment' do
|
||||
is_expected.to be :comment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#verb' do
|
||||
before do
|
||||
allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
|
||||
end
|
||||
|
||||
subject { stream_entry.verb }
|
||||
|
||||
context 'orphaned? is true' do
|
||||
let(:orphaned) { true }
|
||||
|
||||
it 'returns :delete' do
|
||||
is_expected.to be :delete
|
||||
end
|
||||
end
|
||||
|
||||
context 'orphaned? is false' do
|
||||
let(:orphaned) { false }
|
||||
|
||||
context 'activity is reblog' do
|
||||
let(:activity) { reblog }
|
||||
|
||||
it 'returns :share' do
|
||||
is_expected.to be :share
|
||||
end
|
||||
end
|
||||
|
||||
context 'activity is reply' do
|
||||
let(:activity) { reply }
|
||||
|
||||
it 'returns :post' do
|
||||
is_expected.to be :post
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#mentions' do
|
||||
before do
|
||||
allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
|
||||
end
|
||||
|
||||
subject { stream_entry.mentions }
|
||||
|
||||
context 'orphaned? is true' do
|
||||
let(:orphaned) { true }
|
||||
|
||||
it 'returns []' do
|
||||
is_expected.to eq []
|
||||
end
|
||||
end
|
||||
|
||||
context 'orphaned? is false' do
|
||||
before do
|
||||
reblog.mentions << Fabricate(:mention, account: alice)
|
||||
reblog.mentions << Fabricate(:mention, account: bob)
|
||||
end
|
||||
|
||||
let(:orphaned) { false }
|
||||
|
||||
it 'returns [Account] includes alice and bob' do
|
||||
is_expected.to eq [alice, bob]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#targeted?' do
|
||||
it 'returns true for a reblog' do
|
||||
expect(reblog.stream_entry.targeted?).to be true
|
||||
end
|
||||
|
||||
it 'returns false otherwise' do
|
||||
expect(status.stream_entry.targeted?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#threaded?' do
|
||||
it 'returns true for a reply' do
|
||||
expect(reply.stream_entry.threaded?).to be true
|
||||
end
|
||||
|
||||
it 'returns false otherwise' do
|
||||
expect(status.stream_entry.threaded?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'delegated methods' do
|
||||
context 'with a nil status' do
|
||||
subject { described_class.new(status: nil) }
|
||||
|
||||
it 'returns nil for target' do
|
||||
expect(subject.target).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil for title' do
|
||||
expect(subject.title).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil for content' do
|
||||
expect(subject.content).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil for thread' do
|
||||
expect(subject.thread).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a real status' do
|
||||
let(:original) { Fabricate(:status, text: 'Test status') }
|
||||
let(:status) { Fabricate(:status, reblog: original, thread: original) }
|
||||
subject { described_class.new(status: status) }
|
||||
|
||||
it 'delegates target' do
|
||||
expect(status.target).not_to be_nil
|
||||
expect(subject.target).to eq(status.target)
|
||||
end
|
||||
|
||||
it 'delegates title' do
|
||||
expect(status.title).not_to be_nil
|
||||
expect(subject.title).to eq(status.title)
|
||||
end
|
||||
|
||||
it 'delegates content' do
|
||||
expect(status.content).not_to be_nil
|
||||
expect(subject.content).to eq(status.content)
|
||||
end
|
||||
|
||||
it 'delegates thread' do
|
||||
expect(status.thread).not_to be_nil
|
||||
expect(subject.thread).to eq(status.thread)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,8 +15,8 @@ RSpec.describe ProcessMentionsService, type: :service do
|
|||
subject.call(status)
|
||||
end
|
||||
|
||||
it 'creates a mention' do
|
||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||
it 'does not create a mention' do
|
||||
expect(remote_user.mentions.where(status: status).count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -27,14 +27,13 @@ RSpec.describe SuspendAccountService, type: :service do
|
|||
[
|
||||
account.statuses,
|
||||
account.media_attachments,
|
||||
account.stream_entries,
|
||||
account.notifications,
|
||||
account.favourites,
|
||||
account.active_relationships,
|
||||
account.passive_relationships,
|
||||
account.subscriptions
|
||||
].map(&:count)
|
||||
}.from([1, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
|
||||
}.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0])
|
||||
end
|
||||
|
||||
it 'sends a delete actor activity to all known inboxes' do
|
||||
|
@ -70,14 +69,13 @@ RSpec.describe SuspendAccountService, type: :service do
|
|||
[
|
||||
remote_bob.statuses,
|
||||
remote_bob.media_attachments,
|
||||
remote_bob.stream_entries,
|
||||
remote_bob.notifications,
|
||||
remote_bob.favourites,
|
||||
remote_bob.active_relationships,
|
||||
remote_bob.passive_relationships,
|
||||
remote_bob.subscriptions
|
||||
].map(&:count)
|
||||
}.from([1, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
|
||||
}.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0])
|
||||
end
|
||||
|
||||
it 'sends a reject follow to follwer inboxes' do
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true do
|
||||
describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
|
||||
before do
|
||||
double(:api_oembed_url => '')
|
||||
double(:account_stream_entry_url => '')
|
||||
allow(view).to receive(:show_landing_strip?).and_return(true)
|
||||
allow(view).to receive(:site_title).and_return('example site')
|
||||
allow(view).to receive(:site_hostname).and_return('example.com')
|
||||
|
@ -23,9 +22,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
|
|||
reply = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
|
||||
|
||||
assign(:status, status)
|
||||
assign(:stream_entry, status.stream_entry)
|
||||
assign(:account, alice)
|
||||
assign(:type, status.stream_entry.activity_type.downcase)
|
||||
assign(:descendant_threads, [])
|
||||
|
||||
render
|
||||
|
@ -46,11 +43,9 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
|
|||
comment = Fabricate(:status, account: carl, thread: reply, text: 'Hello Bob')
|
||||
|
||||
assign(:status, reply)
|
||||
assign(:stream_entry, reply.stream_entry)
|
||||
assign(:account, alice)
|
||||
assign(:type, reply.stream_entry.activity_type.downcase)
|
||||
assign(:ancestors, reply.stream_entry.activity.ancestors(1, bob))
|
||||
assign(:descendant_threads, [{ statuses: reply.stream_entry.activity.descendants(1) }])
|
||||
assign(:ancestors, reply.ancestors(1, bob))
|
||||
assign(:descendant_threads, [{ statuses: reply.descendants(1) }])
|
||||
|
||||
render
|
||||
|
||||
|
@ -71,9 +66,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
|
|||
status = Fabricate(:status, account: alice, text: 'Hello World')
|
||||
|
||||
assign(:status, status)
|
||||
assign(:stream_entry, status.stream_entry)
|
||||
assign(:account, alice)
|
||||
assign(:type, status.stream_entry.activity_type.downcase)
|
||||
assign(:descendant_threads, [])
|
||||
|
||||
render
|
Loading…
Reference in a new issue