Fix hashtags not being picked up when full-width hash sign is used
Some checks are pending
Chromatic / Run Chromatic (push) Waiting to run

This commit is contained in:
Eugen Rochko 2025-09-13 03:36:26 +02:00
parent 06803422da
commit 1f80082f6d
7 changed files with 27 additions and 10 deletions

View File

@ -620,6 +620,7 @@ export function fetchComposeSuggestions(token) {
fetchComposeSuggestionsEmojis(dispatch, getState, token); fetchComposeSuggestionsEmojis(dispatch, getState, token);
break; break;
case '#': case '#':
case '':
fetchComposeSuggestionsTags(dispatch, getState, token); fetchComposeSuggestionsTags(dispatch, getState, token);
break; break;
default: default:
@ -661,11 +662,11 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
dispatch(useEmoji(suggestion)); dispatch(useEmoji(suggestion));
} else if (suggestion.type === 'hashtag') { } else if (suggestion.type === 'hashtag') {
completion = `#${suggestion.name}`; completion = suggestion.name.slice(token.length - 1);
startPosition = position - 1; startPosition = position + token.length;
} else if (suggestion.type === 'account') { } else if (suggestion.type === 'account') {
completion = getState().getIn(['accounts', suggestion.id, 'acct']); completion = `@${getState().getIn(['accounts', suggestion.id, 'acct'])}`;
startPosition = position; 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 // 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
@ -725,7 +726,7 @@ function insertIntoTagHistory(recognizedTags, text) {
// complicated because of new normalization rules, it's no longer just // complicated because of new normalization rules, it's no longer just
// a case sensitivity issue // a case sensitivity issue
const names = recognizedTags.map(tag => { 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) { if (matches && matches.length > 0) {
return matches[0].slice(1); return matches[0].slice(1);

View File

@ -61,7 +61,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
static defaultProps = { static defaultProps = {
autoFocus: true, autoFocus: true,
searchTokens: ['@', ':', '#'], searchTokens: ['@', '', ':', '#', ''],
}; };
state = { state = {

View File

@ -25,7 +25,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
word = str.slice(left, right + 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]; return [null, null];
} }

View File

@ -54,7 +54,7 @@ module Extractor
end end
def extract_hashtags_with_indices(text, _options = {}) def extract_hashtags_with_indices(text, _options = {})
return [] unless text&.index('#') return [] unless text&.index(/[#]/)
possible_entries = [] possible_entries = []

View File

@ -41,7 +41,7 @@ class Tag < ApplicationRecord
HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze
HASHTAG_RE = %r{(?<![=/)\p{Alnum}])#(#{HASHTAG_NAME_PAT})} HASHTAG_RE = %r{(?<![=/)\p{Alnum}])[#|](#{HASHTAG_NAME_PAT})}
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/ HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/

View File

@ -35,12 +35,24 @@ RSpec.describe Extractor do
end end
describe 'extract_hashtags_with_indices' do describe 'extract_hashtags_with_indices' do
it 'returns an empty array if it does not have #' do it 'returns an empty array if it does not have # or ' do
text = 'a string without hash sign' text = 'a string without hash sign'
extracted = described_class.extract_hashtags_with_indices(text) extracted = described_class.extract_hashtags_with_indices(text)
expect(extracted).to eq [] expect(extracted).to eq []
end end
it 'returns hashtags preceded by an ASCII hash' do
text = 'hello #world'
extracted = described_class.extract_hashtags_with_indices(text)
expect(extracted).to eq [{ hashtag: 'world', indices: [6, 12] }]
end
it 'returns hashtags preceded by a full-width hash' do
text = 'hello world'
extracted = described_class.extract_hashtags_with_indices(text)
expect(extracted).to eq [{ hashtag: 'world', indices: [6, 12] }]
end
it 'does not exclude normal hash text before ://' do it 'does not exclude normal hash text before ://' do
text = '#hashtag://' text = '#hashtag://'
extracted = described_class.extract_hashtags_with_indices(text) extracted = described_class.extract_hashtags_with_indices(text)

View File

@ -84,6 +84,10 @@ RSpec.describe Tag do
expect(subject.match('this is #').to_s).to eq '#' expect(subject.match('this is #').to_s).to eq '#'
end end
it 'matches ' do
expect(subject.match('this is ').to_s).to eq ''
end
it 'matches digits at the start' do it 'matches digits at the start' do
expect(subject.match('hello #3d').to_s).to eq '#3d' expect(subject.match('hello #3d').to_s).to eq '#3d'
end end