From 5b141d1f3c798f47b9be5b08e20ac1e85c0569a8 Mon Sep 17 00:00:00 2001 From: d3m1d0v Date: Mon, 18 Nov 2024 15:28:55 +0300 Subject: [PATCH] feat(plugin): support directive syntax --- package-lock.json | 27 ++++++++++++++---- package.json | 3 ++ src/plugin/directive.ts | 62 +++++++++++++++++++++++++++++++++++++++++ src/plugin/plugin.ts | 13 +++------ src/plugin/renderer.ts | 9 ++++++ src/plugin/transform.ts | 24 ++++++++++++++-- 6 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 src/plugin/directive.ts create mode 100644 src/plugin/renderer.ts diff --git a/package-lock.json b/package-lock.json index c80afe0..8104d56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "@diplodoc/file-extension", "version": "0.0.0", "license": "MIT", + "dependencies": { + "@diplodoc/directive": "^0.2.0" + }, "devDependencies": { "@diplodoc/lint": "^1.2.0", "@diplodoc/tsconfig": "^1.0.2", @@ -497,6 +500,14 @@ "postcss-selector-parser": "^6.0.13" } }, + "node_modules/@diplodoc/directive": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@diplodoc/directive/-/directive-0.2.0.tgz", + "integrity": "sha512-TV0M2gdxYThVwoSHs0r8pKXbAq21e213frKW1m89wlYNxGGQN398sOAWS86k/JY+xogpr/Xq8fGPO3Z4WApcUw==", + "dependencies": { + "markdown-it-directive": "2.0.2" + } + }, "node_modules/@diplodoc/lint": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@diplodoc/lint/-/lint-1.2.0.tgz", @@ -1604,14 +1615,12 @@ "node_modules/@types/linkify-it": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "dev": true + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==" }, "node_modules/@types/markdown-it": { "version": "13.0.9", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz", "integrity": "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==", - "dev": true, "dependencies": { "@types/linkify-it": "^3", "@types/mdurl": "^1" @@ -1620,8 +1629,7 @@ "node_modules/@types/mdurl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "dev": true + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==" }, "node_modules/@types/minimist": { "version": "1.2.5", @@ -5327,6 +5335,15 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/markdown-it-directive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-directive/-/markdown-it-directive-2.0.2.tgz", + "integrity": "sha512-UceeGZwl9bjnuEJWW/4UnJIFfcJ3NYmEXSTrffq6N/77E5wpbAYyopsuH/H4R8Ccuwge9E7otyuvHP5bULkccg==", + "peerDependencies": { + "@types/markdown-it": "^12.0.0 || ^13.0.0", + "markdown-it": "^12.0.0 || ^13.0.0" + } + }, "node_modules/markdown-it/node_modules/entities": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", diff --git a/package.json b/package.json index 93684f1..47c6308 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,9 @@ "npm-run-all": "^4.1.5", "typescript": "^5.6.3" }, + "dependencies": { + "@diplodoc/directive": "^0.2.0" + }, "peerDependencies": { "markdown-it": "^13.0.0" } diff --git a/src/plugin/directive.ts b/src/plugin/directive.ts new file mode 100644 index 0000000..84e3721 --- /dev/null +++ b/src/plugin/directive.ts @@ -0,0 +1,62 @@ +import type {FileOptions} from './plugin'; + +import { + directiveParser, + enableInlineDirectives, + registerInlineDirective, +} from '@diplodoc/directive'; + +import {fileRenderer} from './renderer'; +import {FILE_TOKEN, FileClassName, LinkHtmlAttr} from './const'; + +const ALLOWED_ATTRS: readonly string[] = [ + LinkHtmlAttr.ReferrerPolicy, + LinkHtmlAttr.Rel, + LinkHtmlAttr.Target, + LinkHtmlAttr.Type, + LinkHtmlAttr.HrefLang, +]; + +export const fileDirective: markdownit.PluginWithOptions = (md, opts = {}) => { + const {fileExtraAttrs} = opts; + + fileRenderer(md); + + md.use(directiveParser()); + + enableInlineDirectives(md); + + registerInlineDirective(md, 'file', (state, params) => { + if (!params.content || !params.dests) { + return false; + } + + const filename = params.content.raw; + const filelink = params.dests.link || ''; + + const token = state.push(FILE_TOKEN, '', 0); + token.block = false; + token.markup = ':file'; + token.content = params.content.raw; + + token.attrSet('class', FileClassName.Link); + token.attrSet(LinkHtmlAttr.Href, filelink); + token.attrSet(LinkHtmlAttr.Download, filename); + + if (params.attrs) { + for (const attrName of ALLOWED_ATTRS) { + if (params.attrs[attrName]) { + token.attrSet(attrName, params.attrs[attrName]); + } + } + } + + if (Array.isArray(fileExtraAttrs)) { + for (const [name, value] of fileExtraAttrs) { + token.attrSet(name, value); + } + } + + return true; + }); +}; diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts index 5fc30a4..495d89d 100644 --- a/src/plugin/plugin.ts +++ b/src/plugin/plugin.ts @@ -1,5 +1,3 @@ -import type MarkdownIt from 'markdown-it'; - import { ENV_FLAG_NAME, FILE_TOKEN, @@ -12,12 +10,15 @@ import { REQUIRED_ATTRS, RULE_NAME, } from './const'; +import {fileRenderer} from './renderer'; export type FileOptions = { fileExtraAttrs?: [string, string][]; }; -export const filePlugin: MarkdownIt.PluginWithOptions = (md, opts) => { +export const filePlugin: markdownit.PluginWithOptions = (md, opts) => { + fileRenderer(md); + md.inline.ruler.push(RULE_NAME, (state, silent) => { if (state.src.substring(state.pos, state.pos + PREFIX_LENGTH) !== PREFIX) { return false; @@ -82,10 +83,4 @@ export const filePlugin: MarkdownIt.PluginWithOptions = (md, opts) return true; }); - - md.renderer.rules[FILE_TOKEN] = (tokens, idx, _opts, _env, self) => { - const token = tokens[idx]; - const iconHtml = ``; - return `${iconHtml}${md.utils.escapeHtml(token.content)}`; - }; }; diff --git a/src/plugin/renderer.ts b/src/plugin/renderer.ts new file mode 100644 index 0000000..d1a1bfa --- /dev/null +++ b/src/plugin/renderer.ts @@ -0,0 +1,9 @@ +import {FILE_TOKEN, FileClassName} from './const'; + +export const fileRenderer: markdownit.PluginSimple = (md) => { + md.renderer.rules[FILE_TOKEN] = (tokens, idx, _opts, _env, self) => { + const token = tokens[idx]; + const iconHtml = ``; + return `${iconHtml}${md.utils.escapeHtml(token.content)}`; + }; +}; diff --git a/src/plugin/transform.ts b/src/plugin/transform.ts index 7bfbfe5..1576554 100644 --- a/src/plugin/transform.ts +++ b/src/plugin/transform.ts @@ -3,6 +3,7 @@ import MarkdownIt from 'markdown-it'; import {type FileOptions, filePlugin} from './plugin'; import {ENV_FLAG_NAME} from './const'; import {hidden} from './utils'; +import {fileDirective} from './directive'; export type PluginOptions = FileOptions & { output?: string; @@ -14,6 +15,16 @@ export type TransformOptions = { runtime?: string | {style: string}; bundle?: boolean; extraAttrs?: FileOptions['fileExtraAttrs']; + /** + * Enables directive syntax of yfm-file. + * + * - 'disabled' – directive syntax is disabled. + * - 'enabled' – directive syntax is enabled; old syntax is also enabled. + * - 'only' – enabled only directive syntax; old syntax is disabled. + * + * @default 'disabled' + */ + directiveSyntax?: 'disabled' | 'enabled' | 'only'; /** @internal */ onBundle?: (env: {bundled: Set}, output: string, runtime: RuntimeObj) => void; }; @@ -26,13 +37,20 @@ const registerTransform = ( onBundle, runtime, output, + directiveSyntax, }: Pick & { bundle: boolean; runtime: {style: string}; output: string; + directiveSyntax: NonNullable; }, ) => { - filePlugin(md, {fileExtraAttrs: extraAttrs}); + if (directiveSyntax === 'disabled' || directiveSyntax === 'enabled') { + filePlugin(md, {fileExtraAttrs: extraAttrs}); + } + if (directiveSyntax === 'enabled' || directiveSyntax === 'only') { + fileDirective(md, {fileExtraAttrs: extraAttrs}); + } md.core.ruler.push('yfm_file_after', ({env}) => { if (env?.[ENV_FLAG_NAME]) { @@ -49,7 +67,7 @@ const registerTransform = ( }; export const transform = (opts: TransformOptions = {}) => { - const {bundle = true} = opts; + const {bundle = true, directiveSyntax = 'disabled'} = opts; if (bundle && typeof opts.runtime === 'string') { throw new TypeError('Option `runtime` should be record when `bundle` is enabled.'); @@ -68,6 +86,7 @@ export const transform = (opts: TransformOptions = {}) => { bundle, runtime, output, + directiveSyntax, onBundle: opts.onBundle, extraAttrs: fileExtraAttrs ?? opts.extraAttrs, }); @@ -79,6 +98,7 @@ export const transform = (opts: TransformOptions = {}) => { registerTransform(md, { bundle, runtime, + directiveSyntax, output: destRoot, onBundle: opts.onBundle, extraAttrs: opts.extraAttrs,