Skip to content

Commit 0340dce

Browse files
authored
feat(YfmFile): support directive syntax (#503)
1 parent 5186fab commit 0340dce

File tree

7 files changed

+325
-43
lines changed

7 files changed

+325
-43
lines changed

demo/defaults/md-plugins.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ export function getPlugins({
4646
directiveSyntax: directiveSyntax?.mdPluginValueFor('yfmCut'),
4747
}),
4848
deflist,
49-
yfmFile({bundle: false}),
49+
yfmFile({
50+
bundle: false,
51+
directiveSyntax: directiveSyntax?.mdPluginValueFor('yfmFile'),
52+
}),
5053
(md) => md.use(imsize, {enableInlineStyling: true}),
5154
meta,
5255
monospace,

src/extensions/yfm/YfmCut/YfmCut.test.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import dd from 'ts-dedent';
33

44
import {parseDOM} from '../../../../tests/parse-dom';
55
import {createMarkupChecker} from '../../../../tests/sameMarkup';
6+
import {DirectiveContext} from '../../../../tests/utils';
67
import {ExtensionsManager} from '../../../core';
7-
import {DirectiveSyntaxContext, type DirectiveSyntaxOption} from '../../../utils/directive';
88
import {BaseNode, BaseSchemaSpecs} from '../../base/specs';
99
import {
1010
BlockquoteSpecs,
@@ -18,11 +18,6 @@ import {
1818

1919
import {CutAttr, CutNode, YfmCutSpecs} from './YfmCutSpecs';
2020

21-
class DirectiveContext extends DirectiveSyntaxContext {
22-
setOption(option: DirectiveSyntaxOption | undefined) {
23-
this.option = option;
24-
}
25-
}
2621
const directiveContext = new DirectiveContext(undefined);
2722

2823
function buildDeps() {

src/extensions/yfm/YfmFile/YfmFile.test.ts

Lines changed: 213 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
import {builders} from 'prosemirror-test-builder';
2+
import dd from 'ts-dedent';
23

4+
import type {DirectiveSyntaxValue} from '../../../';
35
import {parseDOM} from '../../../../tests/parse-dom';
46
import {createMarkupChecker} from '../../../../tests/sameMarkup';
7+
import {DirectiveContext} from '../../../../tests/utils';
58
import {ExtensionsManager} from '../../../core';
69
import {BaseNode, BaseSchemaSpecs} from '../../base/specs';
710

8-
import {YfmFileSpecs, yfmFileNodeName} from './YfmFileSpecs';
11+
import {YfmFileAttr, YfmFileSpecs, yfmFileNodeName} from './YfmFileSpecs';
912

10-
const {
11-
schema,
12-
markupParser: parser,
13-
serializer,
14-
} = new ExtensionsManager({
15-
extensions: (builder) => builder.use(BaseSchemaSpecs, {}).use(YfmFileSpecs),
16-
}).buildDeps();
13+
function buildDeps(directiveSyntax: DirectiveSyntaxValue | undefined) {
14+
return new ExtensionsManager({
15+
options: {mdOpts: {preset: 'zero'}},
16+
extensions: (builder) => {
17+
builder.context.set('directiveSyntax', new DirectiveContext(directiveSyntax));
18+
builder.use(BaseSchemaSpecs, {}).use(YfmFileSpecs);
19+
},
20+
}).buildDeps();
21+
}
22+
23+
function buildCheckers(directiveSyntax: DirectiveSyntaxValue) {
24+
const {markupParser, serializer} = buildDeps(directiveSyntax);
25+
return createMarkupChecker({parser: markupParser, serializer});
26+
}
27+
28+
const {schema, markupParser: parser, serializer} = buildDeps(undefined);
1729

1830
const {same} = createMarkupChecker({parser, serializer});
1931

@@ -24,13 +36,14 @@ const {doc, p, file} = builders<'doc' | 'p' | 'file'>(schema, {
2436
});
2537

2638
const defaultAttrs = {
27-
href: 'path/to/readme',
28-
download: 'readme.md',
29-
hreflang: null,
30-
referrerpolicy: null,
31-
rel: null,
32-
target: null,
33-
type: null,
39+
[YfmFileAttr.Link]: 'path/to/readme',
40+
[YfmFileAttr.Name]: 'readme.md',
41+
[YfmFileAttr.Lang]: null,
42+
[YfmFileAttr.ReferrerPolicy]: null,
43+
[YfmFileAttr.Rel]: null,
44+
[YfmFileAttr.Target]: null,
45+
[YfmFileAttr.Type]: null,
46+
[YfmFileAttr.Markup]: '{% file ',
3447
};
3548

3649
describe('YFM File extension', () => {
@@ -65,13 +78,14 @@ describe('YFM File extension', () => {
6578
doc(
6679
p(
6780
file({
68-
href: 'path/to/readme',
69-
download: 'readme.md',
70-
hreflang: 'ru',
71-
referrerpolicy: 'origin',
72-
rel: 'help',
73-
target: '_top',
74-
type: 'text/markdown',
81+
[YfmFileAttr.Link]: 'path/to/readme',
82+
[YfmFileAttr.Name]: 'readme.md',
83+
[YfmFileAttr.Lang]: 'ru',
84+
[YfmFileAttr.ReferrerPolicy]: 'origin',
85+
[YfmFileAttr.Rel]: 'help',
86+
[YfmFileAttr.Target]: '_top',
87+
[YfmFileAttr.Type]: 'text/markdown',
88+
[YfmFileAttr.Markup]: '{% file ',
7589
}),
7690
),
7791
),
@@ -93,4 +107,181 @@ describe('YFM File extension', () => {
93107
),
94108
);
95109
});
110+
111+
describe('directiveSyntax', () => {
112+
const MARKUP = {
113+
CurlySyntax: dd`
114+
{% file src="path/to/readme.md" name="README.md" lang="ru" referrerpolicy="origin" rel="help" target="_top" type="text/markdown" %}
115+
`,
116+
117+
DirectiveSyntax: dd`
118+
:file[README.md](path/to/readme.md){referrerpolicy="origin" rel="help" target="_top" type="text/markdown" hreflang="ru"}
119+
`,
120+
};
121+
122+
const ATTRS = {
123+
[YfmFileAttr.Name]: 'README.md',
124+
[YfmFileAttr.Link]: 'path/to/readme.md',
125+
[YfmFileAttr.ReferrerPolicy]: 'origin',
126+
[YfmFileAttr.Rel]: 'help',
127+
[YfmFileAttr.Target]: '_top',
128+
[YfmFileAttr.Type]: 'text/markdown',
129+
[YfmFileAttr.Lang]: 'ru',
130+
};
131+
132+
const PM_DOC = {
133+
CurlyFile: doc(
134+
p(
135+
file({
136+
...ATTRS,
137+
[YfmFileAttr.Markup]: '{% file ',
138+
}),
139+
),
140+
),
141+
UnknownFile: doc(p(file({...ATTRS}))),
142+
DirectiveFile: doc(
143+
p(
144+
file({
145+
...ATTRS,
146+
[YfmFileAttr.Markup]: ':file',
147+
}),
148+
),
149+
),
150+
};
151+
152+
describe('directiveSyntax:disabled', () => {
153+
it('should parse curly syntax', () => {
154+
const {parse} = buildCheckers('disabled');
155+
parse(MARKUP.CurlySyntax, PM_DOC.CurlyFile, {json: true});
156+
});
157+
158+
it('should not parse directive syntax', () => {
159+
const {parse} = buildCheckers('disabled');
160+
parse(MARKUP.DirectiveSyntax, doc(p(MARKUP.DirectiveSyntax)), {json: true});
161+
});
162+
163+
it('should preserve curly syntax', () => {
164+
const {serialize} = buildCheckers('disabled');
165+
serialize(PM_DOC.CurlyFile, MARKUP.CurlySyntax);
166+
});
167+
168+
it('should serialize block with unknown markup to curly syntax', () => {
169+
const {serialize} = buildCheckers('disabled');
170+
serialize(PM_DOC.UnknownFile, MARKUP.CurlySyntax);
171+
});
172+
173+
it('should preserve directive syntax', () => {
174+
const {serialize} = buildCheckers('disabled');
175+
serialize(PM_DOC.DirectiveFile, MARKUP.DirectiveSyntax);
176+
});
177+
});
178+
179+
describe('directiveSyntax:enabled', () => {
180+
it('should parse curly syntax', () => {
181+
const {parse} = buildCheckers('enabled');
182+
parse(MARKUP.CurlySyntax, PM_DOC.CurlyFile, {json: true});
183+
});
184+
185+
it('should parse directive syntax', () => {
186+
const {parse} = buildCheckers('enabled');
187+
parse(MARKUP.DirectiveSyntax, PM_DOC.DirectiveFile, {json: true});
188+
});
189+
190+
it('should preserve curly syntax', () => {
191+
const {serialize} = buildCheckers('enabled');
192+
serialize(PM_DOC.CurlyFile, MARKUP.CurlySyntax);
193+
});
194+
195+
it('should serialize block with unknown markup to curly syntax', () => {
196+
const {serialize} = buildCheckers('enabled');
197+
serialize(PM_DOC.UnknownFile, MARKUP.CurlySyntax);
198+
});
199+
200+
it('should preserve directive syntax', () => {
201+
const {serialize} = buildCheckers('enabled');
202+
serialize(PM_DOC.DirectiveFile, MARKUP.DirectiveSyntax);
203+
});
204+
});
205+
206+
describe('directiveSyntax:preserve', () => {
207+
it('should parse curly syntax', () => {
208+
const {parse} = buildCheckers('preserve');
209+
parse(MARKUP.CurlySyntax, PM_DOC.CurlyFile, {json: true});
210+
});
211+
212+
it('should parse directive syntax', () => {
213+
const {parse} = buildCheckers('preserve');
214+
parse(MARKUP.DirectiveSyntax, PM_DOC.DirectiveFile, {json: true});
215+
});
216+
217+
it('should preserve curly syntax', () => {
218+
const {serialize} = buildCheckers('preserve');
219+
serialize(PM_DOC.CurlyFile, MARKUP.CurlySyntax);
220+
});
221+
222+
it('should serialize block with unknown markup to directive syntax', () => {
223+
const {serialize} = buildCheckers('preserve');
224+
serialize(PM_DOC.UnknownFile, MARKUP.DirectiveSyntax);
225+
});
226+
227+
it('should preserve directive syntax', () => {
228+
const {serialize} = buildCheckers('preserve');
229+
serialize(PM_DOC.DirectiveFile, MARKUP.DirectiveSyntax);
230+
});
231+
});
232+
233+
describe('directiveSyntax:overwrite', () => {
234+
it('should parse curly syntax', () => {
235+
const {parse} = buildCheckers('overwrite');
236+
parse(MARKUP.CurlySyntax, PM_DOC.CurlyFile, {json: true});
237+
});
238+
239+
it('should parse directive syntax', () => {
240+
const {parse} = buildCheckers('overwrite');
241+
parse(MARKUP.DirectiveSyntax, PM_DOC.DirectiveFile, {json: true});
242+
});
243+
244+
it('should overwrite curly to directive syntax', () => {
245+
const {serialize} = buildCheckers('overwrite');
246+
serialize(PM_DOC.CurlyFile, MARKUP.DirectiveSyntax);
247+
});
248+
249+
it('should serialize block with unknown markup to directive syntax', () => {
250+
const {serialize} = buildCheckers('overwrite');
251+
serialize(PM_DOC.UnknownFile, MARKUP.DirectiveSyntax);
252+
});
253+
254+
it('should preserve directive syntax', () => {
255+
const {serialize} = buildCheckers('overwrite');
256+
serialize(PM_DOC.DirectiveFile, MARKUP.DirectiveSyntax);
257+
});
258+
});
259+
260+
describe('directiveSyntax:only', () => {
261+
it('should not parse curly syntax', () => {
262+
const {parse} = buildCheckers('only');
263+
parse(MARKUP.CurlySyntax, doc(p(MARKUP.CurlySyntax)), {json: true});
264+
});
265+
266+
it('should parse directive syntax', () => {
267+
const {parse} = buildCheckers('only');
268+
parse(MARKUP.DirectiveSyntax, PM_DOC.DirectiveFile, {json: true});
269+
});
270+
271+
it('should overwrite curly to directive syntax', () => {
272+
const {serialize} = buildCheckers('only');
273+
serialize(PM_DOC.CurlyFile, MARKUP.DirectiveSyntax);
274+
});
275+
276+
it('should serialize block with unknown markup to directive syntax', () => {
277+
const {serialize} = buildCheckers('only');
278+
serialize(PM_DOC.UnknownFile, MARKUP.DirectiveSyntax);
279+
});
280+
281+
it('should preserve directive syntax', () => {
282+
const {serialize} = buildCheckers('only');
283+
serialize(PM_DOC.DirectiveFile, MARKUP.DirectiveSyntax);
284+
});
285+
});
286+
});
96287
});

src/extensions/yfm/YfmFile/YfmFileSpecs/const.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,32 @@ import {
33
FILE_REQUIRED_ATTRS,
44
FILE_TOKEN,
55
FILE_TO_LINK_ATTRS_MAP,
6+
FileHtmlAttr,
67
FileSpecialAttr,
78
} from '@diplodoc/file-extension';
89
import type {AttributeSpec} from 'prosemirror-model';
910

1011
export const yfmFileNodeName = FILE_TOKEN;
1112

13+
export const YfmFileAttr = {
14+
Markup: 'data-markup',
15+
Name: FileHtmlAttr.Download,
16+
Link: FileHtmlAttr.Href,
17+
ReferrerPolicy: FileHtmlAttr.ReferrerPolicy,
18+
Rel: FileHtmlAttr.Rel,
19+
Target: FileHtmlAttr.Target,
20+
Type: FileHtmlAttr.Type,
21+
Lang: FileHtmlAttr.HrefLang,
22+
} as const;
23+
24+
export const YFM_FILE_DIRECTIVE_ATTRS: readonly string[] = [
25+
YfmFileAttr.ReferrerPolicy,
26+
YfmFileAttr.Rel,
27+
YfmFileAttr.Target,
28+
YfmFileAttr.Type,
29+
YfmFileAttr.Lang,
30+
];
31+
1232
export const KNOWN_ATTRS: readonly string[] = FILE_KNOWN_ATTRS.map((attrName) => {
1333
if (attrName in FILE_TO_LINK_ATTRS_MAP)
1434
return FILE_TO_LINK_ATTRS_MAP[attrName as FileSpecialAttr];
@@ -21,7 +41,9 @@ export const REQUIRED_ATTRS = FILE_REQUIRED_ATTRS.map((attrName) => {
2141
return attrName;
2242
});
2343

24-
export const fileNodeAttrsSpec: Record<string, AttributeSpec> = {};
44+
export const fileNodeAttrsSpec: Record<string, AttributeSpec> = {
45+
[YfmFileAttr.Markup]: {default: null},
46+
};
2547
for (const attrName of KNOWN_ATTRS) {
2648
const attrSpec: AttributeSpec = (fileNodeAttrsSpec[attrName] = {});
2749
if (!REQUIRED_ATTRS.includes(attrName)) {

0 commit comments

Comments
 (0)