From c2397151f213f341ce948af7f4112512785b32c0 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:07:07 +0000 Subject: [PATCH 01/59] models: status: add support for quoting (cherry picked from commit b1bce9d1939ab5b7e690eb08e8b81faca1d95f6b) --- app/models/status.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/models/status.rb b/app/models/status.rb index 4f2ceb9ca95271..d40eb0014d5d25 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -29,6 +29,7 @@ # edited_at :datetime # trendable :boolean # ordered_media_attachment_ids :bigint(8) is an Array +# quote_id :bigint(8) # class Status < ApplicationRecord @@ -66,6 +67,7 @@ class Status < ApplicationRecord with_options class_name: 'Status', optional: true do belongs_to :thread, foreign_key: 'in_reply_to_id', inverse_of: :replies belongs_to :reblog, foreign_key: 'reblog_of_id', inverse_of: :reblogs + belongs_to :quote, inverse_of: :quote end has_many :favourites, inverse_of: :status, dependent: :destroy @@ -76,6 +78,7 @@ class Status < ApplicationRecord has_many :mentions, dependent: :destroy, inverse_of: :status has_many :mentioned_accounts, through: :mentions, source: :account, class_name: 'Account' has_many :media_attachments, dependent: :nullify + has_many :quoted, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quote, dependent: :nullify # The `dependent` option is enabled by the initial `mentions` association declaration has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status # rubocop:disable Rails/HasManyOrHasOneDependent @@ -178,7 +181,18 @@ class Status < ApplicationRecord account: [:account_stat, user: :role], active_mentions: :account, ], - thread: :account + thread: :account, + quote: [ + :application, + :tags, + :preview_cards, + :media_attachments, + :conversation, + :status_stat, + :preloadable_poll, + account: [:account_stat, :user], + active_mentions: { account: :account_stat }, + ] delegate :domain, to: :account, prefix: true @@ -212,6 +226,10 @@ def reblog? !reblog_of_id.nil? end + def quote? + !quote_id.nil? && quote + end + def within_realtime_window? created_at >= REAL_TIME_WINDOW.ago end @@ -284,7 +302,7 @@ def emojis fields = [spoiler_text, text] fields += preloadable_poll.options unless preloadable_poll.nil? - @emojis = CustomEmoji.from_text(fields.join(' '), account.domain) + @emojis = CustomEmoji.from_text(fields.join(' '), account.domain) + (quote? ? CustomEmoji.from_text([quote.spoiler_text, quote.text].join(' '), quote.account.domain) : []) end def ordered_media_attachments From e059e568af01a1025a5da83f228ab2db5cb08b7a Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:11:04 +0000 Subject: [PATCH 02/59] activitypub: note serializer: begrudgingly serialize quotes using misskey quoteUrl (cherry picked from commit 0990d5ac75cc83c841b74d58cec158b16836ea67) --- app/serializers/activitypub/note_serializer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 52ffaf71709d27..c8832a1b89e93c 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -9,7 +9,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer :in_reply_to, :published, :url, :attributed_to, :to, :cc, :sensitive, :atom_uri, :in_reply_to_atom_uri, - :conversation + :conversation, :quote_url attribute :content attribute :content_map, if: :language? @@ -150,6 +150,10 @@ def conversation end end + def quote_url + object.quote? ? ActivityPub::TagManager.instance.uri_for(object.quote) : nil + end + def local? object.account.local? end From 56570ed2eebe11efcf89e2a21b8d03f60af92b29 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:13:30 +0000 Subject: [PATCH 03/59] context helper: add quoteUrl as as:quoteUrl, even though its wrong (cherry picked from commit 6b07407820ef2e9152e68b392d136cd4a3ca6a4c) --- app/helpers/context_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index d70b2a88fd4c7b..befdd522b13c9c 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -42,6 +42,7 @@ module ContextHelper 'cipherText' => 'toot:cipherText', }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, + quoteUrl: { 'quoteUrl' => 'as:quoteUrl' }, }.freeze def full_context From 3b89c3fa5ec4f9e9700378c5efc851b06811aa65 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:16:28 +0000 Subject: [PATCH 04/59] activitypub: resolve quoted objects when new create activities are received (cherry picked from commit 968bd6f0ee9c8ae4dbb84a2e1bad6250bf394068) --- app/lib/activitypub/activity/create.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index f69b68392a5a01..7e5c22d0961b4d 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -130,6 +130,7 @@ def process_status_params media_attachment_ids: attachment_ids, ordered_media_attachment_ids: attachment_ids, poll: process_poll, + quote: quote_from_url(@object['quoteUrl']), } end @@ -432,4 +433,11 @@ def increment_voters_count! poll.reload retry end + + def quote_from_url(url) + return nil if url.nil? + + quote = ResolveURLService.new.call(url) + status_from_uri(quote.uri) if quote + end end From fc1271137fda372c60c002ac031528f5441919a6 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:18:17 +0000 Subject: [PATCH 05/59] statuses controller: accept quote_id parameter (cherry picked from commit ba965bec3deae8f2dbde50ebe5cfb71bafd7b83e) --- app/controllers/api/v1/statuses_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 2593ef7da582b3..875c0de153c14e 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -76,7 +76,8 @@ def create content_type: status_params[:content_type], allowed_mentions: status_params[:allowed_mentions], idempotency: request.headers['Idempotency-Key'], - with_rate_limit: true + with_rate_limit: true, + quote_id: status_params[:quote_id].presence ) render json: @status, serializer: serializer_for_status @@ -159,6 +160,7 @@ def status_params :visibility, :language, :scheduled_at, + :quote_id, :content_type, allowed_mentions: [], media_ids: [], From 218d967733e4ca8dad7f8153bf0333ca205cf35b Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:20:15 +0000 Subject: [PATCH 06/59] services: post status service: add quote_id to status parameters (cherry picked from commit ee98c0a6f8fb75f0a3970bd812b7382a470684a3) --- app/services/post_status_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index f67064c9a1f1dd..8c38f00ae3aa75 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -31,6 +31,7 @@ def initialize(message, accounts) # @option [String] :idempotency Optional idempotency key # @option [Boolean] :with_rate_limit # @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions + # @option [String] :quote_id # @return [Status] def call(account, options = {}) @account = account @@ -210,6 +211,7 @@ def status_attributes application: @options[:application], content_type: @options[:content_type] || @account.user&.setting_default_content_type, rate_limit: @options[:with_rate_limit], + quote_id: @options[:quote_id], }.compact end From 54d9f876519f676ea8d7f1f6c332afe715addf86 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 22:18:52 -0700 Subject: [PATCH 07/59] views: add quote status html view (cherry picked from commit 56d4b04358449a1974e33e0297e240e1e7e62ba1) --- app/views/statuses/_detailed_status.html.haml | 3 ++ app/views/statuses/_quote_status.html.haml | 35 +++++++++++++++++++ app/views/statuses/_simple_status.html.haml | 4 +++ 3 files changed, 42 insertions(+) create mode 100644 app/views/statuses/_quote_status.html.haml diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index c55dff5d9694e5..32790e3bf2c92e 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -15,6 +15,9 @@ = account_action_button(status.account) + - if status.quote? + = render partial: "statuses/quote_status", locals: {status: status.quote} + .status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? %p< diff --git a/app/views/statuses/_quote_status.html.haml b/app/views/statuses/_quote_status.html.haml new file mode 100644 index 00000000000000..6fdaa9a84f2c02 --- /dev/null +++ b/app/views/statuses/_quote_status.html.haml @@ -0,0 +1,35 @@ +.status.quote-status{ dataurl: ActivityPub::TagManager.instance.url_for(status) } + = 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 + = image_tag status.account.avatar_static_url, width: 18, height: 18, alt: '', class: 'u-photo account__avatar' + %span.display-name + %bdi + %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true) +   + %span.display-name__account + = acct(status.account) + = fa_icon('lock') if status.account.locked? + + .status__content.emojify< + - if status.spoiler_text? + %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< + %span.p-summary> #{Formatter.instance.format_spoiler(status)}  + %button.status__content__spoiler-link= t('statuses.show_more') + .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}" } + = Formatter.instance.format_in_quote(status, custom_emojify: true) + + - if !status.media_attachments.empty? + - if status.media_attachments.first.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, quote: true do + = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } + - elsif status.media_attachments.first.audio? + - audio = status.media_attachments.first + = react_component :audio, src: audio.file.url(:original), height: 60, alt: audio.description, duration: audio.file.meta.dig(:original, :duration) do + = 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, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }, quote: true do + = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } + - elsif status.preview_card + = react_component :card, maxDescription: 10, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json, quote: true diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index 6aa4d23dab4097..650a629223ded1 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -27,6 +27,10 @@ %span.display-name__account = acct(status.account) = fa_icon('lock') if status.account.locked? + + - if status.quote? + = render partial: "statuses/quote_status", locals: {status: status.quote} + .status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? %p< From a9387c7f80ac086b83865554fb883963dbaab8c0 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:25:40 +0000 Subject: [PATCH 08/59] rest: status serializer: include quote data (cherry picked from commit 28fb5c8c52ea3cead25fd861b5b370ff51226e78) --- app/serializers/rest/status_serializer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 96bdd600cfbcc5..537d5e4f85175b 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -25,6 +25,7 @@ class REST::StatusSerializer < ActiveModel::Serializer belongs_to :reblog, serializer: REST::StatusSerializer belongs_to :application, if: :show_application? belongs_to :account, serializer: REST::AccountSerializer + belongs_to :quote, serializer: REST::StatusSerializer has_many :ordered_media_attachments, key: :media_attachments, serializer: REST::MediaAttachmentSerializer has_many :ordered_mentions, key: :mentions From a235fb09db278bed1c1ef3176523108412f299cb Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:48:37 +0000 Subject: [PATCH 09/59] db: add quote_id to statuses table (cherry picked from commit 1df2577b893727601c21fedbb48ddbaa5b54ce4c) --- db/schema.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/schema.rb b/db/schema.rb index a3afab78167fe8..352ade9bf32f56 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1089,6 +1089,7 @@ t.datetime "edited_at", precision: nil t.boolean "trendable" t.bigint "ordered_media_attachment_ids", array: true + t.bigint "quote_id" t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" t.index ["account_id"], name: "index_statuses_on_account_id" t.index ["deleted_at"], name: "index_statuses_on_deleted_at", where: "(deleted_at IS NOT NULL)" From 4e822f0ad733a75a1b4f505d867c7a97524af071 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:48:52 +0000 Subject: [PATCH 10/59] sanitizer config: add quote-inline span to allowlist (cherry picked from commit 0b48ae2c3c9923b735cbf33d92ae0f03d95fd4a2) --- lib/sanitize_ext/sanitize_config.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb index e6ba5ab25022f3..f613c80f713f61 100644 --- a/lib/sanitize_ext/sanitize_config.rb +++ b/lib/sanitize_ext/sanitize_config.rb @@ -31,6 +31,7 @@ module Config next true if /^(h|p|u|dt|e)-/.match?(e) # microformats classes next true if /^(mention|hashtag)$/.match?(e) # semantic classes next true if /^(ellipsis|invisible)$/.match?(e) # link formatting classes + next true if /^quote-inline$/.match?(e) # quote inline classes end node['class'] = class_list.join(' ') From 5c0489807fb710fd7d33ec7a679c0602f0cae9f5 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 02:50:28 +0000 Subject: [PATCH 11/59] db: add quote_id migration (cherry picked from commit a697e1da13bea4f4ff64879065b2a2eb903f2071) --- db/migrate/20221224204906_add_quote_id_to_statuses.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20221224204906_add_quote_id_to_statuses.rb diff --git a/db/migrate/20221224204906_add_quote_id_to_statuses.rb b/db/migrate/20221224204906_add_quote_id_to_statuses.rb new file mode 100644 index 00000000000000..b01f6520a2e99f --- /dev/null +++ b/db/migrate/20221224204906_add_quote_id_to_statuses.rb @@ -0,0 +1,5 @@ +class AddQuoteIdToStatuses < ActiveRecord::Migration[6.1] + def change + add_column :statuses, :quote_id, :bigint, null: true, default: nil + end +end From 33ff4abee506ae06af64a67adf560d6e7e722434 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 03:56:19 +0000 Subject: [PATCH 12/59] status: disallow quoting of non-public posts (cherry picked from commit 1cef1eb847b4e26ab5a96f908aea72de8a80d9cd) --- app/models/status.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/status.rb b/app/models/status.rb index d40eb0014d5d25..be2b0a847e14ae 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -105,6 +105,7 @@ class Status < ApplicationRecord validates :reblog, uniqueness: { scope: :account }, if: :reblog? validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog? validates :content_type, inclusion: { in: %w(text/plain text/markdown text/html) }, allow_nil: true + validates :quote_visibility, inclusion: { in: %w(public unlisted) }, if: :quote? accepts_nested_attributes_for :poll @@ -230,6 +231,10 @@ def quote? !quote_id.nil? && quote end + def quote_visibility + quote&.visibility + end + def within_realtime_window? created_at >= REAL_TIME_WINDOW.ago end From 6dbd0454ad2ef7d826895262646752a6c4e9f535 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 04:02:08 +0000 Subject: [PATCH 13/59] status: prevent recursion when serializing (cherry picked from commit 36955a7a56bf36de94294b46960fd280442f0529) --- app/serializers/rest/status_serializer.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 537d5e4f85175b..3638e871b1c517 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -25,7 +25,6 @@ class REST::StatusSerializer < ActiveModel::Serializer belongs_to :reblog, serializer: REST::StatusSerializer belongs_to :application, if: :show_application? belongs_to :account, serializer: REST::AccountSerializer - belongs_to :quote, serializer: REST::StatusSerializer has_many :ordered_media_attachments, key: :media_attachments, serializer: REST::MediaAttachmentSerializer has_many :ordered_mentions, key: :mentions @@ -203,3 +202,13 @@ def url end end end + +class REST::QuoteStatusSerializer < REST::StatusSerializer + attribute :quote do + nil + end +end + +class REST::StatusSerializer < ActiveModel::Serializer + belongs_to :quote, serializer: REST::QuoteStatusSerializer +end From dff6a02707ed865e9480b595a7a04b2fa39bc8f1 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 04:04:29 +0000 Subject: [PATCH 14/59] db: add quote_id index (cherry picked from commit 8d86c77a58c3eb20e0db15a7aa4b498ae4af1566) --- .../20221224220348_add_index_to_statuses_quote_id.rb | 7 +++++++ db/schema.rb | 1 + 2 files changed, 8 insertions(+) create mode 100644 db/migrate/20221224220348_add_index_to_statuses_quote_id.rb diff --git a/db/migrate/20221224220348_add_index_to_statuses_quote_id.rb b/db/migrate/20221224220348_add_index_to_statuses_quote_id.rb new file mode 100644 index 00000000000000..2a51daf883fa55 --- /dev/null +++ b/db/migrate/20221224220348_add_index_to_statuses_quote_id.rb @@ -0,0 +1,7 @@ +class AddIndexToStatusesQuoteId < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def change + add_index :statuses, :quote_id, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index 352ade9bf32f56..99083c0bda7cf6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1097,6 +1097,7 @@ t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id", where: "(in_reply_to_account_id IS NOT NULL)" t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", where: "(in_reply_to_id IS NOT NULL)" + t.index ["quote_id"], name: "index_statuses_on_quote_id" t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id" t.index ["reblog_of_id", "id"], name: "index_statuses_on_reblog_of_id_and_id", order: { reblog_of_id: "DESC NULLS LAST", id: :desc } t.index ["uri"], name: "index_statuses_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)" From 63f0be12e4a23c8c2e1f3e57c48193d4cf9cea49 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 04:51:21 +0000 Subject: [PATCH 15/59] status: support either _misskey_quote or quoteUrl for fetching quotes (cherry picked from commit 61565488a6e6c4cadc5072b39a18dbde935d214a) --- app/lib/activitypub/activity/create.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 7e5c22d0961b4d..fb6e7421f1e717 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -130,7 +130,7 @@ def process_status_params media_attachment_ids: attachment_ids, ordered_media_attachment_ids: attachment_ids, poll: process_poll, - quote: quote_from_url(@object['quoteUrl']), + quote: process_quote, } end @@ -434,10 +434,21 @@ def increment_voters_count! retry end - def quote_from_url(url) + def guess_quote_url + if @object['quoteUrl'].present? + @object['quoteUrl'] + elsif @object['_misskey_quote'].present? + @object['_misskey_quote'] + end + end + + def process_quote + url = guess_quote_url return nil if url.nil? quote = ResolveURLService.new.call(url) status_from_uri(quote.uri) if quote + rescue + nil end end From 9825f8fccb56af3d04b013865e98d2eec87cd3a1 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 22:24:25 -0700 Subject: [PATCH 16/59] activitypub: case transform: support _misskey keys without messing them up (cherry picked from commit 14d001574c7b823929ee292851aa101502508a77) --- app/lib/activitypub/case_transform.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/activitypub/case_transform.rb b/app/lib/activitypub/case_transform.rb index bf5de722103a88..6b41ce42971509 100644 --- a/app/lib/activitypub/case_transform.rb +++ b/app/lib/activitypub/case_transform.rb @@ -14,7 +14,7 @@ def camel_lower(value) when String camel_lower_cache[value] ||= if value.start_with?('_:') "_:#{value.delete_prefix('_:').underscore.camelize(:lower)}" - elsif LanguagesHelper::ISO_639_1_REGIONAL.key?(value.to_sym) + elsif LanguagesHelper::ISO_639_1_REGIONAL.key?(value.to_sym) || value.start_with?('_') value else value.underscore.camelize(:lower) From 3fbde6d17d9d7193cafd866d46d9f5dcea7b5517 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 04:56:56 +0000 Subject: [PATCH 17/59] activitypub: note serializer: support _misskey keys (cherry picked from commit b36e884cc1507201c243c069813303f28dc8b881) --- app/serializers/activitypub/note_serializer.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index c8832a1b89e93c..c7f07bfc0df7e9 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -9,7 +9,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer :in_reply_to, :published, :url, :attributed_to, :to, :cc, :sensitive, :atom_uri, :in_reply_to_atom_uri, - :conversation, :quote_url + :conversation + + attribute :quote_url, if: -> { object.quote? } + attribute :misskey_quote, key: :_misskey_quote, if: -> { object.quote? } attribute :content attribute :content_map, if: :language? @@ -151,9 +154,11 @@ def conversation end def quote_url - object.quote? ? ActivityPub::TagManager.instance.uri_for(object.quote) : nil + ActivityPub::TagManager.instance.uri_for(object.quote) if object.quote? end + alias misskey_quote quote_url + def local? object.account.local? end From fa87ba789e6026f62527ca655d12d0884a23f522 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 22:27:42 -0700 Subject: [PATCH 18/59] javascript: glitch: pre-process misskey quotes to remove the URL part (cherry picked from commit 0d3df3e8cf2514880f7c1896a35a3de416d78004) --- .../glitch/actions/importer/normalizer.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index 5f10c8d889306a..8cda0edaea91b6 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -59,6 +59,8 @@ export function normalizeStatus(status, normalOldStatus, settings) { normalStatus.contentHtml = normalOldStatus.get('contentHtml'); normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml'); normalStatus.hidden = normalOldStatus.get('hidden'); + normalStatus.quote = normalOldStatus.get('quote'); + normalStatus.quote_hidden = normalOldStatus.get('quote_hidden'); if (normalOldStatus.get('translation')) { normalStatus.translation = normalOldStatus.get('translation'); @@ -72,6 +74,35 @@ export function normalizeStatus(status, normalOldStatus, settings) { normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); normalStatus.hidden = (spoilerText.length > 0 || normalStatus.sensitive) && autoHideCW(settings, spoilerText); + + if (status.quote && status.quote.id) { + const quote_spoilerText = status.quote.spoiler_text || ''; + const quote_searchContent = [quote_spoilerText, status.quote.content].join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); + + const quote_emojiMap = makeEmojiMap(normalStatus.quote); + + const quote_account_emojiMap = makeEmojiMap(status.quote.account); + const displayName = normalStatus.quote.account.display_name.length === 0 ? normalStatus.quote.account.username : normalStatus.quote.account.display_name; + normalStatus.quote.account.display_name_html = emojify(escapeTextContentForBrowser(displayName), quote_account_emojiMap); + normalStatus.quote.search_index = domParser.parseFromString(quote_searchContent, 'text/html').documentElement.textContent; + let docElem = domParser.parseFromString(normalStatus.quote.content, 'text/html').documentElement; + Array.from(docElem.querySelectorAll('span.quote-inline'), span => span.remove()); + Array.from(docElem.querySelectorAll('p,br'), line => { + let parentNode = line.parentNode; + if (line.nextSibling) { + parentNode.insertBefore(document.createTextNode(' '), line.nextSibling); + } + }); + let _contentHtml = docElem.textContent; + normalStatus.quote.contentHtml = '

