2
0

Implement quote posts in Moderator UI (#35964)

This commit is contained in:
Emelia Smith
2025-10-16 15:40:24 +02:00
committed by GitHub
parent 51d0bfcb38
commit 210b389643
11 changed files with 170 additions and 86 deletions

View File

@@ -113,6 +113,7 @@ module ApplicationHelper
end
def material_symbol(icon, attributes = {})
whitespace = attributes.delete(:whitespace) { true }
safe_join(
[
inline_svg_tag(
@@ -121,7 +122,7 @@ module ApplicationHelper
role: :img,
data: attributes[:data]
),
' ',
whitespace ? ' ' : '',
]
)
end

View File

@@ -46,6 +46,14 @@ module StatusesHelper
status.preloadable_poll.options.map { |o| "[ ] #{o}" }.join("\n")
end
def status_classnames(status, is_quote)
if is_quote
'status--is-quote'
elsif status.quote.present?
'status--has-quote'
end
end
def status_description(status)
components = [[media_summary(status), status_text_summary(status)].compact_blank.join(' · ')]

View File

@@ -1955,60 +1955,77 @@ a.sparkline {
box-sizing: border-box;
min-height: 100%;
&.status--has-quote {
.quote-inline {
display: none;
}
}
.status__quote & {
// Remove the border from the .status__card within .status__quote
border: none;
.display-name__account {
line-height: inherit;
}
.status__avatar,
.status__avatar .account__avatar {
width: 32px;
height: 32px;
}
}
.status__prepend {
padding: 0 0 15px;
gap: 4px;
align-items: center;
}
.status__content {
padding-top: 0;
> details {
summary {
display: block;
box-sizing: border-box;
background: var(--nested-card-background);
color: var(--nested-card-text);
border: var(--nested-card-border);
border-radius: 8px;
padding: 8px 13px;
position: relative;
font-size: 15px;
line-height: 22px;
cursor: pointer;
> details {
summary {
&::after {
content: attr(data-show, 'Show more');
margin-top: 8px;
display: block;
box-sizing: border-box;
background: var(--nested-card-background);
color: var(--nested-card-text);
border: var(--nested-card-border);
border-radius: 8px;
padding: 8px 13px;
position: relative;
font-size: 15px;
line-height: 22px;
line-height: 20px;
color: $highlight-text-color;
cursor: pointer;
&::after {
content: attr(data-show, 'Show more');
margin-top: 8px;
display: block;
font-size: 15px;
line-height: 20px;
color: $highlight-text-color;
cursor: pointer;
border: 0;
background: transparent;
padding: 0;
text-decoration: none;
font-weight: 500;
}
&:hover,
&:focus-visible {
&::after {
text-decoration: underline !important;
}
}
border: 0;
background: transparent;
padding: 0;
text-decoration: none;
font-weight: 500;
}
&[open] summary {
margin-bottom: 16px;
&:hover,
&:focus-visible {
&::after {
content: attr(data-hide, 'Hide post');
text-decoration: underline !important;
}
}
}
&[open] summary {
margin-bottom: 16px;
&::after {
content: attr(data-hide, 'Hide post');
}
}
}
.preview-card {
@@ -2065,6 +2082,14 @@ a.sparkline {
}
}
}
.detailed-status__meta {
.detailed-status__application,
.detailed-status__datetime,
.detailed-status__link {
color: inherit;
}
}
}
.admin {

View File

@@ -356,7 +356,7 @@ a.table-action-link {
// Reset the status card to not have borders, background or padding when
// inline in the table of statuses
.status__card {
.batch-table__row__content > .status__card {
border: none;
background: none;
padding: 0;

View File

@@ -1,6 +1,5 @@
-# locals: (status:)
.status__card><
-# locals: (status:, is_quote: false)
.status__card{ class: status_classnames(status, is_quote) }
- if status.reblog?
.status__prepend
= material_symbol('repeat')
@@ -10,31 +9,48 @@
= material_symbol('reply')
= t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(status.in_reply_to_account, path: status.thread.present? ? admin_account_status_path(status.thread.account_id, status.in_reply_to_id) : nil))
= render partial: 'admin/shared/status_content', locals: { status: status.proper }
- if is_quote
.status__info
= conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status), class: 'status__relative-time' do
%span.status__visibility-icon{ title: t("statuses.visibilities.#{status.visibility}") }><
= material_symbol(visibility_icon(status), whitespace: false)
%time.relative-formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at)
= link_to admin_account_path(status.account.id), class: 'status__display-name' do
.status__avatar
.account__avatar
= image_tag status.account.avatar.url(:original), alt: '', width: 46, height: 46, class: 'avatar'
.display-name
%bdi
%strong.display-name__html.emojify.p-name= display_name(status.account, custom_emojify: true)
%span.display-name__account
= acct(status.account)
.detailed-status__meta
- if status.application
= status.application.name
·
= conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at)
- if status.edited?
&nbsp;·
= conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status, { anchor: 'history' }), class: 'detailed-status__datetime' do
%span><= t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'relative-formatted'))
- if status.discarded?
&nbsp;·
%span.negative-hint= t('admin.statuses.deleted')
- unless status.reblog?
&nbsp;·
%span<
= material_symbol(visibility_icon(status))
= t("statuses.visibilities.#{status.visibility}")
- if status.proper.sensitive?
&nbsp;·
= material_symbol('visibility_off')
= t('stream_entries.sensitive_content')
- unless status.direct_visibility?
&nbsp;·
= link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do
= t('admin.statuses.view_publicly')
= render partial: 'admin/shared/status_content', locals: { status: status.proper, is_quote: is_quote }
- unless is_quote
.detailed-status__meta
= conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at)
- if status.edited?
&nbsp;·
= conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status, { anchor: 'history' }), class: 'detailed-status__datetime' do
%span><= t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'relative-formatted'))
- if status.discarded?
&nbsp;·
%span.negative-hint= t('admin.statuses.deleted')
- if status.application
&nbsp;·
= status.application.name
- unless status.reblog?
&nbsp;·
%span<
= material_symbol(visibility_icon(status))
= t("statuses.visibilities.#{status.visibility}")
- if status.proper.sensitive?
&nbsp;·
= material_symbol('visibility_off')
= t('stream_entries.sensitive_content')
- unless status.direct_visibility?
&nbsp;·
= link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do
= t('admin.statuses.view_publicly')

