diff --git a/CHANGELOG.md b/CHANGELOG.md index c140bb6..424f203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ ### Bugfixes --> +## 0.24.1 + +### Features +- URL node with silence with `?` + +### Improvements + +### Changes + +### Bugfixes ## 0.24.0 ### Features diff --git a/etc/mfm-js.api.md b/etc/mfm-js.api.md index 6792d39..50fe414 100644 --- a/etc/mfm-js.api.md +++ b/etc/mfm-js.api.md @@ -230,12 +230,13 @@ export type MfmUrl = { props: { url: string; brackets?: boolean; + silent?: boolean; }; children?: []; }; // @public (undocumented) -export const N_URL: (value: string, brackets?: boolean) => NodeType<'url'>; +export const N_URL: (value: string, brackets?: boolean, silent?: boolean) => NodeType<'url'>; // @public (undocumented) export type NodeType = T extends 'quote' ? MfmQuote : T extends 'search' ? MfmSearch : T extends 'blockCode' ? MfmCodeBlock : T extends 'mathBlock' ? MfmMathBlock : T extends 'center' ? MfmCenter : T extends 'unicodeEmoji' ? MfmUnicodeEmoji : T extends 'emojiCode' ? MfmEmojiCode : T extends 'bold' ? MfmBold : T extends 'small' ? MfmSmall : T extends 'italic' ? MfmItalic : T extends 'strike' ? MfmStrike : T extends 'inlineCode' ? MfmInlineCode : T extends 'mathInline' ? MfmMathInline : T extends 'mention' ? MfmMention : T extends 'hashtag' ? MfmHashtag : T extends 'url' ? MfmUrl : T extends 'link' ? MfmLink : T extends 'fn' ? MfmFn : T extends 'plain' ? MfmPlain : T extends 'text' ? MfmText : never; diff --git a/src/internal/parser.ts b/src/internal/parser.ts index d64b122..4bc3dd4 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -746,7 +746,7 @@ export const language = P.createLanguage({ }, urlAlt: () => { - const open = P.str('<'); + const open = P.alt([P.str('?<'), P.str('<')]); const close = P.str('>'); const parser = P.seq( notLinkLabel, @@ -760,8 +760,9 @@ export const language = P.createLanguage({ if (!result.success) { return P.failure(); } - const text = result.value.slice(1, (result.value.length - 1)); - return P.success(result.index, M.N_URL(text, true)); + const silent = result.value.startsWith('?'); + const text = silent ? result.value.slice(2, (result.value.length - 1)) : result.value.slice(1, (result.value.length - 1)); + return P.success(result.index, M.N_URL(text, true, silent)); }); }, diff --git a/src/node.ts b/src/node.ts index 2f00438..5d6ec1f 100644 --- a/src/node.ts +++ b/src/node.ts @@ -144,12 +144,14 @@ export type MfmUrl = { props: { url: string; brackets?: boolean; + silent?: boolean; }; children?: []; }; -export const N_URL = (value: string, brackets?: boolean): NodeType<'url'> => { +export const N_URL = (value: string, brackets?: boolean, silent?: boolean): NodeType<'url'> => { const node: MfmUrl = { type: 'url', props: { url: value } }; if (brackets) node.props.brackets = brackets; + if (silent) node.props.silent = silent; return node; }; diff --git a/test/parser.ts b/test/parser.ts index 852699c..029e157 100644 --- a/test/parser.ts +++ b/test/parser.ts @@ -1029,6 +1029,22 @@ hoge`; assert.deepStrictEqual(mfm.parse(input), output); }); + test('match non-ascii characters contained url with angle brackets with silent', () => { + const input = '?'; + const output = [ + N_URL('https://大石泉すき.example.com', true, true), + ]; + assert.deepStrictEqual(mfm.parse(input), output); + }); + + test('match ascii characters contained url with angle brackets with silent', () => { + const input = '?'; + const output = [ + N_URL('https://example.com', true, true), + ]; + assert.deepStrictEqual(mfm.parse(input), output); + }); + test('prevent xss', () => { const input = 'javascript:foo'; const output = [