Spaces:
Running
Running
File size: 3,553 Bytes
b39afbe |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
/**
* Copyright (c) 2023 MERCENARIES.AI PTE. LTD.
* All rights reserved.
*/
import Handlebars, { HelperDelegate } from 'handlebars';
import { marked } from 'marked';
const mdRenderer = new marked.Renderer();
mdRenderer.link = function (href, title, text) {
const link = marked.Renderer.prototype.link.apply(this, arguments as any);
return link.replace('<a', "<a target='_blank'");
};
class MarkdownEngine {
handlebars: typeof Handlebars;
SafeString: typeof Handlebars.SafeString = Handlebars.SafeString;
private asyncResolvers: { [directive: string]: (token: string) => Promise<any> } = {};
constructor() {
this.handlebars = Handlebars.create();
}
registerAsyncResolver(directive: string, resolverFunction: (token: string) => Promise<any>) {
this.asyncResolvers[directive] = resolverFunction;
}
registerToken(tokenName: string, resolver: HelperDelegate) {
this.handlebars.registerHelper(tokenName, resolver);
}
async getAsyncDataForDirective(directive: string, token: string): Promise<any> {
const resolver = await this.asyncResolvers[directive];
if (!resolver) {
throw new Error(`No resolver registered for directive: ${directive}`);
}
return await resolver(token);
}
extractDirectiveData(statement: any): { name?: string; param?: string } {
if (statement.type === 'MustacheStatement' || statement.type === 'BlockStatement') {
const name = statement.path?.original;
const param = statement.params?.[0]?.original;
return {
name,
param
};
}
return {};
}
async preprocessData(content: string, tokens: Map<string, string>): Promise<{ content: string; data: any }> {
let data: any = {};
for (const [placeholder, originalDirective] of tokens.entries()) {
const parsed = Handlebars.parse(originalDirective);
const directiveData = this.extractDirectiveData(parsed.body[0]);
const directive = directiveData?.name;
const token = directiveData?.param;
if (directive && token) {
/*const tokenData = await this.getAsyncDataForDirective(directive, token);
data[placeholder] = tokenData;*/
content = content.replace(placeholder, originalDirective);
}
}
return { content, data };
}
extractTokens(content: string): { modifiedContent: string; tokens: Map<string, string> } {
//const tokenRegex = /{{([A-Z0-9]+)[^}]*}}/g;
const tokenRegex = /{{\s*([a-zA-Z0-9_-]+)\s*([^}]*)\s*}}/g;
const tokens = new Map<string, string>();
let counter = 0;
const modifiedContent = content.replace(tokenRegex, (match) => {
const placeholder = `TOKEN_${++counter}`;
tokens.set(placeholder, match);
return placeholder;
});
return { modifiedContent, tokens };
}
injectTokens(content: string, tokens: Map<string, string>): string {
let processedContent = content;
tokens.forEach((value, key) => {
processedContent = processedContent.replace(key, value);
});
return processedContent;
}
async render(markdownContent: string, context: any = {}): Promise<string> {
let { modifiedContent, tokens } = this.extractTokens(markdownContent);
const md = marked.parse(modifiedContent, { renderer: mdRenderer });
let { content, data } = await this.preprocessData(md, tokens);
content = this.injectTokens(content, tokens);
const replacedContent = this.handlebars.compile(content)(Object.assign({}, data, context));
return replacedContent;
}
}
export { MarkdownEngine };
|