Implement quote posts in Moderator UI (#35964)
This commit is contained in:
@@ -113,6 +113,7 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def material_symbol(icon, attributes = {})
|
def material_symbol(icon, attributes = {})
|
||||||
|
whitespace = attributes.delete(:whitespace) { true }
|
||||||
safe_join(
|
safe_join(
|
||||||
[
|
[
|
||||||
inline_svg_tag(
|
inline_svg_tag(
|
||||||
@@ -121,7 +122,7 @@ module ApplicationHelper
|
|||||||
role: :img,
|
role: :img,
|
||||||
data: attributes[:data]
|
data: attributes[:data]
|
||||||
),
|
),
|
||||||
' ',
|
whitespace ? ' ' : '',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ module StatusesHelper
|
|||||||
status.preloadable_poll.options.map { |o| "[ ] #{o}" }.join("\n")
|
status.preloadable_poll.options.map { |o| "[ ] #{o}" }.join("\n")
|
||||||
end
|
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)
|
def status_description(status)
|
||||||
components = [[media_summary(status), status_text_summary(status)].compact_blank.join(' · ')]
|
components = [[media_summary(status), status_text_summary(status)].compact_blank.join(' · ')]
|
||||||
|
|
||||||
|
|||||||
@@ -1955,60 +1955,77 @@ a.sparkline {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-height: 100%;
|
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 {
|
.status__prepend {
|
||||||
padding: 0 0 15px;
|
padding: 0 0 15px;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content {
|
> details {
|
||||||
padding-top: 0;
|
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 {
|
&::after {
|
||||||
summary {
|
content: attr(data-show, 'Show more');
|
||||||
|
margin-top: 8px;
|
||||||
display: block;
|
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;
|
font-size: 15px;
|
||||||
line-height: 22px;
|
line-height: 20px;
|
||||||
|
color: $highlight-text-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border: 0;
|
||||||
&::after {
|
background: transparent;
|
||||||
content: attr(data-show, 'Show more');
|
padding: 0;
|
||||||
margin-top: 8px;
|
text-decoration: none;
|
||||||
display: block;
|
font-weight: 500;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&[open] summary {
|
&:hover,
|
||||||
margin-bottom: 16px;
|
&:focus-visible {
|
||||||
|
|
||||||
&::after {
|
&::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 {
|
.preview-card {
|
||||||
@@ -2065,6 +2082,14 @@ a.sparkline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detailed-status__meta {
|
||||||
|
.detailed-status__application,
|
||||||
|
.detailed-status__datetime,
|
||||||
|
.detailed-status__link {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin {
|
.admin {
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ a.table-action-link {
|
|||||||
|
|
||||||
// Reset the status card to not have borders, background or padding when
|
// Reset the status card to not have borders, background or padding when
|
||||||
// inline in the table of statuses
|
// inline in the table of statuses
|
||||||
.status__card {
|
.batch-table__row__content > .status__card {
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
-# locals: (status:)
|
-# locals: (status:, is_quote: false)
|
||||||
|
.status__card{ class: status_classnames(status, is_quote) }
|
||||||
.status__card><
|
|
||||||
- if status.reblog?
|
- if status.reblog?
|
||||||
.status__prepend
|
.status__prepend
|
||||||
= material_symbol('repeat')
|
= material_symbol('repeat')
|
||||||
@@ -10,31 +9,48 @@
|
|||||||
= material_symbol('reply')
|
= 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))
|
= 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
|
= render partial: 'admin/shared/status_content', locals: { status: status.proper, is_quote: is_quote }
|
||||||
- if status.application
|
|
||||||
= status.application.name
|
- 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
|
= 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)
|
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at)
|
||||||
- if status.edited?
|
- if status.edited?
|
||||||
·
|
·
|
||||||
= conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status, { anchor: 'history' }), class: 'detailed-status__datetime' do
|
= 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'))
|
%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?
|
- if status.discarded?
|
||||||
·
|
·
|
||||||
%span.negative-hint= t('admin.statuses.deleted')
|
%span.negative-hint= t('admin.statuses.deleted')
|
||||||
- unless status.reblog?
|
- if status.application
|
||||||
·
|
·
|
||||||
%span<
|
= status.application.name
|
||||||
= material_symbol(visibility_icon(status))
|
- unless status.reblog?
|
||||||
= t("statuses.visibilities.#{status.visibility}")
|
·
|
||||||
- if status.proper.sensitive?
|
%span<
|
||||||
·
|
= material_symbol(visibility_icon(status))
|
||||||
= material_symbol('visibility_off')
|
= t("statuses.visibilities.#{status.visibility}")
|
||||||
= t('stream_entries.sensitive_content')
|
- if status.proper.sensitive?
|
||||||
- unless status.direct_visibility?
|
·
|
||||||
·
|
= material_symbol('visibility_off')
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do
|
= t('stream_entries.sensitive_content')
|
||||||
= t('admin.statuses.view_publicly')
|
- unless status.direct_visibility?
|
||||||
|
·
|
||||||
|
= link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do
|
||||||
|
= t('admin.statuses.view_publicly')
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
-# locals: (status:, is_quote: false)
|
||||||
- if status.with_poll?
|
- if status.with_poll?
|
||||||
.poll
|
.poll
|
||||||
%ul
|
%ul
|
||||||
@@ -23,3 +24,24 @@
|
|||||||
= render_audio_component(status)
|
= render_audio_component(status)
|
||||||
- else
|
- else
|
||||||
= render_media_gallery_component(status, visible: false)
|
= 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')
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
.status__content><
|
-# locals: (status:, is_quote: false)
|
||||||
- if status.spoiler_text.present?
|
- if status.spoiler_text.present?
|
||||||
%details<
|
%details<
|
||||||
%summary{
|
%summary{
|
||||||
data: {
|
data: {
|
||||||
show: t('statuses.content_warnings.show'),
|
show: t('statuses.content_warnings.show'),
|
||||||
hide: t('statuses.content_warnings.hide'),
|
hide: t('statuses.content_warnings.hide'),
|
||||||
}
|
}
|
||||||
}><
|
}><
|
||||||
%strong>
|
%strong>
|
||||||
= prerender_custom_emojis(h(status.spoiler_text), status.emojis)
|
= prerender_custom_emojis(h(status.spoiler_text), status.emojis)
|
||||||
|
.status__content><
|
||||||
= prerender_custom_emojis(status_content_format(status), status.emojis)
|
= 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 }
|
||||||
- else
|
- else
|
||||||
|
.status__content><
|
||||||
= prerender_custom_emojis(status_content_format(status), status.emojis)
|
= 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 }
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
%tr
|
%tr
|
||||||
%th= t('admin.statuses.reblogs')
|
%th= t('admin.statuses.reblogs')
|
||||||
%td= friendly_number_to_human @status.reblogs_count
|
%td= friendly_number_to_human @status.reblogs_count
|
||||||
|
%tr
|
||||||
|
%th= t('admin.statuses.quotes')
|
||||||
|
%td= friendly_number_to_human @status.quotes_count
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.statuses.favourites')
|
%th= t('admin.statuses.favourites')
|
||||||
%td= friendly_number_to_human @status.favourites_count
|
%td= friendly_number_to_human @status.favourites_count
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
%tr
|
%tr
|
||||||
%td
|
%td
|
||||||
%span{ title: session.user_agent }<
|
%span{ title: session.user_agent }<
|
||||||
= material_symbol session_device_icon(session), 'aria-label': session_device_icon(session)
|
= material_symbol session_device_icon(session)
|
||||||
|
|
||||||
= t 'sessions.description',
|
= t 'sessions.description',
|
||||||
browser: t("sessions.browsers.#{session.browser}", default: session.browser.to_s),
|
browser: t("sessions.browsers.#{session.browser}", default: session.browser.to_s),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
- @verified_links.each do |field|
|
- @verified_links.each do |field|
|
||||||
%li
|
%li
|
||||||
%span.verified-badge
|
%span.verified-badge
|
||||||
= material_symbol 'check', class: 'verified-badge__mark'
|
= material_symbol 'check', { class: 'verified-badge__mark' }
|
||||||
%span= field.value
|
%span= field.value
|
||||||
|
|
||||||
= simple_form_for @account, url: settings_verification_path, html: { class: 'form-section' } do |f|
|
= simple_form_for @account, url: settings_verification_path, html: { class: 'form-section' } do |f|
|
||||||
|
|||||||
@@ -905,6 +905,7 @@ en:
|
|||||||
no_status_selected: No posts were changed as none were selected
|
no_status_selected: No posts were changed as none were selected
|
||||||
open: Open post
|
open: Open post
|
||||||
original_status: Original post
|
original_status: Original post
|
||||||
|
quotes: Quotes
|
||||||
reblogs: Reblogs
|
reblogs: Reblogs
|
||||||
replied_to_html: Replied to %{acct_link}
|
replied_to_html: Replied to %{acct_link}
|
||||||
status_changed: Post changed
|
status_changed: Post changed
|
||||||
@@ -912,6 +913,7 @@ en:
|
|||||||
title: Account posts - @%{name}
|
title: Account posts - @%{name}
|
||||||
trending: Trending
|
trending: Trending
|
||||||
view_publicly: View publicly
|
view_publicly: View publicly
|
||||||
|
view_quoted_post: View quoted post
|
||||||
visibility: Visibility
|
visibility: Visibility
|
||||||
with_media: With media
|
with_media: With media
|
||||||
strikes:
|
strikes:
|
||||||
@@ -1925,10 +1927,15 @@ en:
|
|||||||
limit: You have already pinned the maximum number of posts
|
limit: You have already pinned the maximum number of posts
|
||||||
ownership: Someone else's post cannot be pinned
|
ownership: Someone else's post cannot be pinned
|
||||||
reblog: A boost 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:
|
quote_policies:
|
||||||
followers: Followers only
|
followers: Followers only
|
||||||
nobody: Just me
|
nobody: Just me
|
||||||
public: Anyone
|
public: Anyone
|
||||||
|
quote_post_author: Quoted a post by %{acct}
|
||||||
title: '%{name}: "%{quote}"'
|
title: '%{name}: "%{quote}"'
|
||||||
visibilities:
|
visibilities:
|
||||||
direct: Private mention
|
direct: Private mention
|
||||||
|
|||||||
Reference in New Issue
Block a user