148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
/* 文件:markdown/parser.ts — Markdown 解析器的扩展与语法注册入口。 */
|
||
|
||
import { Marked } from "marked";
|
||
import { NMPSettings } from "src/settings";
|
||
import { App, Vault } from "obsidian";
|
||
import AssetsManager from "../assets";
|
||
import { Extension, MDRendererCallback } from "./extension";
|
||
import { Blockquote} from "./blockquote";
|
||
import { CodeRenderer } from "./code";
|
||
import { EmbedBlockMark } from "./embed-block-mark";
|
||
import { SVGIcon } from "./icons";
|
||
import { LinkRenderer } from "./link";
|
||
import { LocalFile, LocalImageManager } from "./local-file";
|
||
import { MathRenderer } from "./math";
|
||
import { TextHighlight } from "./text-highlight";
|
||
import { Comment } from "./commnet";
|
||
import { Topic } from "./topic";
|
||
import { HeadingRenderer } from "./heading";
|
||
import { FootnoteRenderer } from "./footnote";
|
||
import { EmptyLineRenderer } from "./empty-line";
|
||
import { cleanUrl } from "../utils";
|
||
|
||
|
||
const markedOptiones = {
|
||
gfm: true,
|
||
breaks: true,
|
||
};
|
||
|
||
const customRenderer = {
|
||
hr(): string {
|
||
return '<hr>';
|
||
},
|
||
list(body: string, ordered: boolean, start: number | ''): string {
|
||
const type = ordered ? 'ol' : 'ul';
|
||
const startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
|
||
return '<' + type + startatt + ' class="list-paddingleft-1">' + body + '</' + type + '>';
|
||
},
|
||
listitem(text: string, task: boolean, checked: boolean): string {
|
||
return `<li><section><span data-leaf="">${text}<span></section></li>`;
|
||
},
|
||
image(href: string, title: string | null, text: string): string {
|
||
const cleanHref = cleanUrl(href);
|
||
if (cleanHref === null) {
|
||
return text;
|
||
}
|
||
href = cleanHref;
|
||
|
||
if (!href.startsWith('http')) {
|
||
const res = AssetsManager.getInstance().getResourcePath(decodeURI(href));
|
||
if (res) {
|
||
href = res.resUrl;
|
||
const info = {
|
||
resUrl: res.resUrl,
|
||
filePath: res.filePath,
|
||
media_id: null,
|
||
url: null
|
||
};
|
||
LocalImageManager.getInstance().setImage(res.resUrl, info);
|
||
}
|
||
}
|
||
let out = '';
|
||
if (NMPSettings.getInstance().useFigcaption) {
|
||
out = `<figure style="display: flex; flex-direction: column; align-items: center;"><img src="${href}" alt="${text}"`;
|
||
if (title) {
|
||
out += ` title="${title}"`;
|
||
}
|
||
if (text.length > 0) {
|
||
out += `><figcaption>${text}</figcaption></figure>`;
|
||
}
|
||
else {
|
||
out += '></figure>'
|
||
}
|
||
}
|
||
else {
|
||
out = `<img src="${href}" alt="${text}"`;
|
||
if (title) {
|
||
out += ` title="${title}"`;
|
||
}
|
||
out += '>';
|
||
}
|
||
return out;
|
||
}
|
||
};
|
||
|
||
export class MarkedParser {
|
||
extensions: Extension[] = [];
|
||
marked: Marked;
|
||
app: App;
|
||
vault: Vault;
|
||
|
||
constructor(app: App, callback: MDRendererCallback) {
|
||
this.app = app;
|
||
this.vault = app.vault;
|
||
|
||
const settings = NMPSettings.getInstance();
|
||
const assetsManager = AssetsManager.getInstance();
|
||
|
||
this.extensions.push(new LocalFile(app, settings, assetsManager, callback));
|
||
this.extensions.push(new Blockquote(app, settings, assetsManager, callback));
|
||
this.extensions.push(new EmbedBlockMark(app, settings, assetsManager, callback));
|
||
this.extensions.push(new SVGIcon(app, settings, assetsManager, callback));
|
||
this.extensions.push(new LinkRenderer(app, settings, assetsManager, callback));
|
||
this.extensions.push(new TextHighlight(app, settings, assetsManager, callback));
|
||
this.extensions.push(new CodeRenderer(app, settings, assetsManager, callback));
|
||
this.extensions.push(new Comment(app, settings, assetsManager, callback));
|
||
this.extensions.push(new Topic(app, settings, assetsManager, callback));
|
||
this.extensions.push(new HeadingRenderer(app, settings, assetsManager, callback));
|
||
this.extensions.push(new FootnoteRenderer(app, settings, assetsManager, callback));
|
||
if (settings.enableEmptyLine) {
|
||
this.extensions.push(new EmptyLineRenderer(app, settings, assetsManager, callback));
|
||
}
|
||
if (settings.isAuthKeyVaild()) {
|
||
this.extensions.push(new MathRenderer(app, settings, assetsManager, callback));
|
||
}
|
||
}
|
||
|
||
async buildMarked() {
|
||
this.marked = new Marked();
|
||
this.marked.use(markedOptiones);
|
||
for (const ext of this.extensions) {
|
||
this.marked.use(ext.markedExtension());
|
||
ext.marked = this.marked;
|
||
await ext.prepare();
|
||
}
|
||
this.marked.use({renderer: customRenderer});
|
||
}
|
||
|
||
async prepare() {
|
||
this.extensions.forEach(async ext => await ext.prepare());
|
||
}
|
||
|
||
async postprocess(html: string) {
|
||
let result = html;
|
||
for (let ext of this.extensions) {
|
||
result = await ext.postprocess(result);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
async parse(content: string) {
|
||
if (!this.marked) await this.buildMarked();
|
||
await this.prepare();
|
||
let html = await this.marked.parse(content);
|
||
html = await this.postprocess(html);
|
||
return html;
|
||
}
|
||
}
|