Skip to content

Commit ea39d78

Browse files
committed
add support for additional link modes
1 parent 88daccc commit ea39d78

File tree

10 files changed

+96
-28
lines changed

10 files changed

+96
-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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
### Bugfixes
1111
1212
-->
13+
## 0.24.0
14+
15+
### Features
16+
- Add Remote media syntax for links (#127)
17+
1318
## 0.23.1
1419

1520
### Improvements

docs/syntax.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -535,20 +535,29 @@ 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

553+
type='embed'
554+
```
555+
![Misskey.io](https://misskey.io/)
556+
```
557+
548558
Special characters
549559
```
550560
[#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>)
551-
```
552561
553562
## 詳細
554563
- リンクラベルには再度InlineParserを適用する。ただし、リンクラベルではURL、リンク、メンションは使用できない。
@@ -559,7 +568,7 @@ Special characters
559568
{
560569
type: 'link',
561570
props: {
562-
silent: false,
571+
type: 'plain'
563572
url: {
564573
type: 'url',
565574
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[];

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mfm-js",
3-
"version": "0.23.1",
3+
"version": "0.24.0",
44
"description": "An MFM parser implementation with TypeScript",
55
"main": "./built/index.js",
66
"types": "./built/index.d.ts",

src/internal/parser.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ export const language = P.createLanguage({
465465
P.str('.'),
466466
arg.sep(P.str(','), 1),
467467
], 1).map(pairs => {
468-
const result: Args = { };
468+
const result: Args = {};
469469
for (const pair of pairs) {
470470
result[pair.k] = pair.v;
471471
}
@@ -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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ after`;
142142
assert.strictEqual(mfm.toString(mfm.parse(input)), '?[Ai](https://github.com/syuilo/ai)');
143143
});
144144

145+
it('image link', () => {
146+
const input = '![Ai logo](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg)';
147+
assert.strictEqual(mfm.toString(mfm.parse(input)), '![Ai logo](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg)');
148+
});
149+
145150
it('fn', () => {
146151
const input = '$[tada Hello]';
147152
assert.strictEqual(mfm.toString(mfm.parse(input)), '$[tada Hello]');

test/parser.ts

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ hoge`;
10231023
const input = '[official instance](https://misskey.io/@ai).';
10241024
const output = [
10251025
LINK(
1026-
false,
1026+
'plain',
10271027
N_URL('https://misskey.io/@ai'),
10281028
[TEXT('official instance')]
10291029
),
@@ -1036,7 +1036,7 @@ hoge`;
10361036
const input = '?[official instance](https://misskey.io/@ai).';
10371037
const output = [
10381038
LINK(
1039-
true,
1039+
'silent',
10401040
N_URL('https://misskey.io/@ai'),
10411041
[TEXT('official instance')]
10421042
),
@@ -1049,7 +1049,7 @@ hoge`;
10491049
const input = '[#藍ちゃファンクラブ](<https://misskey.io/explore/tags/藍ちゃファンクラブ>).';
10501050
const output = [
10511051
LINK(
1052-
false,
1052+
'plain',
10531053
N_URL('https://misskey.io/explore/tags/藍ちゃファンクラブ', true),
10541054
[TEXT('#藍ちゃファンクラブ')]
10551055
),
@@ -1058,13 +1058,52 @@ hoge`;
10581058
assert.deepStrictEqual(mfm.parse(input), output);
10591059
});
10601060

1061+
it('embed flag', () => {
1062+
const input = '![image](https://raw.githubusercontent.com/syuilo/ai/master/ai.svg).';
1063+
const output = [
1064+
LINK(
1065+
'embed',
1066+
N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg'),
1067+
[TEXT('image')]
1068+
),
1069+
TEXT('.')
1070+
];
1071+
assert.deepStrictEqual(mfm.parse(input), output);
1072+
});
1073+
1074+
it('with angle brackets silent url', () => {
1075+
const input = '?[image](<https://raw.githubusercontent.com/syuilo/ai/master/ai.svg>).';
1076+
const output = [
1077+
LINK(
1078+
'silent',
1079+
N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg', true),
1080+
[TEXT('image')]
1081+
),
1082+
TEXT('.')
1083+
];
1084+
assert.deepStrictEqual(mfm.parse(input), output);
1085+
});
1086+
1087+
it('with angle brackets embed url', () => {
1088+
const input = '![image](<https://raw.githubusercontent.com/syuilo/ai/master/ai.svg>).';
1089+
const output = [
1090+
LINK(
1091+
'embed',
1092+
N_URL('https://raw.githubusercontent.com/syuilo/ai/master/ai.svg', true),
1093+
[TEXT('image')]
1094+
),
1095+
TEXT('.')
1096+
];
1097+
assert.deepStrictEqual(mfm.parse(input), output);
1098+
});
1099+
10611100
describe('cannot nest a url in a link label', () => {
10621101
it('basic', () => {
10631102
const input = 'official instance: [https://misskey.io/@ai](https://misskey.io/@ai).';
10641103
const output = [
10651104
TEXT('official instance: '),
10661105
LINK(
1067-
false,
1106+
'plain',
10681107
N_URL('https://misskey.io/@ai'),
10691108
[TEXT('https://misskey.io/@ai')]
10701109
),
@@ -1077,7 +1116,7 @@ hoge`;
10771116
const output = [
10781117
TEXT('official instance: '),
10791118
LINK(
1080-
false,
1119+
'plain',
10811120
N_URL('https://misskey.io/@ai'),
10821121
[
10831122
TEXT('https://misskey.io/@ai'),
@@ -1098,7 +1137,7 @@ hoge`;
10981137
const output = [
10991138
TEXT('official instance: '),
11001139
LINK(
1101-
false,
1140+
'plain',
11021141
N_URL('https://misskey.io/@ai'),
11031142
[TEXT('[https://misskey.io/@ai')]
11041143
),
@@ -1113,7 +1152,7 @@ hoge`;
11131152
const output = [
11141153
TEXT('official instance: '),
11151154
LINK(
1116-
false,
1155+
'plain',
11171156
N_URL('https://misskey.io/@ai'),
11181157
[
11191158
BOLD([
@@ -1131,7 +1170,7 @@ hoge`;
11311170
const input = '[@example](https://example.com)';
11321171
const output = [
11331172
LINK(
1134-
false,
1173+
'plain',
11351174
N_URL('https://example.com'),
11361175
[TEXT('@example')]
11371176
),
@@ -1142,7 +1181,7 @@ hoge`;
11421181
const input = '[@example**@example**](https://example.com)';
11431182
const output = [
11441183
LINK(
1145-
false,
1184+
'plain',
11461185
N_URL('https://example.com'),
11471186
[
11481187
TEXT('@example'),
@@ -1160,7 +1199,7 @@ hoge`;
11601199
const input = '[foo](https://example.com/foo(bar))';
11611200
const output = [
11621201
LINK(
1163-
false,
1202+
'plain',
11641203
N_URL('https://example.com/foo(bar)'),
11651204
[TEXT('foo')]
11661205
),
@@ -1173,7 +1212,7 @@ hoge`;
11731212
const output = [
11741213
TEXT('('),
11751214
LINK(
1176-
false,
1215+
'plain',
11771216
N_URL('https://example.com/foo(bar)'),
11781217
[TEXT('foo')]
11791218
),
@@ -1187,7 +1226,7 @@ hoge`;
11871226
const output = [
11881227
TEXT('[test] foo '),
11891228
LINK(
1190-
false,
1229+
'plain',
11911230
N_URL('https://example.com'),
11921231
[TEXT('bar')]
11931232
),

0 commit comments

Comments
 (0)