fix: 完善本地与网络图片渲染
This commit is contained in:
@@ -34,6 +34,8 @@ export type { MarkdownDocumentMetadata } from "@md-to-pdf/core";
|
||||
|
||||
export interface RenderMarkdownOptions {
|
||||
language?: string;
|
||||
imageSourceMap?: ReadonlyMap<string, string>;
|
||||
warnings?: readonly string[];
|
||||
}
|
||||
|
||||
export interface RenderedMarkdown
|
||||
@@ -84,9 +86,83 @@ const markdown = new MarkdownIt(markdownOptions)
|
||||
});
|
||||
|
||||
const defaultFenceRenderer = markdown.renderer.rules.fence;
|
||||
const defaultImageRenderer = markdown.renderer.rules.image;
|
||||
const defaultParagraphOpenRenderer =
|
||||
markdown.renderer.rules.paragraph_open;
|
||||
const defaultParagraphCloseRenderer =
|
||||
markdown.renderer.rules.paragraph_close;
|
||||
const lengthStylePattern = /^-?\d+(?:\.\d+)?(?:em|ex|px|%)$/u;
|
||||
const tableTextAlignStylePattern = /^(?:left|center|right)$/u;
|
||||
|
||||
interface MarkdownRenderEnvironment {
|
||||
warnings?: string[];
|
||||
imageSourceMap?: ReadonlyMap<string, string>;
|
||||
standaloneImage?: boolean;
|
||||
}
|
||||
|
||||
function getStandaloneImage(
|
||||
token: Token | undefined
|
||||
): Token | undefined {
|
||||
if (token?.type !== "inline") {
|
||||
return undefined;
|
||||
}
|
||||
const meaningful = (token.children ?? []).filter(
|
||||
(child) =>
|
||||
child.type !== "text" || child.content.trim().length > 0
|
||||
);
|
||||
return meaningful.length === 1 && meaningful[0]?.type === "image"
|
||||
? meaningful[0]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
markdown.renderer.rules.paragraph_open = (
|
||||
tokens,
|
||||
index,
|
||||
options,
|
||||
environment,
|
||||
renderer
|
||||
) => {
|
||||
const standaloneImage = getStandaloneImage(tokens[index + 1]);
|
||||
(environment as MarkdownRenderEnvironment).standaloneImage =
|
||||
Boolean(standaloneImage);
|
||||
if (standaloneImage) {
|
||||
return '<figure class="md-document-image-block">\n';
|
||||
}
|
||||
return defaultParagraphOpenRenderer
|
||||
? defaultParagraphOpenRenderer(
|
||||
tokens,
|
||||
index,
|
||||
options,
|
||||
environment,
|
||||
renderer
|
||||
)
|
||||
: renderer.renderToken(tokens, index, options);
|
||||
};
|
||||
|
||||
markdown.renderer.rules.paragraph_close = (
|
||||
tokens,
|
||||
index,
|
||||
options,
|
||||
environment,
|
||||
renderer
|
||||
) => {
|
||||
const renderEnvironment =
|
||||
environment as MarkdownRenderEnvironment;
|
||||
if (renderEnvironment.standaloneImage) {
|
||||
renderEnvironment.standaloneImage = false;
|
||||
return "</figure>\n";
|
||||
}
|
||||
return defaultParagraphCloseRenderer
|
||||
? defaultParagraphCloseRenderer(
|
||||
tokens,
|
||||
index,
|
||||
options,
|
||||
environment,
|
||||
renderer
|
||||
)
|
||||
: renderer.renderToken(tokens, index, options);
|
||||
};
|
||||
|
||||
markdown.renderer.rules.fence = (
|
||||
tokens,
|
||||
index,
|
||||
@@ -114,6 +190,46 @@ markdown.renderer.rules.fence = (
|
||||
return renderFallbackFence(tokens, index, renderer);
|
||||
};
|
||||
|
||||
markdown.renderer.rules.image = (
|
||||
tokens,
|
||||
index,
|
||||
options,
|
||||
environment,
|
||||
renderer
|
||||
) => {
|
||||
const token = tokens[index];
|
||||
const renderEnvironment =
|
||||
environment as MarkdownRenderEnvironment;
|
||||
const imageSourceMap = renderEnvironment.imageSourceMap;
|
||||
const source = token?.attrGet("src");
|
||||
const resolvedSource = source
|
||||
? imageSourceMap?.get(source)
|
||||
: undefined;
|
||||
if (token) {
|
||||
if (resolvedSource) {
|
||||
token.attrSet("src", resolvedSource);
|
||||
}
|
||||
token.attrJoin("class", "md-document-image");
|
||||
}
|
||||
|
||||
const imageHtml = defaultImageRenderer
|
||||
? defaultImageRenderer(
|
||||
tokens,
|
||||
index,
|
||||
options,
|
||||
environment,
|
||||
renderer
|
||||
)
|
||||
: renderer.renderToken(tokens, index, options);
|
||||
if (renderEnvironment.standaloneImage && token) {
|
||||
return (
|
||||
imageHtml +
|
||||
`<figcaption class="md-document-image-caption">${markdown.utils.escapeHtml(token.content)}</figcaption>`
|
||||
);
|
||||
}
|
||||
return imageHtml;
|
||||
};
|
||||
|
||||
markdown.use(markdownItECharts, {
|
||||
onError(error, context: MarkdownItEChartsErrorContext) {
|
||||
const environment = context.environment as
|
||||
@@ -193,10 +309,11 @@ export function renderMarkdown(
|
||||
source: string,
|
||||
options: RenderMarkdownOptions = {}
|
||||
): RenderedMarkdown {
|
||||
const warnings: string[] = [];
|
||||
const warnings = [...(options.warnings ?? [])];
|
||||
const parsed = parseFrontMatter(source);
|
||||
const environment = {
|
||||
warnings
|
||||
warnings,
|
||||
imageSourceMap: options.imageSourceMap
|
||||
};
|
||||
const rendered = markdown.render(parsed.content, environment);
|
||||
const bodyHtml = sanitizeHtml(rendered, safeHtmlOptions);
|
||||
@@ -217,6 +334,27 @@ export function renderMarkdown(
|
||||
};
|
||||
}
|
||||
|
||||
function collectImageSources(tokens: readonly Token[], sources: Set<string>) {
|
||||
for (const token of tokens) {
|
||||
if (token.type === "image") {
|
||||
const source = token.attrGet("src");
|
||||
if (source) {
|
||||
sources.add(source);
|
||||
}
|
||||
}
|
||||
if (token.children) {
|
||||
collectImageSources(token.children, sources);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function extractMarkdownImageSources(source: string) {
|
||||
const parsed = parseFrontMatter(source);
|
||||
const sources = new Set<string>();
|
||||
collectImageSources(markdown.parse(parsed.content, {}), sources);
|
||||
return [...sources];
|
||||
}
|
||||
|
||||
function parseFrontMatter(source: string) {
|
||||
try {
|
||||
return matter(source);
|
||||
|
||||
Reference in New Issue
Block a user