View File

@@ -1,3 +1,4 @@
-# locals: (status:, is_quote: false)
- if status.with_poll?
.poll
%ul
@@ -23,3 +24,24 @@
= render_audio_component(status)
- else
= render_media_gallery_component(status, visible: false)
- if status.quote
- if status.quote.accepted? && status.quote.quoted_status.present?
- if is_quote
.status__quote-author-button
%span= t('statuses.quote_post_author', acct: acct(status.account))
- else
.status__quote
= render partial: 'admin/shared/status', object: status.quote.quoted_status, locals: { is_quote: true }
- else
.status__quote.status__quote--error
- if status.quote.pending?
%span= t('statuses.quote_error.pending_approval')
- elsif status.quote.revoked?
%span= t('statuses.quote_error.revoked')
- else
%span= t('statuses.quote_error.not_available')
- if status.quote.quoted_status.present? && can?(:show, status.quote.quoted_status)
= link_to admin_account_status_path(status.quote.quoted_status.account.id, status.quote.quoted_status), class: 'link-button' do
= t('admin.statuses.view_quoted_post')

View File

@@ -1,16 +1,18 @@
.status__content><
- if status.spoiler_text.present?
%details<
%summary{
data: {
show: t('statuses.content_warnings.show'),
hide: t('statuses.content_warnings.hide'),
}
}><
%strong>
= prerender_custom_emojis(h(status.spoiler_text), status.emojis)
-# locals: (status:, is_quote: false)
- if status.spoiler_text.present?
%details<
%summary{
data: {
show: t('statuses.content_warnings.show'),
hide: t('statuses.content_warnings.hide'),
}
}><
%strong>
= prerender_custom_emojis(h(status.spoiler_text), status.emojis)
.status__content><
= prerender_custom_emojis(status_content_format(status), status.emojis)
= render partial: 'admin/shared/status_attachments', locals: { status: status.proper }
- else
= render partial: 'admin/shared/status_attachments', locals: { status: status.proper, is_quote: is_quote }
- else
.status__content><
= prerender_custom_emojis(status_content_format(status), status.emojis)
= render partial: 'admin/shared/status_attachments', locals: { status: status.proper }
= render partial: 'admin/shared/status_attachments', locals: { status: status.proper, is_quote: is_quote }

View File

@@ -45,6 +45,9 @@
%tr
%th= t('admin.statuses.reblogs')
%td= friendly_number_to_human @status.reblogs_count
%tr
%th= t('admin.statuses.quotes')
%td= friendly_number_to_human @status.quotes_count
%tr
%th= t('admin.statuses.favourites')
%td= friendly_number_to_human @status.favourites_count

View File

@@ -1,7 +1,7 @@
%tr
%td
%span{ title: session.user_agent }<
= material_symbol session_device_icon(session), 'aria-label': session_device_icon(session)
= material_symbol session_device_icon(session)
&nbsp;
= t 'sessions.description',
browser: t("sessions.browsers.#{session.browser}", default: session.browser.to_s),

View File

@@ -28,7 +28,7 @@
- @verified_links.each do |field|
%li
%span.verified-badge
= material_symbol 'check', class: 'verified-badge__mark'
= material_symbol 'check', { class: 'verified-badge__mark' }
%span= field.value
= simple_form_for @account, url: settings_verification_path, html: { class: 'form-section' } do |f|

View File

@@ -905,6 +905,7 @@ en:
no_status_selected: No posts were changed as none were selected
open: Open post
original_status: Original post
quotes: Quotes
reblogs: Reblogs
replied_to_html: Replied to %{acct_link}
status_changed: Post changed
@@ -912,6 +913,7 @@ en:
title: Account posts - @%{name}
trending: Trending
view_publicly: View publicly
view_quoted_post: View quoted post
visibility: Visibility
with_media: With media
strikes:
@@ -1925,10 +1927,15 @@ en:
limit: You have already pinned the maximum number of posts
ownership: Someone else's post cannot be pinned
reblog: A boost cannot be pinned
quote_error:
not_available: Post unavailable
pending_approval: Post pending
revoked: Post removed by author
quote_policies:
followers: Followers only
nobody: Just me
public: Anyone
quote_post_author: Quoted a post by %{acct}
title: '%{name}: "%{quote}"'
visibilities:
direct: Private mention