Skip to content

Commit 0e55182

Browse files
committed
feat(plugin): support directive syntax
1 parent 3d2ae52 commit 0e55182

File tree

6 files changed

+119
-16
lines changed

6 files changed

+119
-16
lines changed

package-lock.json

Lines changed: 22 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
"npm-run-all": "^4.1.5",
5252
"typescript": "^5.6.3"
5353
},
54+
"dependencies": {
55+
"@diplodoc/directive": "^0.3.0"
56+
},
5457
"peerDependencies": {
5558
"markdown-it": "^13.0.0"
5659
}

src/plugin/directive.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type {FileOptions} from './plugin';
2+
3+
import {directiveParser, registerInlineDirective} from '@diplodoc/directive';
4+
5+
import {fileRenderer} from './renderer';
6+
import {ENV_FLAG_NAME, FILE_TOKEN, FileClassName, LinkHtmlAttr} from './const';
7+
8+
const ALLOWED_ATTRS: readonly string[] = [
9+
LinkHtmlAttr.ReferrerPolicy,
10+
LinkHtmlAttr.Rel,
11+
LinkHtmlAttr.Target,
12+
LinkHtmlAttr.Type,
13+
LinkHtmlAttr.HrefLang,
14+
];
15+
16+
export const fileDirective: markdownit.PluginWithOptions<FileOptions> = (md, opts = {}) => {
17+
const {fileExtraAttrs} = opts;
18+
19+
fileRenderer(md);
20+
21+
md.use(directiveParser());
22+
23+
registerInlineDirective(md, 'file', (state, params) => {
24+
if (!params.content || !params.dests) {
25+
return false;
26+
}
27+
28+
const filename = params.content.raw;
29+
const filelink = params.dests.link || '';
30+
31+
const token = state.push(FILE_TOKEN, '', 0);
32+
token.block = false;
33+
token.markup = ':file';
34+
token.content = params.content.raw;
35+
36+
token.attrSet('class', FileClassName.Link);
37+
token.attrSet(LinkHtmlAttr.Href, filelink);
38+
token.attrSet(LinkHtmlAttr.Download, filename);
39+
40+
if (params.attrs) {
41+
for (const attrName of ALLOWED_ATTRS) {
42+
if (params.attrs[attrName]) {
43+
token.attrSet(attrName, params.attrs[attrName]);
44+
}
45+
}
46+
}
47+
48+
if (Array.isArray(fileExtraAttrs)) {
49+
for (const [name, value] of fileExtraAttrs) {
50+
token.attrSet(name, value);
51+
}
52+
}
53+
54+
state.env ??= {};
55+
state.env[ENV_FLAG_NAME] = true;
56+
57+
return true;
58+
});
59+
};

