Skip to content

Commit 936be73

Browse files
committed
add support for additional link modes
1 parent e7f70c6 commit 936be73

File tree

9 files changed

+103
-28
lines changed

9 files changed

+103
-28
lines changed

.tool-versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodejs 16.13.2

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
-->
1313
## 0.23.4 (unreleased)
1414

15+
### Features
16+
- Add remote media syntax for links (#127)
17+
1518
### Improvements
1619
- Improve generation of brackets for links (#126)
1720

docs/syntax.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -535,19 +535,24 @@ http://hoge.jp/abc
535535
<h1 id="link">Inline: リンク</h2>
536536

537537
## 形式
538-
silent=false
538+
type='plain'
539539
```
540540
[Misskey.io](https://misskey.io/)
541541
```
542542

543-
silent=true
543+
type='plain' with special characters
544+
```
545+
[#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>)
546+
```
547+
548+
type='silent'
544549
```
545550
?[Misskey.io](https://misskey.io/)
546551
```
547552

548-
Special characters
553+
type='embed'
549554
```
550-
[#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>)
555+
![A cute picture of Ai-chan](https://藍.moe/aiart/1.png)
551556
```
552557

553558
## 詳細
@@ -559,7 +564,7 @@ Special characters
559564
{
560565
type: 'link',
561566
props: {
562-
silent: false,
567+
type: 'plain'
563568
url: {
564569
type: 'url',
565570
props: {

etc/mfm-js.api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function inspect(nodes: MfmNode[], action: (node: MfmNode) => void): void
3838
export const ITALIC: (children: MfmInline[]) => NodeType<'italic'>;
3939

4040
// @public (undocumented)
41-
export const LINK: (silent: boolean, url: MfmUrl, children: MfmInline[]) => NodeType<'link'>;
41+
export const LINK: (type: 'plain' | 'silent' | 'embed', url: MfmUrl, children: MfmInline[]) => NodeType<'link'>;
4242

4343
// @public (undocumented)
4444
export const MATH_BLOCK: (formula: string) => NodeType<'mathBlock'>;
@@ -127,7 +127,7 @@ export type MfmItalic = {
127127
export type MfmLink = {
128128
type: 'link';
129129
props: {
130-
silent: boolean;
130+
type: 'plain' | 'silent' | 'embed';
131131
url: MfmUrl;
132132
};
133133
children: MfmInline[];

src/internal/parser.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ export const language = P.createLanguage({
462462
P.str('.'),
463463
arg.sep(P.str(','), 1),
464464
], 1).map(pairs => {
465-
const result: Args = { };
465+
const result: Args = {};
466466
for (const pair of pairs) {
467467
result[pair.k] = pair.v;
468468
}
@@ -644,7 +644,7 @@ export const language = P.createLanguage({
644644
const closeLabel = P.str(']');
645645
return P.seq([
646646
notLinkLabel,
647-
P.alt([P.str('?['), P.str('[')]),
647+
P.alt([P.str('?['), P.str('!['), P.str('[')]),
648648
P.seq([
649649
P.notMatch(P.alt([closeLabel, newLine])),
650650
nest(labelInline),
@@ -654,10 +654,15 @@ export const language = P.createLanguage({
654654
P.alt([r.urlAlt, r.url]),
655655
P.str(')'),
656656
]).map(result => {
657-
const silent = (result[1] === '?[');
657+
const mapping: {[key: string]: M.MfmLink['props']['type']} = {
658+
'?[': 'silent',
659+
'![': 'embed',
660+
'[': 'plain',
661+
};
662+
const type: M.MfmLink['props']['type'] = mapping[result[1]];
658663
const label = result[2];
659664
const url: M.MfmUrl = result[5];
660-
return M.LINK(silent, url, mergeText(label));
665+
return M.LINK(type, url, mergeText(label));
661666
});
662667
},
663668

src/internal/util.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isMfmBlock, MfmInline, MfmNode, MfmText, TEXT } from '../node';
1+
import { isMfmBlock, MfmInline, MfmNode, MfmText, MfmLink, TEXT } from '../node';
22

33
export function mergeText<T extends MfmNode>(nodes: ((T extends MfmInline ? MfmInline : MfmNode) | string)[]): (T | MfmText)[] {
44
const dest: (T | MfmText)[] = [];
@@ -91,8 +91,12 @@ export function stringifyNode(node: MfmNode): string {
9191
}
9292
}
9393
case 'link': {
94-
const prefix = node.props.silent ? '?' : '';
95-
return `${ prefix }[${ stringifyTree(node.children) }](${ stringifyNode(node.props.url) })`;
94+
const prefixMapping: {[key in MfmLink['props']['type']]: string} = {
95+
'silent': '?',
96+
'embed': '!',
97+
'plain': '',
98+
};
99+
return `${ prefixMapping[node.props.type] }[${ stringifyTree(node.children) }](${ stringifyNode(node.props.url) })`;
96100
}
97101
case 'fn': {
98102
const argFields = Object.keys(node.props.args).map(key => {

src/node.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ export const N_URL = (value: string, brackets?: boolean): NodeType<'url'> => {
156156
export type MfmLink = {
157157
type: 'link';
158158
props: {
159-
silent: boolean;
159+
type: 'plain' | 'silent' | 'embed';
160160
url: MfmUrl;
161161
};
162162
children: MfmInline[];
163163
};
164-
export const LINK = (silent: boolean, url: MfmUrl, children: MfmInline[]): NodeType<'link'> => { return { type: 'link', props: { silent, url }, children }; };
164+
export const LINK = (type: 'plain' | 'silent' | 'embed', url: MfmUrl, children: MfmInline[]): NodeType<'link'> => { return { type: 'link', props: { type, url }, children }; };
165165

166166
export type MfmFn = {
167167
type: 'fn';

test/api.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,21 @@ after`;
142142
assert.strictEqual(mfm.toString(mfm.parse(input)), '?[Ai](https://github.com/syuilo/ai)');
143143
});
144144

145+
test('silent bracket link', () => {
146+
const input = '?[#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>)';
147+
assert.strictEqual(mfm.toString(mfm.parse(input)), '?[#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>)');
148+
});
149+
150+
test('image link', () => {
151+
const input = '![Ai logo](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg)';
152+
assert.strictEqual(mfm.toString(mfm.parse(input)), '![Ai logo](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg)');
153+
});
154+
155+
test('image bracket link', () => {
156+
const input = '![#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>)';
157+
assert.strictEqual(mfm.toString(mfm.parse(input)), '![#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>)');
158+
});
159+
145160
test('fn', () => {
146161
const input = '$[tada Hello]';
147162
assert.strictEqual(mfm.toString(mfm.parse(input)), '$[tada Hello]');

test/parser.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,7 @@ hoge`;
10431043
const input = '[official instance](https://misskey.io/@ai).';
10441044
const output = [
10451045
LINK(
1046-
false,
1046+
'plain',
10471047
N_URL('https://misskey.io/@ai'),
10481048
[TEXT('official instance')]
10491049
),
@@ -1056,7 +1056,7 @@ hoge`;
10561056
const input = '?[official instance](https://misskey.io/@ai).';
10571057
const output = [
10581058
LINK(
1059-
true,
1059+
'silent',
10601060
N_URL('https://misskey.io/@ai'),
10611061
[TEXT('official instance')]
10621062
),
@@ -1069,7 +1069,7 @@ hoge`;
10691069
const input = '[#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>).';
10701070
const output = [
10711071
LINK(
1072-
false,
1072+
'plain',
10731073
N_URL('https://misskey.io/explore/tags/藍ちゃファンクラブ', true),
10741074
[TEXT('#藍ちゃファンクラブ')]
10751075
),
@@ -1086,26 +1086,66 @@ hoge`;
10861086
assert.deepStrictEqual(mfm.parse(input), output);
10871087
});
10881088

1089+
test('embed flag', () => {
1090+
const input = '![image](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg).';
1091+
const output = [
1092+
LINK(
1093+
'embed',
1094+
N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg'),
1095+
[TEXT('image')]
1096+
),
1097+
TEXT('.')
1098+
];
1099+
assert.deepStrictEqual(mfm.parse(input), output);
1100+
});
1101+
1102+
test('with angle brackets silent url', () => {
1103+
const input = '?[image](<https://raw.githubusercontent.com/syuilo/ai/master/ai.svg>).';
1104+
const output = [
1105+
LINK(
1106+
'silent',
1107+
N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg', true),
1108+
[TEXT('image')]
1109+
),
1110+
TEXT('.')
1111+
];
1112+
assert.deepStrictEqual(mfm.parse(input), output);
1113+
});
1114+
1115+
test('with angle brackets embed url', () => {
1116+
const input = '![image](<https://raw.githubusercontent.com/syuilo/ai/master/ai.svg>).';
1117+
const output = [
1118+
LINK(
1119+
'embed',
1120+
N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg', true),
1121+
[TEXT('image')]
1122+
),
1123+
TEXT('.')
1124+
];
1125+
assert.deepStrictEqual(mfm.parse(input), output);
1126+
});
1127+
10891128
describe('cannot nest a url in a link label', () => {
10901129
test('basic', () => {
10911130
const input = 'official instance: [https://misskey.io/@ai](https://misskey.io/@ai).';
10921131
const output = [
10931132
TEXT('official instance: '),
10941133
LINK(
1095-
false,
1134+
'plain',
10961135
N_URL('https://misskey.io/@ai'),
10971136
[TEXT('https://misskey.io/@ai')]
10981137
),
10991138
TEXT('.'),
11001139
];
11011140
assert.deepStrictEqual(mfm.parse(input), output);
11021141
});
1142+
11031143
test('nested', () => {
11041144
const input = 'official instance: [https://misskey.io/@ai**https://misskey.io/@ai**](https://misskey.io/@ai).';
11051145
const output = [
11061146
TEXT('official instance: '),
11071147
LINK(
1108-
false,
1148+
'plain',
11091149
N_URL('https://misskey.io/@ai'),
11101150
[
11111151
TEXT('https://misskey.io/@ai'),
@@ -1126,7 +1166,7 @@ hoge`;
11261166
const output = [
11271167
TEXT('official instance: '),
11281168
LINK(
1129-
false,
1169+
'plain',
11301170
N_URL('https://misskey.io/@ai'),
11311171
[TEXT('[https://misskey.io/@ai')]
11321172
),
@@ -1136,12 +1176,13 @@ hoge`;
11361176
];
11371177
assert.deepStrictEqual(mfm.parse(input), output);
11381178
});
1179+
11391180
test('nested', () => {
11401181
const input = 'official instance: [**[https://misskey.io/@ai](https://misskey.io/@ai)**](https://misskey.io/@ai).';
11411182
const output = [
11421183
TEXT('official instance: '),
11431184
LINK(
1144-
false,
1185+
'plain',
11451186
N_URL('https://misskey.io/@ai'),
11461187
[
11471188
BOLD([
@@ -1159,18 +1200,19 @@ hoge`;
11591200
const input = '[@example](https://example.com)';
11601201
const output = [
11611202
LINK(
1162-
false,
1203+
'plain',
11631204
N_URL('https://example.com'),
11641205
[TEXT('@example')]
11651206
),
11661207
];
11671208
assert.deepStrictEqual(mfm.parse(input), output);
11681209
});
1210+
11691211
test('nested', () => {
11701212
const input = '[@example**@example**](https://example.com)';
11711213
const output = [
11721214
LINK(
1173-
false,
1215+
'plain',
11741216
N_URL('https://example.com'),
11751217
[
11761218
TEXT('@example'),
@@ -1188,7 +1230,7 @@ hoge`;
11881230
const input = '[foo](https://example.com/foo(bar))';
11891231
const output = [
11901232
LINK(
1191-
false,
1233+
'plain',
11921234
N_URL('https://example.com/foo(bar)'),
11931235
[TEXT('foo')]
11941236
),
@@ -1201,7 +1243,7 @@ hoge`;
12011243
const output = [
12021244
TEXT('('),
12031245
LINK(
1204-
false,
1246+
'plain',
12051247
N_URL('https://example.com/foo(bar)'),
12061248
[TEXT('foo')]
12071249
),
@@ -1215,7 +1257,7 @@ hoge`;
12151257
const output = [
12161258
TEXT('[test] foo '),
12171259
LINK(
1218-
false,
1260+
'plain',
12191261
N_URL('https://example.com'),
12201262
[TEXT('bar')]
12211263
),

0 commit comments

Comments
 (0)