From e7f70c69d029fb2e7a5cea1711f934bd58d029fe Mon Sep 17 00:00:00 2001 From: shibao Date: Sun, 5 Feb 2023 11:08:49 -0500 Subject: [PATCH 1/2] Use MfmUrl AST in MfmLink AST --- CHANGELOG.md | 4 ++ docs/syntax.md | 33 ++++++++---- etc/mfm-js.api.md | 4 +- package.json | 2 +- src/internal/parser.ts | 2 +- src/internal/util.ts | 2 +- src/node.ts | 4 +- test/api.ts | 7 ++- test/parser.ts | 118 ++++++++++++++++++++++++++--------------- 9 files changed, 113 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 263361d..e4887d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ ### Bugfixes --> +## 0.23.4 (unreleased) + +### Improvements +- Improve generation of brackets for links (#126) ## 0.23.3 - tweak fn parsing diff --git a/docs/syntax.md b/docs/syntax.md index 29ae576..763ed72 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -225,10 +225,10 @@ __bold__ - 内容には再度InlineParserを適用する。 - 内容を空にすることはできない。 -構文1,3のみ: +構文1,3のみ: - 内容にはすべての文字、改行が使用できる。 -構文2のみ: +構文2のみ: - 内容には`[a-z0-9 \t]i`にマッチする文字が使用できる。 ## ノード @@ -289,11 +289,11 @@ _italic_ - 内容には再度InlineParserを適用する。 - 内容を空にすることはできない。 -構文1のみ: +構文1のみ: - 内容にはすべての文字、改行が使用できる。 -構文2,3のみ: -※1つ目の`*`と`_`を開始記号と呼ぶ。 +構文2,3のみ: +※1つ目の`*`と`_`を開始記号と呼ぶ。 - 内容には`[a-z0-9 \t]i`にマッチする文字が使用できる。 - 開始記号の前の文字が`[a-z0-9]i`に一致しない時にイタリック文字として判定される。 @@ -326,10 +326,10 @@ _italic_ - 内容には再度InlineParserを適用する。 - 内容を空にすることはできない。 -構文1のみ: +構文1のみ: - 内容には`~`、改行以外の文字を使用できる。 -構文2のみ: +構文2のみ: - 内容にはすべての文字、改行が使用できる。 ## ノード @@ -488,12 +488,12 @@ http://hoge.jp/abc ``` ## 詳細 -構文1のみ: +構文1のみ: - 内容には`[.,a-z0-9_/:%#@$&?!~=+-]i`にマッチする文字を使用できる。 - 内容には対になっている括弧を使用できる。対象: `( )` `[ ]` - `.`や`,`は最後の文字にできない。 -構文2のみ: +構文2のみ: - 内容には改行、スペース以外の文字を使用できる。 ## ノード @@ -545,6 +545,11 @@ silent=true ?[Misskey.io](https://misskey.io/) ``` +Special characters +``` +[#藍ちゃファンクラブ]() +``` + ## 詳細 - 表示テキストには再度InlineParserを適用する。ただし、表示テキストではURL、リンク、メンションは使用できない。 @@ -555,7 +560,13 @@ silent=true type: 'link', props: { silent: false, - url: 'https://misskey.io/' + url: { + type: 'url', + props: { + url: 'https://misskey.io/@ai', + brackets: false + } + }, }, children: [ { @@ -665,7 +676,7 @@ abc ```js { type: 'text', - props: + props: text: 'abc' } } diff --git a/etc/mfm-js.api.md b/etc/mfm-js.api.md index 6792d39..385e1f9 100644 --- a/etc/mfm-js.api.md +++ b/etc/mfm-js.api.md @@ -38,7 +38,7 @@ export function inspect(nodes: MfmNode[], action: (node: MfmNode) => void): void export const ITALIC: (children: MfmInline[]) => NodeType<'italic'>; // @public (undocumented) -export const LINK: (silent: boolean, url: string, children: MfmInline[]) => NodeType<'link'>; +export const LINK: (silent: boolean, url: MfmUrl, children: MfmInline[]) => NodeType<'link'>; // @public (undocumented) export const MATH_BLOCK: (formula: string) => NodeType<'mathBlock'>; @@ -128,7 +128,7 @@ export type MfmLink = { type: 'link'; props: { silent: boolean; - url: string; + url: MfmUrl; }; children: MfmInline[]; }; diff --git a/package.json b/package.json index a66c7b9..3da31c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mfm-js", - "version": "0.23.3", + "version": "0.23.4", "description": "An MFM parser implementation with TypeScript", "main": "./built/index.js", "types": "./built/index.d.ts", diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 0d19f27..342f5e7 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -657,7 +657,7 @@ export const language = P.createLanguage({ const silent = (result[1] === '?['); const label = result[2]; const url: M.MfmUrl = result[5]; - return M.LINK(silent, url.props.url, mergeText(label)); + return M.LINK(silent, url, mergeText(label)); }); }, diff --git a/src/internal/util.ts b/src/internal/util.ts index 3f0cffc..d043429 100644 --- a/src/internal/util.ts +++ b/src/internal/util.ts @@ -92,7 +92,7 @@ export function stringifyNode(node: MfmNode): string { } case 'link': { const prefix = node.props.silent ? '?' : ''; - return `${ prefix }[${ stringifyTree(node.children) }](${ node.props.url })`; + return `${ prefix }[${ stringifyTree(node.children) }](${ stringifyNode(node.props.url) })`; } case 'fn': { const argFields = Object.keys(node.props.args).map(key => { diff --git a/src/node.ts b/src/node.ts index 2f00438..7942e96 100644 --- a/src/node.ts +++ b/src/node.ts @@ -157,11 +157,11 @@ export type MfmLink = { type: 'link'; props: { silent: boolean; - url: string; + url: MfmUrl; }; children: MfmInline[]; }; -export const LINK = (silent: boolean, url: string, children: MfmInline[]): NodeType<'link'> => { return { type: 'link', props: { silent, url }, children }; }; +export const LINK = (silent: boolean, url: MfmUrl, children: MfmInline[]): NodeType<'link'> => { return { type: 'link', props: { silent, url }, children }; }; export type MfmFn = { type: 'fn'; diff --git a/test/api.ts b/test/api.ts index 25b29e2..0f6bd0b 100644 --- a/test/api.ts +++ b/test/api.ts @@ -28,7 +28,7 @@ after`; test('quote', () => { const input = ` > abc -> +> > 123 `; @@ -132,6 +132,11 @@ after`; assert.strictEqual(mfm.toString(mfm.parse(input)), '[Ai](https://github.com/syuilo/ai)'); }); + test('bracket link', () => { + const input = '[#藍ちゃファンクラブ]()'; + assert.strictEqual(mfm.toString(mfm.parse(input)), '[#藍ちゃファンクラブ]()'); + }); + test('silent link', () => { const input = '?[Ai](https://github.com/syuilo/ai)'; assert.strictEqual(mfm.toString(mfm.parse(input)), '?[Ai](https://github.com/syuilo/ai)'); diff --git a/test/parser.ts b/test/parser.ts index 50ae700..fc2a9d6 100644 --- a/test/parser.ts +++ b/test/parser.ts @@ -1042,9 +1042,11 @@ hoge`; test('basic', () => { const input = '[official instance](https://misskey.io/@ai).'; const output = [ - LINK(false, 'https://misskey.io/@ai', [ - TEXT('official instance') - ]), + LINK( + false, + N_URL('https://misskey.io/@ai'), + [TEXT('official instance')] + ), TEXT('.') ]; assert.deepStrictEqual(mfm.parse(input), output); @@ -1053,20 +1055,24 @@ hoge`; test('silent flag', () => { const input = '?[official instance](https://misskey.io/@ai).'; const output = [ - LINK(true, 'https://misskey.io/@ai', [ - TEXT('official instance') - ]), + LINK( + true, + N_URL('https://misskey.io/@ai'), + [TEXT('official instance')] + ), TEXT('.') ]; assert.deepStrictEqual(mfm.parse(input), output); }); test('with angle brackets url', () => { - const input = '[official instance]().'; + const input = '[#藍ちゃファンクラブ]().'; const output = [ - LINK(false, 'https://misskey.io/@ai', [ - TEXT('official instance') - ]), + LINK( + false, + N_URL('https://misskey.io/explore/tags/藍ちゃファンクラブ', true), + [TEXT('#藍ちゃファンクラブ')] + ), TEXT('.') ]; assert.deepStrictEqual(mfm.parse(input), output); @@ -1085,9 +1091,11 @@ hoge`; const input = 'official instance: [https://misskey.io/@ai](https://misskey.io/@ai).'; const output = [ TEXT('official instance: '), - LINK(false, 'https://misskey.io/@ai', [ - TEXT('https://misskey.io/@ai'), - ]), + LINK( + false, + N_URL('https://misskey.io/@ai'), + [TEXT('https://misskey.io/@ai')] + ), TEXT('.'), ]; assert.deepStrictEqual(mfm.parse(input), output); @@ -1096,12 +1104,16 @@ hoge`; const input = 'official instance: [https://misskey.io/@ai**https://misskey.io/@ai**](https://misskey.io/@ai).'; const output = [ TEXT('official instance: '), - LINK(false, 'https://misskey.io/@ai', [ - TEXT('https://misskey.io/@ai'), - BOLD([ + LINK( + false, + N_URL('https://misskey.io/@ai'), + [ TEXT('https://misskey.io/@ai'), - ]), - ]), + BOLD([ + TEXT('https://misskey.io/@ai'), + ]), + ] + ), TEXT('.'), ]; assert.deepStrictEqual(mfm.parse(input), output); @@ -1113,9 +1125,11 @@ hoge`; const input = 'official instance: [[https://misskey.io/@ai](https://misskey.io/@ai)](https://misskey.io/@ai).'; const output = [ TEXT('official instance: '), - LINK(false, 'https://misskey.io/@ai', [ - TEXT('[https://misskey.io/@ai'), - ]), + LINK( + false, + N_URL('https://misskey.io/@ai'), + [TEXT('[https://misskey.io/@ai')] + ), TEXT(']('), N_URL('https://misskey.io/@ai'), TEXT(').'), @@ -1126,11 +1140,15 @@ hoge`; const input = 'official instance: [**[https://misskey.io/@ai](https://misskey.io/@ai)**](https://misskey.io/@ai).'; const output = [ TEXT('official instance: '), - LINK(false, 'https://misskey.io/@ai', [ - BOLD([ - TEXT('[https://misskey.io/@ai](https://misskey.io/@ai)'), - ]), - ]), + LINK( + false, + N_URL('https://misskey.io/@ai'), + [ + BOLD([ + TEXT('[https://misskey.io/@ai](https://misskey.io/@ai)'), + ]), + ] + ), TEXT('.'), ]; }); @@ -1140,21 +1158,27 @@ hoge`; test('basic', () => { const input = '[@example](https://example.com)'; const output = [ - LINK(false, 'https://example.com', [ - TEXT('@example'), - ]), + LINK( + false, + N_URL('https://example.com'), + [TEXT('@example')] + ), ]; assert.deepStrictEqual(mfm.parse(input), output); }); test('nested', () => { const input = '[@example**@example**](https://example.com)'; const output = [ - LINK(false, 'https://example.com', [ - TEXT('@example'), - BOLD([ + LINK( + false, + N_URL('https://example.com'), + [ TEXT('@example'), - ]), - ]), + BOLD([ + TEXT('@example'), + ]), + ] + ), ]; assert.deepStrictEqual(mfm.parse(input), output); }); @@ -1163,9 +1187,11 @@ hoge`; test('with brackets', () => { const input = '[foo](https://example.com/foo(bar))'; const output = [ - LINK(false, 'https://example.com/foo(bar)', [ - TEXT('foo') - ]), + LINK( + false, + N_URL('https://example.com/foo(bar)'), + [TEXT('foo')] + ), ]; assert.deepStrictEqual(mfm.parse(input), output); }); @@ -1174,9 +1200,11 @@ hoge`; const input = '([foo](https://example.com/foo(bar)))'; const output = [ TEXT('('), - LINK(false, 'https://example.com/foo(bar)', [ - TEXT('foo') - ]), + LINK( + false, + N_URL('https://example.com/foo(bar)'), + [TEXT('foo')] + ), TEXT(')'), ]; assert.deepStrictEqual(mfm.parse(input), output); @@ -1186,9 +1214,11 @@ hoge`; const input = '[test] foo [bar](https://example.com)'; const output = [ TEXT('[test] foo '), - LINK(false, 'https://example.com', [ - TEXT('bar') - ]), + LINK( + false, + N_URL('https://example.com'), + [TEXT('bar')] + ), ]; assert.deepStrictEqual(mfm.parse(input), output); }); @@ -1369,7 +1399,7 @@ hoge`; ]; assert.deepStrictEqual(mfm.parse(input, { nestLimit: 2 }), output); }); - + test('tag', () => { const input = 'abc'; const output = [ From 936be7312076b8c3fff95d4c8f828be68ae6a9d3 Mon Sep 17 00:00:00 2001 From: shibao Date: Sun, 5 Feb 2023 11:20:54 -0500 Subject: [PATCH 2/2] add support for additional link modes --- .tool-versions | 1 + CHANGELOG.md | 3 ++ docs/syntax.md | 15 ++++++---- etc/mfm-js.api.md | 4 +-- src/internal/parser.ts | 13 ++++++--- src/internal/util.ts | 10 +++++-- src/node.ts | 4 +-- test/api.ts | 15 ++++++++++ test/parser.ts | 66 ++++++++++++++++++++++++++++++++++-------- 9 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..1237b21 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 16.13.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index e4887d4..36856b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ --> ## 0.23.4 (unreleased) +### Features +- Add remote media syntax for links (#127) + ### Improvements - Improve generation of brackets for links (#126) diff --git a/docs/syntax.md b/docs/syntax.md index 763ed72..df73525 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -535,19 +535,24 @@ http://hoge.jp/abc

