From a16299b65adf521b86d37a48010141f4c76d124d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 14 Aug 2023 20:19:05 +0200 Subject: [PATCH] Change hashtags at the end of the post to render out-of-band --- app/helpers/formatting_helper.rb | 2 +- app/lib/text_formatter.rb | 43 +++++++++++++++++++++++++++++++- spec/lib/text_formatter_spec.rb | 35 +++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index 5ab0c8c7ba..72377e2771 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -15,7 +15,7 @@ module FormattingHelper 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) : [])) + html_aware_format(status.text, status.local?, strip_rich_entities: true, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : [])) end def rss_status_content_format(status) diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb index 581ee835b3..f659edfa1b 100644 --- a/app/lib/text_formatter.rb +++ b/app/lib/text_formatter.rb @@ -20,6 +20,7 @@ class TextFormatter # @option options [Boolean] :multiline # @option options [Boolean] :with_domains # @option options [Boolean] :with_rel_me + # @option options [Boolean] :strip_rich_entities # @option options [Array] :preloaded_accounts def initialize(text, options = {}) @text = text @@ -75,16 +76,22 @@ class TextFormatter entity[:indices].first end + interrupt_text_at = detect_hashtag_block if options[:strip_rich_entities] result = +'' last_index = entities.reduce(0) do |index, entity| indices = entity[:indices] + result << h(text[index...indices.first]) + + break interrupt_text_at if interrupt_text_at.present? && indices.first >= interrupt_text_at + result << yield(entity) + indices.last end - result << h(text[last_index..]) + result << h(text[last_index..]) unless interrupt_text_at.present? && last_index >= interrupt_text_at result end @@ -163,4 +170,38 @@ class TextFormatter def preloaded_accounts? preloaded_accounts.present? end + + def detect_hashtag_block + block_begin = nil + block_end = nil + + entities.map.with_index do |entity, i| + next unless entity[:hashtag] + + next_entity = entities[i + 1] + + if !next_entity.nil? && !next_entity[:hashtag] + block_begin = nil + block_end = nil + next + elsif next_entity.nil? + block_begin = entity[:indices].first if block_begin.nil? + block_end = entity[:indices].last + next + end + + entity_end = entity[:indices].last + next_entity_start = next_entity[:indices].first + + if next_entity_start == entity_end + 1 + block_begin = entity[:indices].first if block_begin.nil? + block_end = entity_end + else + block_begin = nil + block_end = nil + end + end + + block_begin if block_begin.present? && block_end.present? && text[block_end..].blank? && text[block_begin - 1] == "\n" + end end diff --git a/spec/lib/text_formatter_spec.rb b/spec/lib/text_formatter_spec.rb index 8b922c018b..72bbb40512 100644 --- a/spec/lib/text_formatter_spec.rb +++ b/spec/lib/text_formatter_spec.rb @@ -4,8 +4,9 @@ require 'rails_helper' RSpec.describe TextFormatter do describe '#to_s' do - subject { described_class.new(text, preloaded_accounts: preloaded_accounts).to_s } + subject { described_class.new(text, strip_rich_entities: strip_rich_entities, preloaded_accounts: preloaded_accounts).to_s } + let(:strip_rich_entities) { false } let(:preloaded_accounts) { nil } context 'when given text containing plain text' do @@ -273,7 +274,7 @@ RSpec.describe TextFormatter do end context 'when given text containing a hashtag' do - let(:text) { '#hashtag' } + let(:text) { 'foo #hashtag' } it 'creates a hashtag link' do expect(subject).to include '/tags/hashtag" class="mention hashtag" rel="tag">#hashtag' @@ -281,7 +282,7 @@ RSpec.describe TextFormatter do end context 'when given text containing a hashtag with Unicode chars' do - let(:text) { '#hashtagタグ' } + let(:text) { 'foo #hashtagタグ' } it 'creates a hashtag link' do expect(subject).to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#hashtagタグ' @@ -311,5 +312,33 @@ RSpec.describe TextFormatter do expect(subject).to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"' end end + + context 'with strip_rich_entities option' do + let(:strip_rich_entities) { true } + + context 'when a hashtag is in the middle of a sentence' do + let(:text) { 'Hello #foo world' } + + it 'keeps the hashtag in place' do + expect(subject).to include 'foo' + end + end + + context 'when a hashtag starts a line but is followed by text' do + let(:text) { '#foo Hello world' } + + it 'keeps the hashtag in place' do + expect(subject).to include 'foo' + end + end + + context 'when a hashtag is on the last line in the text' do + let(:text) { "Hello world\n#foo" } + + it 'strips out the hashtag' do + expect(subject).to_not include 'foo' + end + end + end end end