'+emojify(_contentHtml.slice(0, 150), quote_emojiMap) + (_contentHtml.slice(150) ? '...' : '')+'

'; + normalStatus.quote.spoilerHtml = emojify(escapeTextContentForBrowser(quote_spoilerText), quote_emojiMap); + normalStatus.quote_hidden = (quote_spoilerText.length > 0 || normalStatus.quote.sensitive) && autoHideCW(settings, quote_spoilerText); + + // delete the quote link!!!! + let parentDocElem = domParser.parseFromString(normalStatus.contentHtml, 'text/html').documentElement; + Array.from(parentDocElem.querySelectorAll('span.quote-inline'), span => span.remove()); + normalStatus.contentHtml = parentDocElem.children[1].innerHTML; + } } if (normalOldStatus) { From 8a7161bd687679b746e2b6bbbbbc72307c1299b8 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 25 Dec 2022 10:58:25 +0000 Subject: [PATCH 19/59] javascript: glitch: dont render cards if the status has a quote attached (cherry picked from commit 5be6a59f80d56f342d0276bdea4ec48f2d0d4c5b) --- app/javascript/flavours/glitch/components/status.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 2eff30d76656c0..4561b1eacbc00d 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -730,7 +730,7 @@ class Status extends ImmutablePureComponent { if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0) && settings.getIn(['collapsed', 'backgrounds', 'preview_images'])) { background = attachments.getIn([0, 'preview_url']); } - } else if (status.get('card') && settings.get('inline_preview_cards') && !this.props.muted) { + } else if (!status.get('quote') && status.get('card') && settings.get('inline_preview_cards') && !this.props.muted) { media.push( Date: Sun, 25 Dec 2022 21:11:11 +0000 Subject: [PATCH 20/59] javascript: glitch: start rendering quotes (cherry picked from commit 005256ae8cfa1ccd5093a7ef43c5c797c0f9348f) --- .../glitch/components/status_content.jsx | 18 ++++++++++++++++++ .../flavours/glitch/styles/rich_text.scss | 1 + 2 files changed, 19 insertions(+) diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index 069dfc7898c9af..e35304ce9175ed 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -374,6 +374,21 @@ class StatusContent extends PureComponent { ); + let quote = ''; + + if (status.get('quote', null) !== null) { + let quoteStatus = status.get('quote'); + let quoteStatusContent = { __html: quoteStatus.get('contentHtml') }; + + quote = ( +
+
+
+
+
+ ); + } + if (status.get('spoiler_text').length > 0) { let mentionsPlaceholder = ''; @@ -409,6 +424,7 @@ class StatusContent extends PureComponent { {mentionsPlaceholder}
+ {quote}
+ {quote}
+ {quote}
Date: Sat, 29 Jun 2024 22:35:18 -0700 Subject: [PATCH 21/59] flavors: glitch: show emojified display name in quotes (cherry picked from commit 08aecd24bacf85fc161ed8a0874baf75408c4516) --- .../flavours/glitch/components/status_content.jsx | 14 ++++++++++++++ .../flavours/glitch/styles/rich_text.scss | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index e35304ce9175ed..01504782d7cebf 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -9,6 +9,7 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; +import Icon from 'flavours/glitch/components/icon'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; @@ -379,10 +380,23 @@ class StatusContent extends PureComponent { if (status.get('quote', null) !== null) { let quoteStatus = status.get('quote'); let quoteStatusContent = { __html: quoteStatus.get('contentHtml') }; + let quoteStatusAccount = quoteStatus.get('account'); + let quoteStatusDisplayName = { __html: quoteStatusAccount.get('display_name_html') }; quote = (
+ + + +
diff --git a/app/javascript/flavours/glitch/styles/rich_text.scss b/app/javascript/flavours/glitch/styles/rich_text.scss index 2deab6bd922f4f..f331a0257c7ca3 100644 --- a/app/javascript/flavours/glitch/styles/rich_text.scss +++ b/app/javascript/flavours/glitch/styles/rich_text.scss @@ -65,6 +65,11 @@ font-style: italic; } + i[role='img'] { + font-style: normal; + padding-right: 0.25em; + } + sub { font-size: smaller; vertical-align: sub; @@ -92,3 +97,7 @@ list-style-type: decimal; } } + +.status__quote { + padding-bottom: 0.5em; +} From 3ca521a4ac9ffb00228313dca1da814887c542e2 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 01:15:54 +0000 Subject: [PATCH 22/59] flavors: glitch: action bar: add quote button (cherry picked from commit adf1e9fc2e70ee8299fee6e965a15a5163ec367f) --- .../glitch/components/status_action_bar.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/javascript/flavours/glitch/components/status_action_bar.jsx b/app/javascript/flavours/glitch/components/status_action_bar.jsx index 7b00f84f1b96b5..ce289d84b538b7 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar.jsx @@ -46,6 +46,7 @@ const messages = defineMessages({ replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, + quote: { id: 'status.quote', defaultMessage: 'Quote' }, cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favorite' }, @@ -74,6 +75,7 @@ class StatusActionBar extends ImmutablePureComponent { onReply: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, + onQuote: PropTypes.func, onDelete: PropTypes.func, onDirect: PropTypes.func, onMention: PropTypes.func, @@ -140,6 +142,17 @@ class StatusActionBar extends ImmutablePureComponent { } }; + handleQuoteClick = () => { + const { signedIn } = this.context.identity; + + if (signedIn) { + this.props.onQuote(this.props.status, this.context.router.history); + } else { + // TODO(ariadne): Add an interaction modal for quoting specifically. + this.props.onInteractionModal('reply', this.props.status); + } + }; + handleBookmarkClick = (e) => { this.props.onBookmark(this.props.status, e); }; @@ -330,6 +343,7 @@ class StatusActionBar extends ImmutablePureComponent { obfuscateCount /> + From 7124a90561ac0777e79e089b7fa1b21f0e73c5ad Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 01:19:47 +0000 Subject: [PATCH 23/59] glitch: actions: add quoteCompose and cancelQuoteCompose (cherry picked from commit 9d4851e3cd1fbda668cf276748171e1cdc4d77b3) --- .../flavours/glitch/actions/compose.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 70bc5bce42513d..c09755f932efe2 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -90,6 +90,9 @@ export const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER'; export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS'; export const COMPOSE_FOCUS = 'COMPOSE_FOCUS'; +export const COMPOSE_QUOTE = 'COMPOSE_QUOTE'; +export const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL'; + const messages = defineMessages({ uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' }, uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, @@ -152,6 +155,25 @@ export function cancelReplyCompose() { }; } +export function quoteCompose(status, router) { + return (dispatch, getState) => { + dispatch({ + type: COMPOSE_QUOTE, + status: status, + }); + + if (!getState().getIn(['compose', 'mounted'])) { + router.push('/publish'); + } + }; +}; + +export function cancelQuoteCompose() { + return { + type: COMPOSE_QUOTE_CANCEL, + }; +}; + export function resetCompose() { return { type: COMPOSE_RESET, @@ -240,6 +262,7 @@ export function submitCompose(overridePrivacy = null) { status, content_type: getState().getIn(['compose', 'content_type']), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), + quote_id: getState().getIn(['compose', 'quote_id'], null), media_ids: media.map(item => item.get('id')), media_attributes, sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0), From e42ffdad6cc1ae4e3669554a989297fe0e7ba907 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 01:25:11 +0000 Subject: [PATCH 24/59] flavors: glitch: add quote indicator component (cherry picked from commit c7e00d4c4e86d98e7030511e24ef4e466634aa71) --- .../compose/components/quote_indicator.js | 82 +++++++++++++++++++ .../containers/quote_indicator_container.js | 27 ++++++ 2 files changed, 109 insertions(+) create mode 100644 app/javascript/flavours/glitch/features/compose/components/quote_indicator.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js diff --git a/app/javascript/flavours/glitch/features/compose/components/quote_indicator.js b/app/javascript/flavours/glitch/features/compose/components/quote_indicator.js new file mode 100644 index 00000000000000..a3eeee1350ebf8 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/quote_indicator.js @@ -0,0 +1,82 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Components. +import AccountContainer from 'flavours/glitch/containers/account_container'; +import IconButton from 'flavours/glitch/components/icon_button'; +import AttachmentList from 'flavours/glitch/components/attachment_list'; + +// Messages. +const messages = defineMessages({ + cancel: { + defaultMessage: 'Cancel', + id: 'quote_indicator.cancel', + }, +}); + + +export default @injectIntl +class QuoteIndicator extends ImmutablePureComponent { + + static propTypes = { + status: ImmutablePropTypes.map, + intl: PropTypes.object.isRequired, + onCancel: PropTypes.func, + }; + + handleClick = () => { + const { onCancel } = this.props; + if (onCancel) { + onCancel(); + } + } + + // Rendering. + render () { + const { status, intl } = this.props; + + if (!status) { + return null; + } + + const account = status.get('account'); + const content = status.get('content'); + const attachments = status.get('media_attachments'); + + // The result. + return ( +
+
+ + {account && ( + + )} +
+
+ {attachments.size > 0 && ( + + )} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js b/app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js new file mode 100644 index 00000000000000..9421d69dd8aa2f --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js @@ -0,0 +1,27 @@ +import { connect } from 'react-redux'; +import { cancelQuoteCompose } from 'flavours/glitch/actions/compose'; +import QuoteIndicator from '../components/quote_indicator'; + +const makeMapStateToProps = () => { + const mapStateToProps = state => { + const statusId = state.getIn(['compose', 'quote_id']); + const editing = false; + + return { + status: state.getIn(['statuses', statusId]), + editing, + }; + }; + + return mapStateToProps; +}; + +const mapDispatchToProps = dispatch => ({ + + onCancel () { + dispatch(cancelQuoteCompose()); + }, + +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(QuoteIndicator); From 96fdd9a01d462f31e2cb66da15b6baa67d9d988e Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 10 Aug 2024 22:52:34 -0700 Subject: [PATCH 25/59] flavors: glitch: add quote handling to status feature (cherry picked from commit a47d91707240dc6160ef00adb36f991d84acb1a5) --- .../flavours/glitch/features/status/index.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index 382f0ae03740c6..c234ee65dee97d 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -29,6 +29,7 @@ import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { initBlockModal } from '../../actions/blocks'; import { replyCompose, + quoteCompose, mentionCompose, directCompose, } from '../../actions/compose'; @@ -309,6 +310,21 @@ class Status extends ImmutablePureComponent { } }; + handleQuoteClick = (status) => { + const { signedIn } = this.context.identity; + const { dispatch } = this.props; + + if (signedIn) { + dispatch(quoteCompose(status, this.context.router.history)); + } else { + dispatch(openModal('INTERACTION', { + type: 'reply', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); + } + }; + handleReblogClick = (status, e) => { const { dispatch } = this.props; const { signedIn } = this.props.identity; @@ -714,6 +730,7 @@ class Status extends ImmutablePureComponent { onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onBookmark={this.handleBookmarkClick} + onQuote={this.handleQuoteClick} onDelete={this.handleDeleteClick} onEdit={this.handleEditClick} onDirect={this.handleDirectClick} From c9a93d75fda9c80ef97387559f726da3cc7f1591 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 01:41:32 +0000 Subject: [PATCH 26/59] add styles for quote indicator (cherry picked from commit 766a6438113176b66aec14441cd6a410e43db236) --- .../flavours/glitch/styles/components.scss | 3 +++ .../flavours/glitch/styles/contrast/diff.scss | 3 +++ .../flavours/glitch/styles/mastodon-light/diff.scss | 12 ++++++++++++ app/javascript/flavours/glitch/styles/rich_text.scss | 1 + 4 files changed, 19 insertions(+) diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index ed5e1b319daf75..264fbc4750da6a 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -1033,6 +1033,7 @@ body > [data-popper-placement] { .status__content, .edit-indicator__content, +.quote-indicator__content, .reply-indicator__content { position: relative; word-wrap: break-word; @@ -1128,6 +1129,7 @@ body > [data-popper-placement] { overflow: visible; } +.quote-indicator, .reply-indicator { display: grid; grid-template-columns: 46px minmax(0, 1fr); @@ -1288,6 +1290,7 @@ body > [data-popper-placement] { } .edit-indicator__content, +.quote-indicator__content, .reply-indicator__content { .emojione { width: 18px; diff --git a/app/javascript/flavours/glitch/styles/contrast/diff.scss b/app/javascript/flavours/glitch/styles/contrast/diff.scss index ae607f484a8c9b..d634be1f217974 100644 --- a/app/javascript/flavours/glitch/styles/contrast/diff.scss +++ b/app/javascript/flavours/glitch/styles/contrast/diff.scss @@ -1,4 +1,6 @@ .status__content a, +.link-footer a, +.quote-indicator__content a, .reply-indicator__content a, .edit-indicator__content a, .link-footer a, @@ -30,6 +32,7 @@ } .status__content a, +.quote-indicator__content a, .reply-indicator__content a, .edit-indicator__content a { color: $highlight-text-color; diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss index d41577cf0592f8..cc0046fc8d7d86 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss @@ -113,6 +113,7 @@ html { } // Change the background colors of status__content__spoiler-link +.quote-indicator__content .status__content__spoiler-link, .reply-indicator__content .status__content__spoiler-link, .status__content .status__content__spoiler-link { background: $ui-base-color; @@ -407,12 +408,23 @@ html { } .status__content, +.quote-indicator__content, .reply-indicator__content { a { color: $highlight-text-color; } } +.dismissable-banner { + border-left: 1px solid lighten($ui-base-color, 8%); + border-right: 1px solid lighten($ui-base-color, 8%); +} + +.quote-indicator { + background: transparent; + border: 1px solid lighten($ui-base-color, 8%); +} + .notification__filter-bar button.active::after, .account__section-headline a.active::after { border-color: transparent transparent $white; diff --git a/app/javascript/flavours/glitch/styles/rich_text.scss b/app/javascript/flavours/glitch/styles/rich_text.scss index f331a0257c7ca3..054eed32522961 100644 --- a/app/javascript/flavours/glitch/styles/rich_text.scss +++ b/app/javascript/flavours/glitch/styles/rich_text.scss @@ -2,6 +2,7 @@ .status__content__text, .e-content, .edit-indicator__content, +.quote-indicator__content, .reply-indicator__content { pre, blockquote { From 6b879de12d69f45ed882ca7768682bdec702c0d6 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 01:41:45 +0000 Subject: [PATCH 27/59] glitch: reducers: set up correct state for quoting (cherry picked from commit 214a4c9e6b81735da3b323269a0a7d17870929cf) --- .../flavours/glitch/reducers/compose.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 361b1fc081f7d0..cb218464c475d3 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -9,6 +9,8 @@ import { COMPOSE_REPLY, COMPOSE_REPLY_CANCEL, COMPOSE_DIRECT, + COMPOSE_QUOTE, + COMPOSE_QUOTE_CANCEL, COMPOSE_MENTION, COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_SUCCESS, @@ -82,6 +84,7 @@ const initialState = ImmutableMap({ preselectDate: null, in_reply_to: null, is_composing: false, + quote_id: null, is_submitting: false, is_changing_upload: false, is_uploading: false, @@ -170,6 +173,7 @@ function clearAll(state) { map.set('is_submitting', false); map.set('is_changing_upload', false); map.set('in_reply_to', null); + map.set('quote_id', null); map.update( 'advanced_options', map => map.mergeWith(overwrite, state.get('default_advanced_options')), @@ -443,6 +447,7 @@ export default function compose(state = initialState, action) { return state.withMutations(map => { map.set('id', null); map.set('in_reply_to', action.status.get('id')); + map.set('quote_id', null); map.set('text', statusToTextMentions(state, action.status)); map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); map.update( @@ -474,9 +479,27 @@ export default function compose(state = initialState, action) { map.set('spoiler_text', ''); } }); + case COMPOSE_QUOTE: + return state.withMutations(map => { + map.set('id', null); + map.set('in_reply_to', null); + map.set('quote_id', action.status.get('id')); + map.set('text', ''); + map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); + map.update( + 'advanced_options', + map => map.merge(new ImmutableMap({ do_not_federate: !!action.status.get('local_only') })) + ); + map.set('focusDate', new Date()); + map.set('caretPosition', null); + map.set('preselectDate', new Date()); + map.set('idempotencyKey', uuid()); + }); case COMPOSE_REPLY_CANCEL: state = state.setIn(['advanced_options', 'threaded_mode'], false); // eslint-disable-next-line no-fallthrough -- fall-through to `COMPOSE_RESET` is intended + case COMPOSE_QUOTE_CANCEL: + // eslint-disable-next-line no-fallthrough -- fall-through to `COMPOSE_RESET` is intended case COMPOSE_RESET: return state.withMutations(map => { map.set('in_reply_to', null); From 77249c3e76afcf032feb8843249bef877a74caa9 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 10 Aug 2024 23:01:45 -0700 Subject: [PATCH 28/59] glitch: fix up quote indicator (cherry picked from commit 5a8d4265ef7c64298b68fb7ba23efa88ac8b036d) --- .../flavours/glitch/components/status.jsx | 1 + .../glitch/containers/status_container.js | 13 +++++++++++++ .../{quote_indicator.js => quote_indicator.jsx} | 16 +++++++++++----- 3 files changed, 25 insertions(+), 5 deletions(-) rename app/javascript/flavours/glitch/features/compose/components/{quote_indicator.js => quote_indicator.jsx} (89%) diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 4561b1eacbc00d..e9018377e5b7e1 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -86,6 +86,7 @@ class Status extends ImmutablePureComponent { rootId: PropTypes.string, onClick: PropTypes.func, onReply: PropTypes.func, + onQuote: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, onBookmark: PropTypes.func, diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 116a3455f786eb..87e843accfe32b 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import { initBlockModal } from 'flavours/glitch/actions/blocks'; import { replyCompose, + quoteCompose, mentionCompose, directCompose, } from 'flavours/glitch/actions/compose'; @@ -84,6 +85,18 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({ }); }, + onQuote (status, router) { + dispatch((_, getState) => { + let state = getState(); + + if (state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0) { + dispatch(openModal({ modalType: 'CONFIRM_QUOTE', modalProps: { status }})); + } else { + dispatch(quoteCompose(status, router)); + } + }); + }, + onReblog (status, e) { dispatch(toggleReblog(status.get('id'), e.shiftKey)); }, diff --git a/app/javascript/flavours/glitch/features/compose/components/quote_indicator.js b/app/javascript/flavours/glitch/features/compose/components/quote_indicator.jsx similarity index 89% rename from app/javascript/flavours/glitch/features/compose/components/quote_indicator.js rename to app/javascript/flavours/glitch/features/compose/components/quote_indicator.jsx index a3eeee1350ebf8..bba63e03bc9c87 100644 --- a/app/javascript/flavours/glitch/features/compose/components/quote_indicator.js +++ b/app/javascript/flavours/glitch/features/compose/components/quote_indicator.jsx @@ -1,14 +1,17 @@ // Package imports. import PropTypes from 'prop-types'; import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; + import { defineMessages, injectIntl } from 'react-intl'; + +import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; // Components. -import AccountContainer from 'flavours/glitch/containers/account_container'; -import IconButton from 'flavours/glitch/components/icon_button'; import AttachmentList from 'flavours/glitch/components/attachment_list'; +import Icon from 'flavours/glitch/components/icon'; +import IconButton from 'flavours/glitch/components/icon_button'; +import AccountContainer from 'flavours/glitch/containers/account_container'; // Messages. const messages = defineMessages({ @@ -33,7 +36,7 @@ class QuoteIndicator extends ImmutablePureComponent { if (onCancel) { onCancel(); } - } + }; // Rendering. render () { @@ -58,6 +61,9 @@ class QuoteIndicator extends ImmutablePureComponent { title={intl.formatMessage(messages.cancel)} inverted /> + {account && (
{attachments.size > 0 && ( From 0e794e595bddf99e138ad9c9a9a5ed211fe383b5 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 02:57:20 +0000 Subject: [PATCH 29/59] activitypub: switch to fedibird:quoteUri (cherry picked from commit e59c40eb686684c353e5b322753c5022b444a92b) --- app/helpers/context_helper.rb | 2 +- app/serializers/activitypub/note_serializer.rb | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index befdd522b13c9c..40f7e67f5d10db 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -42,7 +42,7 @@ module ContextHelper 'cipherText' => 'toot:cipherText', }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, - quoteUrl: { 'quoteUrl' => 'as:quoteUrl' }, + quoteUri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' }, }.freeze def full_context diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index c7f07bfc0df7e9..0b4d3344905f55 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -11,8 +11,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer :atom_uri, :in_reply_to_atom_uri, :conversation - attribute :quote_url, if: -> { object.quote? } - attribute :misskey_quote, key: :_misskey_quote, if: -> { object.quote? } + attribute :quote_uri, if: -> { object.quote? } attribute :content attribute :content_map, if: :language? @@ -153,12 +152,10 @@ def conversation end end - def quote_url + def quote_uri ActivityPub::TagManager.instance.uri_for(object.quote) if object.quote? end - alias misskey_quote quote_url - def local? object.account.local? end From 62f4fcc7525d019ff86a3358f95420862ced0707 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 03:57:02 +0000 Subject: [PATCH 30/59] formatting helper: add the quote-inline hack for incompatible clients (cherry picked from commit 7d4127065d5c53977eb0ac3411481e9b23cd2f96) --- app/helpers/formatting_helper.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index f0d583bc541ec7..ede95735f761bc 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -19,7 +19,17 @@ def extract_status_plain_text(status) module_function :extract_status_plain_text def status_content_format(status) - html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), content_type: status.content_type) + base = html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), content_type: status.content_type) + + if status.quote? && status.local? + after_html = begin + "#{status.quote.to_log_permalink}" + end.html_safe # rubocop:disable Rails/OutputSafety + + base + after_html + else + base + end end def rss_status_content_format(status) From 86f0bce27c3030f4e43501ad5f9e012b8255990e Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 03:59:38 +0000 Subject: [PATCH 31/59] components: detailed status: suppress cards on quote posts (cherry picked from commit 67a7b6067ade900187ee06e00c9835ae67e2c5b2) --- .../glitch/features/status/components/detailed_status.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index 2db9fa6d3a367d..4d55eafac311bf 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -221,7 +221,7 @@ class DetailedStatus extends ImmutablePureComponent { ); mediaIcons.push('picture-o'); } - } else if (status.get('card')) { + } else if (!status.get('quote') && status.get('card')) { media.push(); mediaIcons.push('link'); } From 0275843c844ec303888bcd0f53543394828ff448 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 10 Aug 2024 23:04:06 -0700 Subject: [PATCH 32/59] add quote option to detailed statuses (cherry picked from commit d23cd8da00a393cb276b07ba9412051bec363a47) --- .../glitch/features/status/components/action_bar.jsx | 7 +++++++ .../status/containers/detailed_status_container.js | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx index 3377019bdc8a9d..fdf0a6da84ea5c 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx @@ -36,6 +36,7 @@ const messages = defineMessages({ reply: { id: 'status.reply', defaultMessage: 'Reply' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, + quote: { id: 'status.quote', defaultMessage: 'Quote' }, cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favorite' }, @@ -65,6 +66,7 @@ class ActionBar extends PureComponent { onReblog: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired, onBookmark: PropTypes.func.isRequired, + onQuote: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, onEdit: PropTypes.func.isRequired, onDirect: PropTypes.func.isRequired, @@ -94,6 +96,10 @@ class ActionBar extends PureComponent { this.props.onBookmark(this.props.status, e); }; + handleQuoteClick = () => { + this.props.onQuote(this.props.status); + }; + handleDeleteClick = () => { this.props.onDelete(this.props.status); }; @@ -247,6 +253,7 @@ class ActionBar extends PureComponent {
+
diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js index 34bde2fc6ed66b..c0f3a157b85f7e 100644 --- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js +++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js @@ -6,6 +6,7 @@ import { showAlertForError } from '../../../actions/alerts'; import { initBlockModal } from '../../../actions/blocks'; import { replyCompose, + quoteCompose, mentionCompose, directCompose, } from '../../../actions/compose'; @@ -52,6 +53,17 @@ const mapDispatchToProps = (dispatch) => ({ }); }, + onQuote (status, router) { + dispatch((_, getState) => { + let state = getState(); + if (state.getIn(['compose', 'text']).trim().length !== 0) { + dispatch(openModal({ modalType: 'CONFIRM_QUOTE', modalProps: { status } })); + } else { + dispatch(quoteCompose(status, router)); + } + }); + }, + onReblog (status, e) { dispatch(toggleReblog(status.get('id'), e.shiftKey)); }, From ec8f5d70afd6fcc44adfb3f1f38b15a4db3b8af9 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 04:26:52 +0000 Subject: [PATCH 33/59] activitypub: fix context extensions for quote_uri (cherry picked from commit 7efe4bc5d3df3bff0f57df15e76ee441b99858b5) --- app/helpers/context_helper.rb | 2 +- app/serializers/activitypub/note_serializer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 40f7e67f5d10db..7517e5cf0d5ae5 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -42,7 +42,7 @@ module ContextHelper 'cipherText' => 'toot:cipherText', }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, - quoteUri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' }, + quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' }, }.freeze def full_context diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 0b4d3344905f55..28122393c23ada 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -3,7 +3,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer include FormattingHelper - context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :direct_message + context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :direct_message, :quote_uri attributes :id, :type, :summary, :in_reply_to, :published, :url, From 93e42dcf605607ba85f5fd17315b6659e7d771fc Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 26 Dec 2022 04:38:21 +0000 Subject: [PATCH 34/59] add reply and quote icons to the reply/quote indicators so people know what is going on (cherry picked from commit df07456f51d0894a3dd35255c72edf8f44e6256d) --- .../glitch/features/compose/components/quote_indicator.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/features/compose/components/quote_indicator.jsx b/app/javascript/flavours/glitch/features/compose/components/quote_indicator.jsx index bba63e03bc9c87..e4c74e7467a933 100644 --- a/app/javascript/flavours/glitch/features/compose/components/quote_indicator.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/quote_indicator.jsx @@ -62,7 +62,7 @@ class QuoteIndicator extends ImmutablePureComponent { inverted /> {account && ( Date: Mon, 26 Dec 2022 04:38:32 +0000 Subject: [PATCH 35/59] delete obsolete console.log statements (cherry picked from commit c4253c32a0dc46a106e84fe9fdc5f96f699680b6) From 64d51e127c3796de086f067bc3061d74510a85a4 Mon Sep 17 00:00:00 2001 From: Robert Gerus Date: Wed, 28 Dec 2022 01:30:27 +0100 Subject: [PATCH 36/59] status: turn quote author/text into links to original profile/post (cherry picked from commit 5025713c5aceae4e3e6894cc6dab713c00dcf371) --- .../flavours/glitch/components/status_content.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index 01504782d7cebf..fbab8b58767e21 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -393,11 +393,14 @@ class StatusContent extends PureComponent { id='quote-right' aria-hidden='true' key='icon-quote-right' /> - + + + - ); From 7d90e94baf1e854950415cf179cf63862829cdb5 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 23:06:41 -0700 Subject: [PATCH 37/59] mimic 5d0ed011915acf294b2f63a01ccc8a90aa07b61b --- app/lib/activitypub/activity/create.rb | 6 +++++- app/models/status_edit.rb | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index fb6e7421f1e717..966248e35f32a9 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -435,8 +435,12 @@ def increment_voters_count! end def guess_quote_url - if @object['quoteUrl'].present? + if @object['quoteUri'].present? + @object['quoteUri'] + elsif @object['quoteUrl'].present? @object['quoteUrl'] + elsif @object['quoteURL'].present? + @object['quoteURL'] elsif @object['_misskey_quote'].present? @object['_misskey_quote'] end diff --git a/app/models/status_edit.rb b/app/models/status_edit.rb index 165b5403ec165c..bbf05e9247cf69 100644 --- a/app/models/status_edit.rb +++ b/app/models/status_edit.rb @@ -64,6 +64,8 @@ def ordered_media_attachments end.take(Status::MEDIA_ATTACHMENTS_LIMIT) end + delegate :quote?, to: :status + def proper self end From 27a2225f0f1f053a6ea3d27d77ea389e29de50e9 Mon Sep 17 00:00:00 2001 From: Kouhai Date: Wed, 5 Jul 2023 01:45:19 -0700 Subject: [PATCH 38/59] th: run haml-lint on quote haml --- app/views/statuses/_quote_status.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/statuses/_quote_status.html.haml b/app/views/statuses/_quote_status.html.haml index 6fdaa9a84f2c02..1fd04563bc631d 100644 --- a/app/views/statuses/_quote_status.html.haml +++ b/app/views/statuses/_quote_status.html.haml @@ -1,4 +1,4 @@ -.status.quote-status{ dataurl: ActivityPub::TagManager.instance.url_for(status) } +.status.quote-status{ dataurl: ActivityPub::TagManager.instance.url_for(status) } = 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 @@ -13,7 +13,7 @@ .status__content.emojify< - if status.spoiler_text? - %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< + %p{ style: ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< %span.p-summary> #{Formatter.instance.format_spoiler(status)}  %button.status__content__spoiler-link= t('statuses.show_more') .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}" } @@ -22,14 +22,14 @@ - if !status.media_attachments.empty? - if status.media_attachments.first.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, quote: true do + = 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, quote: true do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - elsif status.media_attachments.first.audio? - audio = status.media_attachments.first = react_component :audio, src: audio.file.url(:original), height: 60, alt: audio.description, duration: audio.file.meta.dig(:original, :duration) do = 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, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }, quote: true do + = 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.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }, quote: true do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - elsif status.preview_card = react_component :card, maxDescription: 10, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json, quote: true From 00029684cd8d1d30a8105cfcbff160f01e083325 Mon Sep 17 00:00:00 2001 From: Kouhai Date: Thu, 6 Jul 2023 22:33:05 -0700 Subject: [PATCH 39/59] th: fix js quote issue --- app/javascript/flavours/glitch/actions/importer/normalizer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index 8cda0edaea91b6..c1192bea2bd609 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -79,9 +79,9 @@ export function normalizeStatus(status, normalOldStatus, settings) { const quote_spoilerText = status.quote.spoiler_text || ''; const quote_searchContent = [quote_spoilerText, status.quote.content].join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); - const quote_emojiMap = makeEmojiMap(normalStatus.quote); + const quote_emojiMap = makeEmojiMap(normalStatus.quote.emojis); - const quote_account_emojiMap = makeEmojiMap(status.quote.account); + const quote_account_emojiMap = makeEmojiMap(status.quote.account.emojis); const displayName = normalStatus.quote.account.display_name.length === 0 ? normalStatus.quote.account.username : normalStatus.quote.account.display_name; normalStatus.quote.account.display_name_html = emojify(escapeTextContentForBrowser(displayName), quote_account_emojiMap); normalStatus.quote.search_index = domParser.parseFromString(quote_searchContent, 'text/html').documentElement.textContent; From 8c47b24ff121ef99136da61673bc844359e53a3a Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 23:21:19 -0700 Subject: [PATCH 40/59] th: glitch: fix quote-toot icon --- .../glitch/components/status_action_bar.jsx | 18 +++++++++++++++++- .../glitch/components/status_content.jsx | 4 +++- .../features/status/components/action_bar.jsx | 3 ++- .../400-24px/format_quote-fill.svg | 1 + .../material-icons/400-24px/format_quote.svg | 1 + 5 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 app/javascript/material-icons/400-24px/format_quote-fill.svg create mode 100644 app/javascript/material-icons/400-24px/format_quote.svg diff --git a/app/javascript/flavours/glitch/components/status_action_bar.jsx b/app/javascript/flavours/glitch/components/status_action_bar.jsx index ce289d84b538b7..2994b5d68ca4dc 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar.jsx @@ -10,6 +10,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; +import FormatQuoteIcon from '@/material-icons/400-24px/format_quote-fill.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; @@ -230,6 +231,7 @@ class StatusActionBar extends ImmutablePureComponent { let menu = []; let reblogIcon = 'retweet'; + let quoteIcon = 'quote-right'; let replyIcon; let replyIconComponent; let replyTitle; @@ -312,6 +314,7 @@ class StatusActionBar extends ImmutablePureComponent { const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private'; let reblogTitle, reblogIconComponent; + let quoteTitle, quoteIconComponent; if (status.get('reblogged')) { reblogTitle = intl.formatMessage(messages.cancel_reblog_private); @@ -327,6 +330,19 @@ class StatusActionBar extends ImmutablePureComponent { reblogIconComponent = RepeatDisabledIcon; } + // quotes + if (publicStatus) { + quoteTitle = intl.formatMessage(messages.quote); + quoteIconComponent = FormatQuoteIcon; + } else if (reblogPrivate) { + quoteTitle = intl.formatMessage(messages.reblog_private); + quoteIconComponent = FormatQuoteIcon; + } else { + quoteTitle = intl.formatMessage(messages.cannot_reblog); + quoteIconComponent = FormatQuoteIcon; + } + + const filterButton = this.props.onFilter && ( ); @@ -343,7 +359,7 @@ class StatusActionBar extends ImmutablePureComponent { obfuscateCount /> - + diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index fbab8b58767e21..efa98e5fbfbfcb 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -9,6 +9,7 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; +import QuoteIcon from '@/material-icons/400-24px/format_quote-fill.svg?react'; import Icon from 'flavours/glitch/components/icon'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; @@ -392,7 +393,8 @@ class StatusContent extends PureComponent { fixedWidth id='quote-right' aria-hidden='true' - key='icon-quote-right' /> + key='icon-quote-right' + iconComponent={QuoteIcon} /> diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx index fdf0a6da84ea5c..a86396a49cc386 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx @@ -9,6 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; +import QuoteIcon from '@/material-icons/400-24px/format_quote-fill.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; @@ -253,7 +254,7 @@ class ActionBar extends PureComponent {

-
+
diff --git a/app/javascript/material-icons/400-24px/format_quote-fill.svg b/app/javascript/material-icons/400-24px/format_quote-fill.svg new file mode 100644 index 00000000000000..f4afa3ed17ff63 --- /dev/null +++ b/app/javascript/material-icons/400-24px/format_quote-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/format_quote.svg b/app/javascript/material-icons/400-24px/format_quote.svg new file mode 100644 index 00000000000000..c354385ea93845 --- /dev/null +++ b/app/javascript/material-icons/400-24px/format_quote.svg @@ -0,0 +1 @@ + \ No newline at end of file From 06b656fbcca4813df91f637446f353f5cf4b5e59 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 2 Feb 2024 21:14:01 -0800 Subject: [PATCH 41/59] th: glitch: status: fix quote icon on quotes --- app/javascript/flavours/glitch/components/status_content.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index efa98e5fbfbfcb..e88ecacf7fc084 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -391,10 +391,9 @@ class StatusContent extends PureComponent {
From c24511c598a03b776602cdd8f814eedee0a5ee18 Mon Sep 17 00:00:00 2001 From: Kouhai Date: Sat, 3 Feb 2024 14:38:33 -0800 Subject: [PATCH 42/59] th: fiddle with quote style --- .../flavours/glitch/styles/rich_text.scss | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/javascript/flavours/glitch/styles/rich_text.scss b/app/javascript/flavours/glitch/styles/rich_text.scss index 054eed32522961..7f032cbf46b453 100644 --- a/app/javascript/flavours/glitch/styles/rich_text.scss +++ b/app/javascript/flavours/glitch/styles/rich_text.scss @@ -101,4 +101,21 @@ .status__quote { padding-bottom: 0.5em; + pointer-events: none; + + // HACK: adjust quote icon alignment & color icon for emph + .quote-display-name > .icon { + margin-left: -4px; + + // hopefully this doesn't break user css + margin-bottom: -6px; + + // needs to match below: &:is(blockquote) + color: $highlight-text-color; + } + + // see below: .status_quote|&:is(blockquote) + &:is(blockquote) { + border-left-color: $highlight-text-color; + } } From 1522507a63ff0646ecb2c7d7f9c4be5b3ef67151 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 23:37:29 -0700 Subject: [PATCH 43/59] rubocop --- db/migrate/20221224204906_add_quote_id_to_statuses.rb | 2 ++ db/migrate/20221224220348_add_index_to_statuses_quote_id.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/db/migrate/20221224204906_add_quote_id_to_statuses.rb b/db/migrate/20221224204906_add_quote_id_to_statuses.rb index b01f6520a2e99f..847b4904d2e36f 100644 --- a/db/migrate/20221224204906_add_quote_id_to_statuses.rb +++ b/db/migrate/20221224204906_add_quote_id_to_statuses.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AddQuoteIdToStatuses < ActiveRecord::Migration[6.1] def change add_column :statuses, :quote_id, :bigint, null: true, default: nil diff --git a/db/migrate/20221224220348_add_index_to_statuses_quote_id.rb b/db/migrate/20221224220348_add_index_to_statuses_quote_id.rb index 2a51daf883fa55..db4ff4039ef8d8 100644 --- a/db/migrate/20221224220348_add_index_to_statuses_quote_id.rb +++ b/db/migrate/20221224220348_add_index_to_statuses_quote_id.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AddIndexToStatusesQuoteId < ActiveRecord::Migration[6.1] disable_ddl_transaction! From 54e721be733a94c728138b11c63244fb5f67ad23 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 23:40:09 -0700 Subject: [PATCH 44/59] js and css lint --- app/javascript/flavours/glitch/actions/compose.js | 4 ++-- .../features/compose/containers/quote_indicator_container.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index c09755f932efe2..f1898fddf75ff7 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -166,13 +166,13 @@ export function quoteCompose(status, router) { router.push('/publish'); } }; -}; +} export function cancelQuoteCompose() { return { type: COMPOSE_QUOTE_CANCEL, }; -}; +} export function resetCompose() { return { diff --git a/app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js b/app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js index 9421d69dd8aa2f..dee8d8c5ce60b5 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/quote_indicator_container.js @@ -1,5 +1,7 @@ import { connect } from 'react-redux'; + import { cancelQuoteCompose } from 'flavours/glitch/actions/compose'; + import QuoteIndicator from '../components/quote_indicator'; const makeMapStateToProps = () => { From 2abef98b98b1a2959d1d8fa76ac7ae06fa9ecf20 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 10 Aug 2024 23:05:31 -0700 Subject: [PATCH 45/59] i18n lint --- app/javascript/flavours/glitch/locales/en.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/locales/en.json b/app/javascript/flavours/glitch/locales/en.json index d56a80a5288ad1..3cf1256effffa4 100644 --- a/app/javascript/flavours/glitch/locales/en.json +++ b/app/javascript/flavours/glitch/locales/en.json @@ -34,6 +34,8 @@ "confirmations.missing_media_description.confirm": "Send anyway", "confirmations.missing_media_description.edit": "Edit media", "confirmations.missing_media_description.message": "At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.", + "confirmations.quote.confirm": "Quote", + "confirmations.quote.message": "Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?", "direct.group_by_conversations": "Group by conversation", "endorsed_accounts_editor.endorsed_accounts": "Featured accounts", "favourite_modal.combo": "You can press {combo} to skip this next time", @@ -68,6 +70,7 @@ "notifications.column_settings.filter_bar.show_bar": "Show filter bar", "notifications.marked_clear": "Clear selected notifications", "notifications.marked_clear_confirmation": "Are you sure you want to permanently clear all selected notifications?", + "quote_indicator.cancel": "Cancel", "settings.always_show_spoilers_field": "Always enable the Content Warning field", "settings.auto_collapse": "Automatic collapsing", "settings.auto_collapse_all": "Everything", @@ -156,5 +159,6 @@ "status.in_reply_to": "This toot is a reply", "status.is_poll": "This toot is a poll", "status.local_only": "Only visible from your instance", - "status.uncollapse": "Uncollapse" + "status.uncollapse": "Uncollapse", + "status.quote": "Quote" } From 929285001f52b38be42c0be6b7e62cd55e201c49 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 23:47:36 -0700 Subject: [PATCH 46/59] haml lint and updating to treehouses current version --- app/views/statuses/_detailed_status.html.haml | 2 +- app/views/statuses/_quote_status.html.haml | 42 ++++++++++--------- app/views/statuses/_simple_status.html.haml | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 32790e3bf2c92e..4d22cda3299bbb 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -16,7 +16,7 @@ = account_action_button(status.account) - if status.quote? - = render partial: "statuses/quote_status", locals: {status: status.quote} + = render partial: 'statuses/quote_status', locals: { status: status.quote } .status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? diff --git a/app/views/statuses/_quote_status.html.haml b/app/views/statuses/_quote_status.html.haml index 1fd04563bc631d..e6338b9bfdc889 100644 --- a/app/views/statuses/_quote_status.html.haml +++ b/app/views/statuses/_quote_status.html.haml @@ -1,35 +1,37 @@ .status.quote-status{ dataurl: ActivityPub::TagManager.instance.url_for(status) } - = link_to ActivityPub::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 noreferrer' do .status__avatar %div - = image_tag status.account.avatar_static_url, width: 18, height: 18, alt: '', class: 'u-photo account__avatar' + - if prefers_autoplay? + = image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar' + - else + = image_tag status.account.avatar_static_url, alt: '', class: 'u-photo account__avatar' %span.display-name %bdi - %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true) + %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: prefers_autoplay?)   %span.display-name__account = acct(status.account) = fa_icon('lock') if status.account.locked? - .status__content.emojify< + .status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< + - if status.spoiler_text? - %p{ style: ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< - %span.p-summary> #{Formatter.instance.format_spoiler(status)}  + %p< + %span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}  %button.status__content__spoiler-link= t('statuses.show_more') - .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}" } - = Formatter.instance.format_in_quote(status, custom_emojify: true) + .e-content{ lang: status.language }< + = prerender_custom_emojis(status_content_format(status), status.emojis) + + - if status.preloadable_poll + = render_poll_component(status) - - if !status.media_attachments.empty? - - if status.media_attachments.first.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, quote: true do - = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - - elsif status.media_attachments.first.audio? - - audio = status.media_attachments.first - = react_component :audio, src: audio.file.url(:original), height: 60, alt: audio.description, duration: audio.file.meta.dig(:original, :duration) do - = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } + - if !status.ordered_media_attachments.empty? + - if status.ordered_media_attachments.first.video? + = render_video_component(status, width: 610, height: 343) + - elsif status.ordered_media_attachments.first.audio? + = render_audio_component(status, width: 610, height: 343) - 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, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }, quote: true do - = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } + = render_media_gallery_component(status, height: 343) - elsif status.preview_card - = react_component :card, maxDescription: 10, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json, quote: true + = render_card_component(status) diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index 650a629223ded1..1c24d080f8fe07 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -29,7 +29,7 @@ = fa_icon('lock') if status.account.locked? - if status.quote? - = render partial: "statuses/quote_status", locals: {status: status.quote} + = render partial: 'statuses/quote_status', locals: { status: status.quote } .status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? From d6c307adf12e511eff80f8c91261202b9a273aaa Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 29 Jun 2024 23:56:47 -0700 Subject: [PATCH 47/59] fix build --- app/javascript/flavours/glitch/components/status_content.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index e88ecacf7fc084..7a51974115dccc 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -10,7 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import QuoteIcon from '@/material-icons/400-24px/format_quote-fill.svg?react'; -import Icon from 'flavours/glitch/components/icon'; +import { Icon } from 'flavours/glitch/components/icon'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; From d5dd792c58eb36e1536bdcb3dbf79915e66c7576 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sun, 30 Jun 2024 00:04:19 -0700 Subject: [PATCH 48/59] actually a few more branches that should be tested --- .github/workflows/test-ruby.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index fcfeed5fbad7b3..c21fecce9de67a 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -6,6 +6,7 @@ on: branches: - 'main' - 'stable-*' + - 'merge-upstream' pull_request: env: From 7034beda7c57e555282001ee77f52603f7288c3b Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sun, 30 Jun 2024 00:09:47 -0700 Subject: [PATCH 49/59] fix recursive quote foreign key --- app/models/status.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/status.rb b/app/models/status.rb index be2b0a847e14ae..a3d73e230a747d 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -67,7 +67,7 @@ class Status < ApplicationRecord with_options class_name: 'Status', optional: true do belongs_to :thread, foreign_key: 'in_reply_to_id', inverse_of: :replies belongs_to :reblog, foreign_key: 'reblog_of_id', inverse_of: :reblogs - belongs_to :quote, inverse_of: :quote + belongs_to :quote, inverse_of: :quoted end has_many :favourites, inverse_of: :status, dependent: :destroy @@ -191,6 +191,7 @@ class Status < ApplicationRecord :conversation, :status_stat, :preloadable_poll, + preview_cards_status: [:preview_card], account: [:account_stat, :user], active_mentions: { account: :account_stat }, ] From 057f57ae5d92efa8dcf26080db68386e64bbd99a Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sun, 30 Jun 2024 01:30:43 -0700 Subject: [PATCH 50/59] minor merge bugs - working version --- .../flavours/glitch/components/status_action_bar.jsx | 2 +- app/javascript/flavours/glitch/features/status/index.jsx | 4 ++-- app/models/status.rb | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/javascript/flavours/glitch/components/status_action_bar.jsx b/app/javascript/flavours/glitch/components/status_action_bar.jsx index 2994b5d68ca4dc..c4f125b1794d16 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar.jsx @@ -144,7 +144,7 @@ class StatusActionBar extends ImmutablePureComponent { }; handleQuoteClick = () => { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { this.props.onQuote(this.props.status, this.context.router.history); diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index c234ee65dee97d..8a884f0e5fe88f 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -311,11 +311,11 @@ class Status extends ImmutablePureComponent { }; handleQuoteClick = (status) => { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const { dispatch } = this.props; if (signedIn) { - dispatch(quoteCompose(status, this.context.router.history)); + dispatch(quoteCompose(status, this.props.history)); } else { dispatch(openModal('INTERACTION', { type: 'reply', diff --git a/app/models/status.rb b/app/models/status.rb index a3d73e230a747d..781b96d9cb594b 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -186,7 +186,6 @@ class Status < ApplicationRecord quote: [ :application, :tags, - :preview_cards, :media_attachments, :conversation, :status_stat, From 040b84e49c5eb69d232102e640cbf03f5fb1b34e Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 27 Jul 2024 21:36:27 -0700 Subject: [PATCH 51/59] fix i18n --- app/javascript/flavours/glitch/locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/flavours/glitch/locales/en.json b/app/javascript/flavours/glitch/locales/en.json index 3cf1256effffa4..95763d6ee17173 100644 --- a/app/javascript/flavours/glitch/locales/en.json +++ b/app/javascript/flavours/glitch/locales/en.json @@ -159,6 +159,6 @@ "status.in_reply_to": "This toot is a reply", "status.is_poll": "This toot is a poll", "status.local_only": "Only visible from your instance", - "status.uncollapse": "Uncollapse", - "status.quote": "Quote" + "status.quote": "Quote", + "status.uncollapse": "Uncollapse" } From 1088276ce5fecbf04ca97bf8d9d4995f559fc8e3 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 27 Jul 2024 23:24:16 -0700 Subject: [PATCH 52/59] mention OP when quoting --- app/services/process_mentions_service.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 1c4c7805f1423e..350b544899f04d 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -18,6 +18,7 @@ def call(status, save_records: true) Status.transaction do scan_text! + identify_quote! assign_mentions! end end @@ -65,6 +66,25 @@ def scan_text! @status.save! if @save_records end + def identify_quote! + if @status.quote_id? + # There's got to be a better way to get an account from a status id... + quoted_status = Status.find(@status.quote_id) + mentioned_account = Account.find(quoted_status.account_id) + + # mimic the tail of scan_text since we're not sure what is relevant there + return if mention_undeliverable?(mentioned_account) || mentioned_account&.unavailable? + + mention = @previous_mentions.find { |x| x.account_id == mentioned_account.id } + mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id } + mention ||= @status.mentions.new(account: mentioned_account) + + @current_mentions << mention + + "@#{mentioned_account.acct}" + end + end + def assign_mentions! # Make sure we never mention blocked accounts unless @current_mentions.empty? From d366e207bb5e6667c1e0fd90e75f508316156451 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sun, 28 Jul 2024 00:37:01 -0700 Subject: [PATCH 53/59] move quote to bottom, separate into different component --- .../glitch/components/quote_content.jsx | 47 +++++++++++++++++++ .../glitch/components/status_content.jsx | 42 +++++------------ .../flavours/glitch/styles/rich_text.scss | 5 +- app/views/statuses/_detailed_status.html.haml | 6 +-- 4 files changed, 65 insertions(+), 35 deletions(-) create mode 100644 app/javascript/flavours/glitch/components/quote_content.jsx diff --git a/app/javascript/flavours/glitch/components/quote_content.jsx b/app/javascript/flavours/glitch/components/quote_content.jsx new file mode 100644 index 00000000000000..c0d9264d1c2307 --- /dev/null +++ b/app/javascript/flavours/glitch/components/quote_content.jsx @@ -0,0 +1,47 @@ +import PropTypes from "prop-types"; + +import ImmutablePropTypes from "react-immutable-proptypes"; + +import QuoteIcon from "@/material-icons/400-24px/format_quote-fill.svg"; +import {Icon} from "flavours/glitch/components/icon"; + + +const QuoteContent = ({ + quoteStatus, + handleAccountClick +}) => { + let quoteStatusContent = { __html: quoteStatus.get('contentHtml') }; + let quoteStatusAccount = quoteStatus.get('account'); + let quoteStatusDisplayName = { __html: quoteStatusAccount.get('display_name_html') }; + + return ( + + ); +}; + +QuoteContent.propTypes = { + quoteStatus: ImmutablePropTypes.map.isRequired, + handleAccountClick: PropTypes.func.isRequired, +}; + +export default QuoteContent; diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index 7a51974115dccc..57a31da22dab25 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -9,13 +9,12 @@ import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import QuoteIcon from '@/material-icons/400-24px/format_quote-fill.svg?react'; -import { Icon } from 'flavours/glitch/components/icon'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import { Permalink } from './permalink'; +import QuoteContent from "./quote_content"; import StatusExpandButton from './status_expand_button'; const textMatchesTarget = (text, origin, host) => { @@ -376,34 +375,15 @@ class StatusContent extends PureComponent { ); - let quote = ''; - - if (status.get('quote', null) !== null) { - let quoteStatus = status.get('quote'); - let quoteStatusContent = { __html: quoteStatus.get('contentHtml') }; - let quoteStatusAccount = quoteStatus.get('account'); - let quoteStatusDisplayName = { __html: quoteStatusAccount.get('display_name_html') }; + let quoteStatus = status.get('quote', null); + let quote = null; + if (quoteStatus !== null) { quote = ( - + ); } @@ -442,7 +422,6 @@ class StatusContent extends PureComponent { {mentionsPlaceholder}
- {quote}
+ {quote} {extraMedia}
@@ -468,7 +448,6 @@ class StatusContent extends PureComponent { onMouseUp={this.handleMouseUp} tabIndex={0} > - {quote}
{translateButton} {media} + {quote} {extraMedia}
); @@ -490,7 +470,6 @@ class StatusContent extends PureComponent { className='status__content' tabIndex={0} > - {quote}
{translateButton} {media} + {quote} {extraMedia}
); diff --git a/app/javascript/flavours/glitch/styles/rich_text.scss b/app/javascript/flavours/glitch/styles/rich_text.scss index 7f032cbf46b453..f14e573915c35d 100644 --- a/app/javascript/flavours/glitch/styles/rich_text.scss +++ b/app/javascript/flavours/glitch/styles/rich_text.scss @@ -100,8 +100,11 @@ } .status__quote { - padding-bottom: 0.5em; + margin-top: 0.5em; pointer-events: none; + border: 1px solid var(--background-border-color); + border-radius: 8px; + padding: 8px; // HACK: adjust quote icon alignment & color icon for emph .quote-display-name > .icon { diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 4d22cda3299bbb..98ffe0d53b7862 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -15,9 +15,6 @@ = account_action_button(status.account) - - if status.quote? - = render partial: 'statuses/quote_status', locals: { status: status.quote } - .status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? %p< @@ -29,6 +26,9 @@ - if status.preloadable_poll = render_poll_component(status) + - if status.quote? + = render partial: 'statuses/quote_status', locals: { status: status.quote } + - if !status.ordered_media_attachments.empty? - if status.ordered_media_attachments.first.video? = render_video_component(status, width: 670, height: 380, detailed: true) From 32c875311491494f81042642f4464b4c22f5f775 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sun, 28 Jul 2024 00:45:40 -0700 Subject: [PATCH 54/59] no idea what it does but it mimics other behavior and makes the tests pass --- app/javascript/flavours/glitch/components/quote_content.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/components/quote_content.jsx b/app/javascript/flavours/glitch/components/quote_content.jsx index c0d9264d1c2307..9cb5d572f28f8d 100644 --- a/app/javascript/flavours/glitch/components/quote_content.jsx +++ b/app/javascript/flavours/glitch/components/quote_content.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import ImmutablePropTypes from "react-immutable-proptypes"; -import QuoteIcon from "@/material-icons/400-24px/format_quote-fill.svg"; +import QuoteIcon from "@/material-icons/400-24px/format_quote-fill.svg?react"; import {Icon} from "flavours/glitch/components/icon"; From be23faa83a01f05f8336dad67e31f8f6753f6ffc Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sun, 4 Aug 2024 02:10:40 -0700 Subject: [PATCH 55/59] fix links in quotes, separate out CSS --- .../glitch/components/quote_content.jsx | 87 +++++++++++++++---- .../glitch/components/status_content.jsx | 2 +- .../glitch/styles/neuromatchstodon/index.scss | 1 + .../styles/neuromatchstodon/quotes.scss | 79 +++++++++++++++++ .../flavours/glitch/styles/rich_text.scss | 24 ----- 5 files changed, 153 insertions(+), 40 deletions(-) create mode 100644 app/javascript/flavours/glitch/styles/neuromatchstodon/quotes.scss diff --git a/app/javascript/flavours/glitch/components/quote_content.jsx b/app/javascript/flavours/glitch/components/quote_content.jsx index 9cb5d572f28f8d..fa0e903331edcf 100644 --- a/app/javascript/flavours/glitch/components/quote_content.jsx +++ b/app/javascript/flavours/glitch/components/quote_content.jsx @@ -1,38 +1,94 @@ import PropTypes from "prop-types"; +import {useCallback} from "react"; + +import {defineMessages, injectIntl} from "react-intl"; import ImmutablePropTypes from "react-immutable-proptypes"; import QuoteIcon from "@/material-icons/400-24px/format_quote-fill.svg?react"; +import {Avatar} from "flavours/glitch/components/avatar"; import {Icon} from "flavours/glitch/components/icon"; +import {RelativeTimestamp} from "flavours/glitch/components/relative_timestamp"; +const messages = defineMessages({ + edited: {id: 'status.edited', defaultMessage: 'Edited {date}'}, +}); const QuoteContent = ({ quoteStatus, - handleAccountClick + parseClick, + intl }) => { - let quoteStatusContent = { __html: quoteStatus.get('contentHtml') }; + let quoteStatusContent = {__html: quoteStatus.get('contentHtml')}; let quoteStatusAccount = quoteStatus.get('account'); - let quoteStatusDisplayName = { __html: quoteStatusAccount.get('display_name_html') }; + let quoteStatusDisplayName = {__html: quoteStatusAccount.get('display_name_html')}; + const handle = quoteStatus.getIn(['account', 'acct']); + const accountURL = quoteStatus.getIn(['account', 'url']); + const statusID = quoteStatus.get('id'); + const createdAt = quoteStatus.get('created_at'); + const editedAt = quoteStatus.get('edited_at'); + + const handleAccountClick = useCallback((e) => { + parseClick(e, `/@${handle}`); + }, [handle, parseClick]); + + const handleStatusClick = useCallback((e) => { + parseClick(e, `/@${handle}/${statusID}`); + }, [handle, statusID, parseClick]); return ( -
+ @@ -41,7 +97,8 @@ const QuoteContent = ({ QuoteContent.propTypes = { quoteStatus: ImmutablePropTypes.map.isRequired, - handleAccountClick: PropTypes.func.isRequired, + parseClick: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, }; -export default QuoteContent; +export default injectIntl(QuoteContent); diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index 57a31da22dab25..1cb88d3ea8aad2 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -381,7 +381,7 @@ class StatusContent extends PureComponent { if (quoteStatus !== null) { quote = ( ); diff --git a/app/javascript/flavours/glitch/styles/neuromatchstodon/index.scss b/app/javascript/flavours/glitch/styles/neuromatchstodon/index.scss index 9c3885ff72ad8f..3c4179096de599 100644 --- a/app/javascript/flavours/glitch/styles/neuromatchstodon/index.scss +++ b/app/javascript/flavours/glitch/styles/neuromatchstodon/index.scss @@ -1,3 +1,4 @@ @import 'latex'; @import 'bigger_collapsed_statuses'; @import 'better_code_blocks'; +@import 'quotes'; diff --git a/app/javascript/flavours/glitch/styles/neuromatchstodon/quotes.scss b/app/javascript/flavours/glitch/styles/neuromatchstodon/quotes.scss new file mode 100644 index 00000000000000..8d231832f19183 --- /dev/null +++ b/app/javascript/flavours/glitch/styles/neuromatchstodon/quotes.scss @@ -0,0 +1,79 @@ +/* +This stylesheet required more than usual injection of styles into +stuff that can get overridden by upstream. + +That includes stuff in these files, findable by ctrl+f'ing "quote" +unless indicated otherwise. + +- componenents.scss +- contrast/diff.scss +- mastodon-light/diff.scss + + dismissable-banner (L435) + +*/ + +$header-gap: 0.2em; +$header-font-size: 15px; + +.status__quote { + margin-top: 0.5em; + border: 1px solid var(--background-border-color); + border-radius: 8px; + padding: 8px; + + .quote { + &__header { + display: flex; + font-size: $header-font-size; + gap: $header-gap; + } + + &__author { + display: flex; + font-size: $header-font-size; + gap: $header-gap; + + &:hover { + text-decoration: none; + + & .quote__display-name { + text-decoration: underline; + } + } + + // quote icon has its own left padding + svg { + margin-left: -5px; + } + } + + &__spacer { + margin: 0 0.25em; + } + + &__display-name { + &:hover { + text-decoration: underline; + } + + & > .icon { + // needs to match below: &:is(blockquote) + color: $highlight-text-color; + } + } + + &__content-link:hover { + text-decoration: none; + } + } + + .deemphasized { + color: $action-button-color; + text-decoration: none; + } + + // see below: .status_quote|&:is(blockquote) + &:is(blockquote) { + border-left-color: $highlight-text-color; + } +} diff --git a/app/javascript/flavours/glitch/styles/rich_text.scss b/app/javascript/flavours/glitch/styles/rich_text.scss index f14e573915c35d..9bfe8b5079c3b4 100644 --- a/app/javascript/flavours/glitch/styles/rich_text.scss +++ b/app/javascript/flavours/glitch/styles/rich_text.scss @@ -98,27 +98,3 @@ list-style-type: decimal; } } - -.status__quote { - margin-top: 0.5em; - pointer-events: none; - border: 1px solid var(--background-border-color); - border-radius: 8px; - padding: 8px; - - // HACK: adjust quote icon alignment & color icon for emph - .quote-display-name > .icon { - margin-left: -4px; - - // hopefully this doesn't break user css - margin-bottom: -6px; - - // needs to match below: &:is(blockquote) - color: $highlight-text-color; - } - - // see below: .status_quote|&:is(blockquote) - &:is(blockquote) { - border-left-color: $highlight-text-color; - } -} From e1cf667e046ffc6feb7446afda5a285b6f5670ba Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 10 Aug 2024 22:34:49 -0700 Subject: [PATCH 56/59] rm script-src nonce in development bc it prevents relaxing other CSPs for devtools --- config/initializers/content_security_policy.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index 97993f78c88bb1..0b34aadf52506b 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -69,7 +69,11 @@ def sso_host Rails.application.config.content_security_policy_nonce_generator = ->(_request) { SecureRandom.base64(16) } -Rails.application.config.content_security_policy_nonce_directives = %w(style-src script-src) +Rails.application.config.content_security_policy_nonce_directives = if Rails.env.development? + %w(style-src) + else + %w(style-src script-src) + end Rails.application.reloader.to_prepare do PgHero::HomeController.content_security_policy do |p| @@ -93,7 +97,7 @@ def sso_host end LetterOpenerWeb::LettersController.after_action do - request.content_security_policy_nonce_directives = %w(script-src) + request.content_security_policy_nonce_directives = %w() end end end From ffe2066bbd87d92ae119616d8c33868216e168f6 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 10 Aug 2024 23:15:58 -0700 Subject: [PATCH 57/59] fix quote modal to match new upstream style --- .../components/confirmation_modals/index.ts | 2 + .../components/confirmation_modals/quote.tsx | 46 +++++++++++++++++++ .../features/ui/components/modal_root.jsx | 38 +++++++-------- 3 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/quote.tsx diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts index 912c99a393a1f0..3c8430899944b1 100644 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts @@ -6,3 +6,5 @@ export { ConfirmEditStatusModal } from './edit_status'; export { ConfirmUnfollowModal } from './unfollow'; export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; + +export { ConfirmQuoteModal } from './quote'; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/quote.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/quote.tsx new file mode 100644 index 00000000000000..ab8bb419009575 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/quote.tsx @@ -0,0 +1,46 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { quoteCompose } from 'flavours/glitch/actions/compose'; +import type { Status } from 'flavours/glitch/models/status'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + quoteTitle: { + id: 'confirmations.reply.title', + defaultMessage: 'Overwrite post?', + }, + quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' }, + quoteMessage: { + id: 'confirmations.quote.message', + defaultMessage: + 'Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?', + }, +}); + +export const ConfirmQuoteModal: React.FC< + { + status: Status; + } & BaseConfirmationModalProps +> = ({ status, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(quoteCompose(status)); + }, [dispatch, status]); + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx index 64c6b52c31ec3d..45003788eff4ca 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx @@ -38,6 +38,7 @@ import { ConfirmUnfollowModal, ConfirmClearNotificationsModal, ConfirmLogOutModal, + ConfirmQuoteModal, } from './confirmation_modals'; import DeprecatedSettingsModal from './deprecated_settings_modal'; import DoodleModal from './doodle_modal'; @@ -49,31 +50,32 @@ import ModalLoading from './modal_loading'; import VideoModal from './video_modal'; export const MODAL_COMPONENTS = { - 'MEDIA': () => Promise.resolve({ default: MediaModal }), - 'VIDEO': () => Promise.resolve({ default: VideoModal }), - 'AUDIO': () => Promise.resolve({ default: AudioModal }), - 'IMAGE': () => Promise.resolve({ default: ImageModal }), - 'BOOST': () => Promise.resolve({ default: BoostModal }), - 'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }), - 'DOODLE': () => Promise.resolve({ default: DoodleModal }), - 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), - 'CONFIRM_DELETE_STATUS': () => Promise.resolve({ default: ConfirmDeleteStatusModal }), - 'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }), - 'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }), - 'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }), - 'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }), - 'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), - 'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), + 'MEDIA': () => Promise.resolve({default: MediaModal}), + 'VIDEO': () => Promise.resolve({default: VideoModal}), + 'AUDIO': () => Promise.resolve({default: AudioModal}), + 'IMAGE': () => Promise.resolve({default: ImageModal}), + 'BOOST': () => Promise.resolve({default: BoostModal}), + 'FAVOURITE': () => Promise.resolve({default: FavouriteModal}), + 'DOODLE': () => Promise.resolve({default: DoodleModal}), + 'CONFIRM': () => Promise.resolve({default: ConfirmationModal}), + 'CONFIRM_DELETE_STATUS': () => Promise.resolve({default: ConfirmDeleteStatusModal}), + 'CONFIRM_DELETE_LIST': () => Promise.resolve({default: ConfirmDeleteListModal}), + 'CONFIRM_REPLY': () => Promise.resolve({default: ConfirmReplyModal}), + 'CONFIRM_QUOTE': () => Promise.resolve({default: ConfirmQuoteModal}), + 'CONFIRM_EDIT_STATUS': () => Promise.resolve({default: ConfirmEditStatusModal}), + 'CONFIRM_UNFOLLOW': () => Promise.resolve({default: ConfirmUnfollowModal}), + 'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({default: ConfirmClearNotificationsModal}), + 'CONFIRM_LOG_OUT': () => Promise.resolve({default: ConfirmLogOutModal}), 'MUTE': MuteModal, 'BLOCK': BlockModal, 'DOMAIN_BLOCK': DomainBlockModal, 'REPORT': ReportModal, 'SETTINGS': SettingsModal, - 'DEPRECATED_SETTINGS': () => Promise.resolve({ default: DeprecatedSettingsModal }), - 'ACTIONS': () => Promise.resolve({ default: ActionsModal }), + 'DEPRECATED_SETTINGS': () => Promise.resolve({default: DeprecatedSettingsModal}), + 'ACTIONS': () => Promise.resolve({default: ActionsModal}), 'EMBED': EmbedModal, 'LIST_EDITOR': ListEditor, - 'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }), + 'FOCAL_POINT': () => Promise.resolve({default: FocalPointModal}), 'LIST_ADDER': ListAdder, 'PINNED_ACCOUNTS_EDITOR': PinnedAccountsEditor, 'COMPARE_HISTORY': CompareHistoryModal, From bdf152d3c659aeeaf9776f301b1cdde62b059a1b Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sat, 10 Aug 2024 23:21:26 -0700 Subject: [PATCH 58/59] fix quoteCompose action also to match new upstream style --- app/javascript/flavours/glitch/actions/compose.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index f1898fddf75ff7..c4d47812b6e88e 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -155,16 +155,14 @@ export function cancelReplyCompose() { }; } -export function quoteCompose(status, router) { +export function quoteCompose(status) { return (dispatch, getState) => { dispatch({ type: COMPOSE_QUOTE, status: status, }); - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/publish'); - } + ensureComposeIsVisible(getState); }; } From 7dd25918f30d192ef1306ba8450b66d7a5135627 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Sun, 11 Aug 2024 01:31:19 -0700 Subject: [PATCH 59/59] quotable concern stub, i think this is where you put this? --- .../flavours/glitch/api_types/statuses.ts | 1 + .../concerns/status/quotable_concern.rb | 26 +++++++++++++++++++ app/models/status.rb | 1 + app/serializers/rest/status_serializer.rb | 2 ++ 4 files changed, 30 insertions(+) create mode 100644 app/models/concerns/status/quotable_concern.rb diff --git a/app/javascript/flavours/glitch/api_types/statuses.ts b/app/javascript/flavours/glitch/api_types/statuses.ts index c84ecc7afc4266..518bc1abfe349e 100644 --- a/app/javascript/flavours/glitch/api_types/statuses.ts +++ b/app/javascript/flavours/glitch/api_types/statuses.ts @@ -122,4 +122,5 @@ export interface ApiStatusJSON { // glitch-soc additions local_only?: boolean; content_type?: string; + quotable?: boolean; } diff --git a/app/models/concerns/status/quotable_concern.rb b/app/models/concerns/status/quotable_concern.rb new file mode 100644 index 00000000000000..c49ff10d1fbf13 --- /dev/null +++ b/app/models/concerns/status/quotable_concern.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Determine whether a status can be quoted by a given account +module Status::QuotableConcern + extend ActiveSupport::Concern + + included do + def quotable? + if current_user? + !noquote && !filtered && distributable? + else + false + end + end + end + + def noquote + # whether the account has #NoQuote tag in bio + false + end + + def filtered + # whether the account has filtered notifications from us + false + end +end diff --git a/app/models/status.rb b/app/models/status.rb index 781b96d9cb594b..6715943186a3d1 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -37,6 +37,7 @@ class Status < ApplicationRecord include Discard::Model include Paginable include RateLimitable + include Status::QuotableConcern include Status::SafeReblogInsert include Status::SearchConcern include Status::SnapshotConcern diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 3638e871b1c517..573e47b8ea01de 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -17,6 +17,7 @@ class REST::StatusSerializer < ActiveModel::Serializer attribute :pinned, if: :pinnable? attribute :local_only, if: :local? has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user? + attribute :quotable, if: :current_user? attribute :content, unless: :source_requested? attribute :text, if: :source_requested? @@ -35,6 +36,7 @@ class REST::StatusSerializer < ActiveModel::Serializer has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer delegate :local?, to: :object + delegate :quotable?, to: :object def id object.id.to_s