Inline: リンク

## 形式 -silent=false +type='plain' ``` [Misskey.io](https://misskey.io/) ``` -silent=true +type='plain' with special characters +``` +[#藍ちゃファンクラブ]() +``` + +type='silent' ``` ?[Misskey.io](https://misskey.io/) ``` -Special characters +type='embed' ``` -[#藍ちゃファンクラブ]() +![A cute picture of Ai-chan](https://藍.moe/aiart/1.png) ``` ## 詳細 @@ -559,7 +564,7 @@ Special characters { type: 'link', props: { - silent: false, + type: 'plain' url: { type: 'url', props: { diff --git a/etc/mfm-js.api.md b/etc/mfm-js.api.md index 385e1f9..444d604 100644 --- a/etc/mfm-js.api.md +++ b/etc/mfm-js.api.md @@ -38,7 +38,7 @@ export function inspect(nodes: MfmNode[], action: (node: MfmNode) => void): void export const ITALIC: (children: MfmInline[]) => NodeType<'italic'>; // @public (undocumented) -export const LINK: (silent: boolean, url: MfmUrl, children: MfmInline[]) => NodeType<'link'>; +export const LINK: (type: 'plain' | 'silent' | 'embed', url: MfmUrl, children: MfmInline[]) => NodeType<'link'>; // @public (undocumented) export const MATH_BLOCK: (formula: string) => NodeType<'mathBlock'>; @@ -127,7 +127,7 @@ export type MfmItalic = { export type MfmLink = { type: 'link'; props: { - silent: boolean; + type: 'plain' | 'silent' | 'embed'; url: MfmUrl; }; children: MfmInline[]; diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 342f5e7..3385e8b 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -462,7 +462,7 @@ export const language = P.createLanguage({ P.str('.'), arg.sep(P.str(','), 1), ], 1).map(pairs => { - const result: Args = { }; + const result: Args = {}; for (const pair of pairs) { result[pair.k] = pair.v; } @@ -644,7 +644,7 @@ export const language = P.createLanguage({ const closeLabel = P.str(']'); return P.seq([ notLinkLabel, - P.alt([P.str('?['), P.str('[')]), + P.alt([P.str('?['), P.str('!['), P.str('[')]), P.seq([ P.notMatch(P.alt([closeLabel, newLine])), nest(labelInline), @@ -654,10 +654,15 @@ export const language = P.createLanguage({ P.alt([r.urlAlt, r.url]), P.str(')'), ]).map(result => { - const silent = (result[1] === '?['); + const mapping: {[key: string]: M.MfmLink['props']['type']} = { + '?[': 'silent', + '![': 'embed', + '[': 'plain', + }; + const type: M.MfmLink['props']['type'] = mapping[result[1]]; const label = result[2]; const url: M.MfmUrl = result[5]; - return M.LINK(silent, url, mergeText(label)); + return M.LINK(type, url, mergeText(label)); }); }, diff --git a/src/internal/util.ts b/src/internal/util.ts index d043429..079afd3 100644 --- a/src/internal/util.ts +++ b/src/internal/util.ts @@ -1,4 +1,4 @@ -import { isMfmBlock, MfmInline, MfmNode, MfmText, TEXT } from '../node'; +import { isMfmBlock, MfmInline, MfmNode, MfmText, MfmLink, TEXT } from '../node'; export function mergeText(nodes: ((T extends MfmInline ? MfmInline : MfmNode) | string)[]): (T | MfmText)[] { const dest: (T | MfmText)[] = []; @@ -91,8 +91,12 @@ export function stringifyNode(node: MfmNode): string { } } case 'link': { - const prefix = node.props.silent ? '?' : ''; - return `${ prefix }[${ stringifyTree(node.children) }](${ stringifyNode(node.props.url) })`; + const prefixMapping: {[key in MfmLink['props']['type']]: string} = { + 'silent': '?', + 'embed': '!', + 'plain': '', + }; + return `${ prefixMapping[node.props.type] }[${ stringifyTree(node.children) }](${ stringifyNode(node.props.url) })`; } case 'fn': { const argFields = Object.keys(node.props.args).map(key => { diff --git a/src/node.ts b/src/node.ts index 7942e96..042f825 100644 --- a/src/node.ts +++ b/src/node.ts @@ -156,12 +156,12 @@ export const N_URL = (value: string, brackets?: boolean): NodeType<'url'> => { export type MfmLink = { type: 'link'; props: { - silent: boolean; + type: 'plain' | 'silent' | 'embed'; url: MfmUrl; }; children: MfmInline[]; }; -export const LINK = (silent: boolean, url: MfmUrl, children: MfmInline[]): NodeType<'link'> => { return { type: 'link', props: { silent, url }, children }; }; +export const LINK = (type: 'plain' | 'silent' | 'embed', url: MfmUrl, children: MfmInline[]): NodeType<'link'> => { return { type: 'link', props: { type, url }, children }; }; export type MfmFn = { type: 'fn'; diff --git a/test/api.ts b/test/api.ts index 0f6bd0b..3c97202 100644 --- a/test/api.ts +++ b/test/api.ts @@ -142,6 +142,21 @@ after`; assert.strictEqual(mfm.toString(mfm.parse(input)), '?[Ai](https://github.com/syuilo/ai)'); }); + test('silent bracket link', () => { + const input = '?[#藍ちゃファンクラブ]()'; + assert.strictEqual(mfm.toString(mfm.parse(input)), '?[#藍ちゃファンクラブ]()'); + }); + + test('image link', () => { + const input = '![Ai logo](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg)'; + assert.strictEqual(mfm.toString(mfm.parse(input)), '![Ai logo](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg)'); + }); + + test('image bracket link', () => { + const input = '![#藍ちゃファンクラブ]()'; + assert.strictEqual(mfm.toString(mfm.parse(input)), '![#藍ちゃファンクラブ]()'); + }); + test('fn', () => { const input = '$[tada Hello]'; assert.strictEqual(mfm.toString(mfm.parse(input)), '$[tada Hello]'); diff --git a/test/parser.ts b/test/parser.ts index fc2a9d6..b46fb3d 100644 --- a/test/parser.ts +++ b/test/parser.ts @@ -1043,7 +1043,7 @@ hoge`; const input = '[official instance](https://misskey.io/@ai).'; const output = [ LINK( - false, + 'plain', N_URL('https://misskey.io/@ai'), [TEXT('official instance')] ), @@ -1056,7 +1056,7 @@ hoge`; const input = '?[official instance](https://misskey.io/@ai).'; const output = [ LINK( - true, + 'silent', N_URL('https://misskey.io/@ai'), [TEXT('official instance')] ), @@ -1069,7 +1069,7 @@ hoge`; const input = '[#藍ちゃファンクラブ]().'; const output = [ LINK( - false, + 'plain', N_URL('https://misskey.io/explore/tags/藍ちゃファンクラブ', true), [TEXT('#藍ちゃファンクラブ')] ), @@ -1086,13 +1086,52 @@ hoge`; assert.deepStrictEqual(mfm.parse(input), output); }); + test('embed flag', () => { + const input = '![image](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg).'; + const output = [ + LINK( + 'embed', + N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg'), + [TEXT('image')] + ), + TEXT('.') + ]; + assert.deepStrictEqual(mfm.parse(input), output); + }); + + test('with angle brackets silent url', () => { + const input = '?[image]().'; + const output = [ + LINK( + 'silent', + N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg', true), + [TEXT('image')] + ), + TEXT('.') + ]; + assert.deepStrictEqual(mfm.parse(input), output); + }); + + test('with angle brackets embed url', () => { + const input = '![image]().'; + const output = [ + LINK( + 'embed', + N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg', true), + [TEXT('image')] + ), + TEXT('.') + ]; + assert.deepStrictEqual(mfm.parse(input), output); + }); + describe('cannot nest a url in a link label', () => { test('basic', () => { const input = 'official instance: [https://misskey.io/@ai](https://misskey.io/@ai).'; const output = [ TEXT('official instance: '), LINK( - false, + 'plain', N_URL('https://misskey.io/@ai'), [TEXT('https://misskey.io/@ai')] ), @@ -1100,12 +1139,13 @@ hoge`; ]; assert.deepStrictEqual(mfm.parse(input), output); }); + test('nested', () => { const input = 'official instance: [https://misskey.io/@ai**https://misskey.io/@ai**](https://misskey.io/@ai).'; const output = [ TEXT('official instance: '), LINK( - false, + 'plain', N_URL('https://misskey.io/@ai'), [ TEXT('https://misskey.io/@ai'), @@ -1126,7 +1166,7 @@ hoge`; const output = [ TEXT('official instance: '), LINK( - false, + 'plain', N_URL('https://misskey.io/@ai'), [TEXT('[https://misskey.io/@ai')] ), @@ -1136,12 +1176,13 @@ hoge`; ]; assert.deepStrictEqual(mfm.parse(input), output); }); + test('nested', () => { const input = 'official instance: [**[https://misskey.io/@ai](https://misskey.io/@ai)**](https://misskey.io/@ai).'; const output = [ TEXT('official instance: '), LINK( - false, + 'plain', N_URL('https://misskey.io/@ai'), [ BOLD([ @@ -1159,18 +1200,19 @@ hoge`; const input = '[@example](https://example.com)'; const output = [ LINK( - false, + 'plain', N_URL('https://example.com'), [TEXT('@example')] ), ]; assert.deepStrictEqual(mfm.parse(input), output); }); + test('nested', () => { const input = '[@example**@example**](https://example.com)'; const output = [ LINK( - false, + 'plain', N_URL('https://example.com'), [ TEXT('@example'), @@ -1188,7 +1230,7 @@ hoge`; const input = '[foo](https://example.com/foo(bar))'; const output = [ LINK( - false, + 'plain', N_URL('https://example.com/foo(bar)'), [TEXT('foo')] ), @@ -1201,7 +1243,7 @@ hoge`; const output = [ TEXT('('), LINK( - false, + 'plain', N_URL('https://example.com/foo(bar)'), [TEXT('foo')] ), @@ -1215,7 +1257,7 @@ hoge`; const output = [ TEXT('[test] foo '), LINK( - false, + 'plain', N_URL('https://example.com'), [TEXT('bar')] ),