diff --git a/src/mfm/parser.ts b/src/mfm/parser.ts index 3edb23b602..480d590755 100644 --- a/src/mfm/parser.ts +++ b/src/mfm/parser.ts @@ -112,9 +112,27 @@ const mfm = P.createLanguage({ const text = input.substr(i); const match = text.match(/^#([^\s\.,!\?#]+)/i); if (!match) return P.makeFailure(i, 'not a hashtag'); - if (match[1].match(/^[0-9]+$/)) return P.makeFailure(i, 'not a hashtag'); - if (input[i - 1] != '\n' && input[i - 1] != ' ' && input[i - 1] != null) return P.makeFailure(i, 'require space before "#"'); - return P.makeSuccess(i + match[0].length, makeNode('hashtag', { hashtag: match[1] })); + let hashtag = match[1]; + let pendingBracket = 0; + const end = hashtag.split('').findIndex(char => { + if (char == ')') { + if (pendingBracket > 0) { + pendingBracket--; + return false; + } else { + return true; + } + } else if (char == '(') { + pendingBracket++; + return false; + } else { + return false; + } + }); + if (end > 0) hashtag = hashtag.substr(0, end); + if (hashtag.match(/^[0-9]+$/)) return P.makeFailure(i, 'not a hashtag'); + if (!['\n', ' ', '(', null, undefined].includes(input[i - 1])) return P.makeFailure(i, 'require space before "#"'); + return P.makeSuccess(i + ('#' + hashtag).length, makeNode('hashtag', { hashtag: hashtag })); }), //#endregion diff --git a/test/mfm.ts b/test/mfm.ts index 206105bc56..9956f679bf 100644 --- a/test/mfm.ts +++ b/test/mfm.ts @@ -212,12 +212,37 @@ describe('Text', () => { ], tokens); }); + it('with brackets', () => { + const tokens = analyze('(#foo)'); + assert.deepEqual([ + text('('), + node('hashtag', { hashtag: 'foo' }), + text(')'), + ], tokens); + }); + + it('with brackets (space before)', () => { + const tokens = analyze('(bar #foo)'); + assert.deepEqual([ + text('(bar '), + node('hashtag', { hashtag: 'foo' }), + text(')'), + ], tokens); + }); + it('disallow number only', () => { const tokens = analyze('#123'); assert.deepEqual([ text('#123'), ], tokens); }); + + it('disallow number only (with brackets)', () => { + const tokens = analyze('(#123)'); + assert.deepEqual([ + text('(#123)'), + ], tokens); + }); }); describe('quote', () => {