439 lines
13 KiB
JavaScript
439 lines
13 KiB
JavaScript
// src/markdown.ts
|
|
import { slugify as defaultSlugify } from "@mdit-vue/shared";
|
|
import { logger as logger2 } from "@vuepress/utils";
|
|
import MarkdownIt from "markdown-it";
|
|
|
|
// src/plugins.ts
|
|
import {
|
|
componentPlugin
|
|
} from "@mdit-vue/plugin-component";
|
|
import {
|
|
frontmatterPlugin
|
|
} from "@mdit-vue/plugin-frontmatter";
|
|
import {
|
|
headersPlugin
|
|
} from "@mdit-vue/plugin-headers";
|
|
import { sfcPlugin } from "@mdit-vue/plugin-sfc";
|
|
import { titlePlugin } from "@mdit-vue/plugin-title";
|
|
import { tocPlugin } from "@mdit-vue/plugin-toc";
|
|
|
|
// src/plugins/anchorPlugin.ts
|
|
import { default as anchorPlugin } from "markdown-it-anchor";
|
|
|
|
// src/plugins/assetsPlugin/resolveLink.ts
|
|
import { path } from "@vuepress/utils";
|
|
import { decode } from "mdurl";
|
|
var resolveLink = (link, {
|
|
env,
|
|
absolutePathPrependBase = false,
|
|
relativePathPrefix,
|
|
strict = false
|
|
}) => {
|
|
if (link.startsWith("data:")) return link;
|
|
let resolvedLink = decode(link);
|
|
const isRelativePath = strict ? (
|
|
// in strict mode, only link that starts with `./` or `../` is considered as relative path
|
|
/^\.{1,2}\//.test(link)
|
|
) : (
|
|
// in non-strict mode, link that does not start with `/` and does not have protocol is considered as relative path
|
|
!link.startsWith("/") && !/[A-z]+:\/\//.test(link)
|
|
);
|
|
if (isRelativePath && env.filePathRelative) {
|
|
resolvedLink = `${relativePathPrefix}/${path.join(
|
|
path.dirname(env.filePathRelative),
|
|
resolvedLink
|
|
)}`;
|
|
}
|
|
if (absolutePathPrependBase && env.base && link.startsWith("/")) {
|
|
resolvedLink = path.join(env.base, resolvedLink);
|
|
}
|
|
return resolvedLink;
|
|
};
|
|
|
|
// src/plugins/assetsPlugin/assetsPlugin.ts
|
|
var assetsPlugin = (md, {
|
|
absolutePathPrependBase = false,
|
|
relativePathPrefix = "@source"
|
|
} = {}) => {
|
|
const rawImageRule = md.renderer.rules.image;
|
|
md.renderer.rules.image = (tokens, idx, options, env, self) => {
|
|
const token = tokens[idx];
|
|
const link = token.attrGet("src");
|
|
if (link) {
|
|
token.attrSet(
|
|
"src",
|
|
resolveLink(link, { env, absolutePathPrependBase, relativePathPrefix })
|
|
);
|
|
}
|
|
return rawImageRule(tokens, idx, options, env, self);
|
|
};
|
|
const createHtmlRule = (rawHtmlRule) => (tokens, idx, options, env, self) => {
|
|
tokens[idx].content = tokens[idx].content.replace(
|
|
/(<img\b.*?src=)(['"])([^\2]*?)\2/gs,
|
|
(_, prefix, quote, src) => `${prefix}${quote}${resolveLink(src.trim(), {
|
|
env,
|
|
absolutePathPrependBase,
|
|
relativePathPrefix,
|
|
strict: true
|
|
})}${quote}`
|
|
).replace(
|
|
/(<img\b.*?srcset=)(['"])([^\2]*?)\2/gs,
|
|
(_, prefix, quote, srcset) => `${prefix}${quote}${srcset.split(",").map(
|
|
(item) => item.trim().replace(
|
|
/^([^ ]*?)([ \n].*)?$/,
|
|
(_2, url, descriptor = "") => `${resolveLink(url.trim(), {
|
|
env,
|
|
absolutePathPrependBase,
|
|
relativePathPrefix,
|
|
strict: true
|
|
})}${descriptor.replace(/[ \n]+/g, " ").trimEnd()}`
|
|
)
|
|
).join(", ")}${quote}`
|
|
);
|
|
return rawHtmlRule(tokens, idx, options, env, self);
|
|
};
|
|
const rawHtmlBlockRule = md.renderer.rules.html_block;
|
|
const rawHtmlInlineRule = md.renderer.rules.html_inline;
|
|
md.renderer.rules.html_block = createHtmlRule(rawHtmlBlockRule);
|
|
md.renderer.rules.html_inline = createHtmlRule(rawHtmlInlineRule);
|
|
};
|
|
|
|
// src/plugins/emojiPlugin.ts
|
|
import { full as emojiPlugin } from "markdown-it-emoji";
|
|
|
|
// src/plugins/importCodePlugin/createImportCodeBlockRule.ts
|
|
import { path as path2 } from "@vuepress/utils";
|
|
var MIN_LENGTH = 9;
|
|
var START_CODES = [64, 91, 99, 111, 100, 101];
|
|
var SYNTAX_RE = /^@\[code(?:{(?:(?:(?<lineStart>\d+)?-(?<lineEnd>\d+)?)|(?<lineSingle>\d+))})?(?: (?<info>[^\]]+))?\]\((?<importPath>[^)]*)\)/;
|
|
var parseLineNumber = (line) => line ? Number.parseInt(line, 10) : void 0;
|
|
var createImportCodeBlockRule = ({ handleImportPath = (str) => str }) => (state, startLine, endLine, silent) => {
|
|
if (state.sCount[startLine] - state.blkIndent >= 4) {
|
|
return false;
|
|
}
|
|
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
const max = state.eMarks[startLine];
|
|
if (pos + MIN_LENGTH > max) return false;
|
|
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
if (state.src.charCodeAt(pos + i) !== START_CODES[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
if (!match?.groups) return false;
|
|
if (silent) return true;
|
|
const { info, importPath } = match.groups;
|
|
const lineSingle = parseLineNumber(match.groups.lineSingle);
|
|
const lineStart = lineSingle ?? parseLineNumber(match.groups.lineStart) ?? 0;
|
|
const lineEnd = lineSingle ?? parseLineNumber(match.groups.lineEnd);
|
|
const meta = {
|
|
importPath: handleImportPath(importPath),
|
|
lineStart,
|
|
lineEnd
|
|
};
|
|
const token = state.push("import_code", "code", 0);
|
|
token.info = info ?? path2.extname(meta.importPath).slice(1);
|
|
token.markup = "```";
|
|
token.map = [startLine, startLine + 1];
|
|
token.meta = meta;
|
|
state.line = startLine + 1;
|
|
return true;
|
|
};
|
|
|
|
// src/plugins/importCodePlugin/resolveImportCode.ts
|
|
import { colors, fs, logger, path as path3 } from "@vuepress/utils";
|
|
var resolveImportCode = ({ importPath, lineStart, lineEnd }, { filePath }) => {
|
|
let importFilePath = importPath;
|
|
if (!path3.isAbsolute(importPath)) {
|
|
if (!filePath) {
|
|
logger.error(
|
|
`Import file ${colors.magenta(importPath)} can not be resolved`
|
|
);
|
|
return {
|
|
importFilePath: null,
|
|
importCode: "Error when resolving path"
|
|
};
|
|
}
|
|
importFilePath = path3.resolve(filePath, "..", importPath);
|
|
}
|
|
if (!fs.existsSync(importFilePath)) {
|
|
logger.error(`Import file ${colors.magenta(importPath)} not found`);
|
|
return {
|
|
importFilePath,
|
|
importCode: "File not found"
|
|
};
|
|
}
|
|
const fileContent = fs.readFileSync(importFilePath).toString();
|
|
return {
|
|
importFilePath,
|
|
importCode: fileContent.split("\n").slice(lineStart ? lineStart - 1 : lineStart, lineEnd).join("\n").replace(/\n?$/, "\n")
|
|
};
|
|
};
|
|
|
|
// src/plugins/importCodePlugin/importCodePlugin.ts
|
|
var importCodePlugin = (md, options = {}) => {
|
|
md.block.ruler.before(
|
|
"fence",
|
|
"import_code",
|
|
createImportCodeBlockRule(options),
|
|
{
|
|
alt: ["paragraph", "reference", "blockquote", "list"]
|
|
}
|
|
);
|
|
md.renderer.rules.import_code = (tokens, idx, options2, env, slf) => {
|
|
const token = tokens[idx];
|
|
const { importFilePath, importCode } = resolveImportCode(token.meta, env);
|
|
token.content = importCode;
|
|
if (importFilePath) {
|
|
;
|
|
(env.importedFiles ??= []).push(importFilePath);
|
|
}
|
|
return md.renderer.rules.fence(tokens, idx, options2, env, slf);
|
|
};
|
|
};
|
|
|
|
// src/plugins/linksPlugin/linksPlugin.ts
|
|
import { inferRoutePath, isLinkExternal } from "@vuepress/shared";
|
|
|
|
// src/plugins/linksPlugin/resolvePaths.ts
|
|
import { removeLeadingSlash } from "@vuepress/shared";
|
|
import { path as path4 } from "@vuepress/utils";
|
|
var resolvePaths = (rawPath, base, filePathRelative) => {
|
|
let absolutePath;
|
|
let relativePath;
|
|
if (rawPath.startsWith("/")) {
|
|
if (rawPath.endsWith(".md")) {
|
|
absolutePath = path4.join(base, rawPath);
|
|
relativePath = removeLeadingSlash(rawPath);
|
|
} else {
|
|
absolutePath = rawPath;
|
|
relativePath = path4.relative(base, absolutePath);
|
|
}
|
|
} else {
|
|
if (filePathRelative) {
|
|
relativePath = path4.join(
|
|
// file path may contain non-ASCII characters
|
|
path4.dirname(encodeURI(filePathRelative)),
|
|
rawPath
|
|
);
|
|
absolutePath = path4.join(base, relativePath);
|
|
} else {
|
|
relativePath = rawPath.replace(/^(?:\.\/)?(.*)$/, "$1");
|
|
absolutePath = null;
|
|
}
|
|
}
|
|
return {
|
|
absolutePath,
|
|
relativePath
|
|
};
|
|
};
|
|
|
|
// src/plugins/linksPlugin/linksPlugin.ts
|
|
var linksPlugin = (md, options = {}) => {
|
|
const internalTag = options.internalTag || "RouteLink";
|
|
const externalAttrs = {
|
|
target: "_blank",
|
|
rel: "noopener noreferrer",
|
|
...options.externalAttrs
|
|
};
|
|
let hasOpenInternalLink = false;
|
|
const handleLinkOpen = (tokens, idx, env) => {
|
|
const token = tokens[idx];
|
|
const hrefIndex = token.attrIndex("href");
|
|
if (hrefIndex < 0) {
|
|
return;
|
|
}
|
|
const hrefAttr = token.attrs[hrefIndex];
|
|
const hrefLink = hrefAttr[1];
|
|
const { base = "/", filePathRelative = null } = env;
|
|
if (isLinkExternal(hrefLink, base)) {
|
|
Object.entries(externalAttrs).forEach(
|
|
([key, val]) => token.attrSet(key, val)
|
|
);
|
|
return;
|
|
}
|
|
const internalLinkMatch = hrefLink.match(
|
|
/^([^#?]*?(?:\/|\.md|\.html))([#?].*)?$/
|
|
);
|
|
if (!internalLinkMatch) {
|
|
return;
|
|
}
|
|
const rawPath = internalLinkMatch[1];
|
|
const rawHashAndQueries = internalLinkMatch[2] || "";
|
|
const { relativePath, absolutePath } = resolvePaths(
|
|
rawPath,
|
|
base,
|
|
filePathRelative
|
|
);
|
|
if (["RouterLink", "RouteLink"].includes(internalTag)) {
|
|
token.tag = internalTag;
|
|
hrefAttr[0] = "to";
|
|
const normalizedPath = inferRoutePath(
|
|
absolutePath ? absolutePath.replace(new RegExp(`^${base}`), "/") : relativePath
|
|
);
|
|
hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}`;
|
|
hasOpenInternalLink = true;
|
|
} else {
|
|
const normalizedPath = inferRoutePath(absolutePath ?? relativePath);
|
|
hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}`;
|
|
}
|
|
;
|
|
(env.links ??= []).push({
|
|
raw: hrefLink,
|
|
relative: relativePath,
|
|
absolute: absolutePath
|
|
});
|
|
};
|
|
md.renderer.rules.link_open = (tokens, idx, options2, env, self) => {
|
|
handleLinkOpen(tokens, idx, env);
|
|
return self.renderToken(tokens, idx, options2);
|
|
};
|
|
md.renderer.rules.link_close = (tokens, idx, options2, _env, self) => {
|
|
if (hasOpenInternalLink) {
|
|
hasOpenInternalLink = false;
|
|
tokens[idx].tag = internalTag;
|
|
}
|
|
return self.renderToken(tokens, idx, options2);
|
|
};
|
|
};
|
|
|
|
// src/plugins/vPrePlugin/resolveVPre.ts
|
|
var resolveVPre = (info) => {
|
|
if (/:v-pre\b/.test(info)) {
|
|
return true;
|
|
}
|
|
if (/:no-v-pre\b/.test(info)) {
|
|
return false;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// src/plugins/vPrePlugin/vPrePlugin.ts
|
|
var vPrePlugin = (md, { inline = true, block = true } = {}) => {
|
|
const rawFence = md.renderer.rules.fence;
|
|
md.renderer.rules.fence = (...args) => {
|
|
const [tokens, idx] = args;
|
|
const token = tokens[idx];
|
|
const info = token.info ? md.utils.unescapeAll(token.info).trim() : "";
|
|
let result = rawFence(...args);
|
|
if (resolveVPre(info) ?? block) {
|
|
result = `<pre v-pre${result.slice("<pre".length)}`;
|
|
}
|
|
return result;
|
|
};
|
|
if (inline) {
|
|
const rawInlineCodeRule = md.renderer.rules.code_inline;
|
|
md.renderer.rules.code_inline = (...args) => {
|
|
const result = rawInlineCodeRule(...args);
|
|
return `<code v-pre${result.slice("<code".length)}`;
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/markdown.ts
|
|
var createMarkdown = ({
|
|
anchor,
|
|
assets,
|
|
code,
|
|
vPre,
|
|
component,
|
|
emoji,
|
|
frontmatter,
|
|
headers,
|
|
title,
|
|
importCode,
|
|
links,
|
|
sfc,
|
|
slugify = defaultSlugify,
|
|
toc,
|
|
...markdownItOptions
|
|
} = {}) => {
|
|
const md = MarkdownIt({
|
|
...markdownItOptions,
|
|
// should always enable html option
|
|
html: true
|
|
});
|
|
if (anchor !== false) {
|
|
md.use(anchorPlugin, {
|
|
level: [1, 2, 3, 4, 5, 6],
|
|
slugify,
|
|
permalink: anchorPlugin.permalink.headerLink({
|
|
class: "header-anchor",
|
|
// Add a span inside the link so Safari shows headings in reader view.
|
|
safariReaderFix: true
|
|
}),
|
|
...anchor
|
|
});
|
|
}
|
|
if (assets !== false) {
|
|
md.use(assetsPlugin, assets);
|
|
}
|
|
if (code) {
|
|
logger2.warn(
|
|
`\`markdown.code\` option has been removed, please use '@vuepress/plugin-shiki' or '@vuepress/plugin-prismjs' instead.
|
|
See https://v2.vuepress.vuejs.org/reference/config.html#markdown-code`
|
|
);
|
|
}
|
|
if (component !== false) {
|
|
md.use(componentPlugin);
|
|
}
|
|
if (emoji !== false) {
|
|
md.use(emojiPlugin, emoji);
|
|
}
|
|
if (frontmatter !== false) {
|
|
md.use(frontmatterPlugin, {
|
|
...frontmatter,
|
|
grayMatterOptions: {
|
|
excerpt: false,
|
|
...frontmatter?.grayMatterOptions
|
|
}
|
|
});
|
|
}
|
|
if (headers !== false) {
|
|
md.use(headersPlugin, {
|
|
level: [2, 3],
|
|
slugify,
|
|
...headers
|
|
});
|
|
}
|
|
if (importCode !== false) {
|
|
md.use(importCodePlugin, importCode);
|
|
}
|
|
if (links !== false) {
|
|
md.use(linksPlugin, links);
|
|
}
|
|
if (sfc !== false) {
|
|
md.use(sfcPlugin, sfc);
|
|
}
|
|
if (toc !== false) {
|
|
md.use(tocPlugin, {
|
|
level: [2, 3],
|
|
slugify,
|
|
linkTag: "router-link",
|
|
...toc
|
|
});
|
|
}
|
|
if (title !== false) {
|
|
md.use(titlePlugin);
|
|
}
|
|
if (vPre !== false) {
|
|
md.use(vPrePlugin, vPre);
|
|
}
|
|
return md;
|
|
};
|
|
export {
|
|
anchorPlugin,
|
|
assetsPlugin,
|
|
componentPlugin,
|
|
createMarkdown,
|
|
emojiPlugin,
|
|
frontmatterPlugin,
|
|
headersPlugin,
|
|
importCodePlugin,
|
|
linksPlugin,
|
|
sfcPlugin,
|
|
titlePlugin,
|
|
tocPlugin,
|
|
vPrePlugin
|
|
};
|