src/plugin/plugin.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import type MarkdownIt from 'markdown-it';
2-
31
import {
42
ENV_FLAG_NAME,
53
FILE_TOKEN,
@@ -12,12 +10,15 @@ import {
1210
REQUIRED_ATTRS,
1311
RULE_NAME,
1412
} from './const';
13+
import {fileRenderer} from './renderer';
1514

1615
export type FileOptions = {
1716
fileExtraAttrs?: [string, string][];
1817
};
1918

20-
export const filePlugin: MarkdownIt.PluginWithOptions<FileOptions> = (md, opts) => {
19+
export const filePlugin: markdownit.PluginWithOptions<FileOptions> = (md, opts) => {
20+
fileRenderer(md);
21+
2122
md.inline.ruler.push(RULE_NAME, (state, silent) => {
2223
if (state.src.substring(state.pos, state.pos + PREFIX_LENGTH) !== PREFIX) {
2324
return false;
@@ -82,10 +83,4 @@ export const filePlugin: MarkdownIt.PluginWithOptions<FileOptions> = (md, opts)
8283

8384
return true;
8485
});
85-
86-
md.renderer.rules[FILE_TOKEN] = (tokens, idx, _opts, _env, self) => {
87-
const token = tokens[idx];
88-
const iconHtml = `<span class="${md.utils.escapeHtml(FileClassName.Icon)}"></span>`;
89-
return `<a${self.renderAttrs(token)}>${iconHtml}${md.utils.escapeHtml(token.content)}</a>`;
90-
};
9186
};

src/plugin/renderer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {FILE_TOKEN, FileClassName} from './const';
2+
3+
export const fileRenderer: markdownit.PluginSimple = (md) => {
4+
md.renderer.rules[FILE_TOKEN] = (tokens, idx, _opts, _env, self) => {
5+
const token = tokens[idx];
6+
const iconHtml = `<span class="${md.utils.escapeHtml(FileClassName.Icon)}"></span>`;
7+
return `<a${self.renderAttrs(token)}>${iconHtml}${md.utils.escapeHtml(token.content)}</a>`;
8+
};
9+
};

src/plugin/transform.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import MarkdownIt from 'markdown-it';
33
import {type FileOptions, filePlugin} from './plugin';
44
import {ENV_FLAG_NAME} from './const';
55
import {hidden} from './utils';
6+
import {fileDirective} from './directive';
67

78
export type PluginOptions = FileOptions & {
89
output?: string;
@@ -14,6 +15,16 @@ export type TransformOptions = {
1415
runtime?: string | {style: string};
1516
bundle?: boolean;
1617
extraAttrs?: FileOptions['fileExtraAttrs'];
18+
/**
19+
* Enables directive syntax of yfm-file.
20+
*
21+
* - 'disabled' – directive syntax is disabled.
22+
* - 'enabled' – directive syntax is enabled; old (curly bracket) syntax is also enabled.
23+
* - 'only' – enabled only directive syntax; old (curly bracket) syntax is disabled.
24+
*
25+
* @default 'disabled'
26+
*/
27+
directiveSyntax?: 'disabled' | 'enabled' | 'only';
1728
/** @internal */
1829
onBundle?: (env: {bundled: Set<string>}, output: string, runtime: RuntimeObj) => void;
1930
};
@@ -26,13 +37,20 @@ const registerTransform = (
2637
onBundle,
2738
runtime,
2839
output,
40+
directiveSyntax,
2941
}: Pick<TransformOptions, 'extraAttrs' | 'onBundle'> & {
3042
bundle: boolean;
3143
runtime: {style: string};
3244
output: string;
45+
directiveSyntax: NonNullable<TransformOptions['directiveSyntax']>;
3346
},
3447
) => {
35-
filePlugin(md, {fileExtraAttrs: extraAttrs});
48+
if (directiveSyntax === 'disabled' || directiveSyntax === 'enabled') {
49+
filePlugin(md, {fileExtraAttrs: extraAttrs});
50+
}
51+
if (directiveSyntax === 'enabled' || directiveSyntax === 'only') {
52+
fileDirective(md, {fileExtraAttrs: extraAttrs});
53+
}
3654

3755
md.core.ruler.push('yfm_file_after', ({env}) => {
3856
if (env?.[ENV_FLAG_NAME]) {
@@ -49,7 +67,7 @@ const registerTransform = (
4967
};
5068

5169
export const transform = (opts: TransformOptions = {}) => {
52-
const {bundle = true} = opts;
70+
const {bundle = true, directiveSyntax = 'disabled'} = opts;
5371

5472
if (bundle && typeof opts.runtime === 'string') {
5573
throw new TypeError('Option `runtime` should be record when `bundle` is enabled.');
@@ -68,6 +86,7 @@ export const transform = (opts: TransformOptions = {}) => {
6886
bundle,
6987
runtime,
7088
output,
89+
directiveSyntax,
7190
onBundle: opts.onBundle,
7291
extraAttrs: fileExtraAttrs ?? opts.extraAttrs,
7392
});
@@ -79,6 +98,7 @@ export const transform = (opts: TransformOptions = {}) => {
7998
registerTransform(md, {
8099
bundle,
81100
runtime,
101+
directiveSyntax,
82102
output: destRoot,
83103
onBundle: opts.onBundle,
84104
extraAttrs: opts.extraAttrs,

0 commit comments

Comments
 (0)