From 76053fb4a9f6ec5a528fbf9232cb3e55a2cc5cc7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 27 Oct 2025 18:18:01 +0100 Subject: [PATCH] Fix hashtags not being picked up when full-width hash sign is used (#36103) Co-authored-by: Claire --- app/javascript/mastodon/actions/compose.js | 11 ++++++----- .../mastodon/components/autosuggest_input.jsx | 2 +- .../mastodon/components/autosuggest_textarea.jsx | 2 +- app/lib/extractor.rb | 2 +- app/models/tag.rb | 2 +- spec/lib/extractor_spec.rb | 14 +++++++++++++- spec/models/tag_spec.rb | 4 ++++ 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index ccb69f0a3d..d6de589e90 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -622,6 +622,7 @@ export function fetchComposeSuggestions(token) { fetchComposeSuggestionsEmojis(dispatch, getState, token); break; case '#': + case '#': fetchComposeSuggestionsTags(dispatch, getState, token); break; default: @@ -663,11 +664,11 @@ export function selectComposeSuggestion(position, token, suggestion, path) { dispatch(useEmoji(suggestion)); } else if (suggestion.type === 'hashtag') { - completion = `#${suggestion.name}`; - startPosition = position - 1; + completion = suggestion.name.slice(token.length - 1); + startPosition = position + token.length; } else if (suggestion.type === 'account') { - completion = getState().getIn(['accounts', suggestion.id, 'acct']); - startPosition = position; + completion = `@${getState().getIn(['accounts', suggestion.id, 'acct'])}`; + startPosition = position - 1; } // We don't want to replace hashtags that vary only in case due to accessibility, but we need to fire off an event so that @@ -727,7 +728,7 @@ function insertIntoTagHistory(recognizedTags, text) { // complicated because of new normalization rules, it's no longer just // a case sensitivity issue const names = recognizedTags.map(tag => { - const matches = text.match(new RegExp(`#${tag.name}`, 'i')); + const matches = text.match(new RegExp(`[##]${tag.name}`, 'i')); if (matches && matches.length > 0) { return matches[0].slice(1); diff --git a/app/javascript/mastodon/components/autosuggest_input.jsx b/app/javascript/mastodon/components/autosuggest_input.jsx index f707a18e1d..267c044215 100644 --- a/app/javascript/mastodon/components/autosuggest_input.jsx +++ b/app/javascript/mastodon/components/autosuggest_input.jsx @@ -61,7 +61,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { static defaultProps = { autoFocus: true, - searchTokens: ['@', ':', '#'], + searchTokens: ['@', '@', ':', '#', '#'], }; state = { diff --git a/app/javascript/mastodon/components/autosuggest_textarea.jsx b/app/javascript/mastodon/components/autosuggest_textarea.jsx index 68cf9e17fc..137bad9b7e 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.jsx +++ b/app/javascript/mastodon/components/autosuggest_textarea.jsx @@ -25,7 +25,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => { word = str.slice(left, right + caretPosition); } - if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) { + if (!word || word.trim().length < 3 || ['@', '@', ':', '#', '#'].indexOf(word[0]) === -1) { return [null, null]; } diff --git a/app/lib/extractor.rb b/app/lib/extractor.rb index 7e647a7587..206d989bf3 100644 --- a/app/lib/extractor.rb +++ b/app/lib/extractor.rb @@ -54,7 +54,7 @@ module Extractor end def extract_hashtags_with_indices(text, _options = {}) - return [] unless text&.index('#') + return [] unless text&.index(/[##]/) possible_entries = [] diff --git a/app/models/tag.rb b/app/models/tag.rb index dff1011112..1514a44005 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -41,7 +41,7 @@ class Tag < ApplicationRecord HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze - HASHTAG_RE = %r{(?