|
@@ -1,48 +1,82 @@
|
|
|
import { dynamicImport } from '@cspell/dynamic-import';
|
|
import { dynamicImport } from '@cspell/dynamic-import';
|
|
|
|
|
+import { isPopulated } from '@growi/core';
|
|
|
|
|
+import type { IPagePopulatedToShowRevision } from '@growi/core/dist/interfaces';
|
|
|
import type { Root, Code } from 'mdast';
|
|
import type { Root, Code } from 'mdast';
|
|
|
|
|
+import type { HydratedDocument } from 'mongoose';
|
|
|
|
|
+import type * as RehypeMeta from 'rehype-meta';
|
|
|
|
|
+import type * as RehypeStringify from 'rehype-stringify';
|
|
|
import type * as RemarkParse from 'remark-parse';
|
|
import type * as RemarkParse from 'remark-parse';
|
|
|
-import type * as RemarkStringify from 'remark-stringify';
|
|
|
|
|
|
|
+import type * as RemarkRehype from 'remark-rehype';
|
|
|
import type * as Unified from 'unified';
|
|
import type * as Unified from 'unified';
|
|
|
import type * as UnistUtilVisit from 'unist-util-visit';
|
|
import type * as UnistUtilVisit from 'unist-util-visit';
|
|
|
|
|
|
|
|
|
|
+import type { PageDocument } from '~/server/models/page';
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
interface ModuleCache {
|
|
interface ModuleCache {
|
|
|
remarkParse?: typeof RemarkParse.default;
|
|
remarkParse?: typeof RemarkParse.default;
|
|
|
- remarkStringify?: typeof RemarkStringify.default;
|
|
|
|
|
unified?: typeof Unified.unified;
|
|
unified?: typeof Unified.unified;
|
|
|
visit?: typeof UnistUtilVisit.visit;
|
|
visit?: typeof UnistUtilVisit.visit;
|
|
|
|
|
+ remarkRehype?: typeof RemarkRehype.default;
|
|
|
|
|
+ rehypeMeta?: typeof RehypeMeta.default;
|
|
|
|
|
+ rehypeStringify?: typeof RehypeStringify.default;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let moduleCache: ModuleCache = {};
|
|
let moduleCache: ModuleCache = {};
|
|
|
|
|
|
|
|
const initializeModules = async(): Promise<void> => {
|
|
const initializeModules = async(): Promise<void> => {
|
|
|
- if (moduleCache.remarkParse != null && moduleCache.remarkStringify != null && moduleCache.unified != null && moduleCache.visit != null) {
|
|
|
|
|
|
|
+ if (moduleCache.remarkParse != null
|
|
|
|
|
+ && moduleCache.unified != null
|
|
|
|
|
+ && moduleCache.visit != null
|
|
|
|
|
+ && moduleCache.remarkRehype != null
|
|
|
|
|
+ && moduleCache.rehypeMeta != null
|
|
|
|
|
+ && moduleCache.rehypeStringify != null
|
|
|
|
|
+ ) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const [{ default: remarkParse }, { default: remarkStringify }, { unified }, { visit }] = await Promise.all([
|
|
|
|
|
|
|
+ const [
|
|
|
|
|
+ { default: remarkParse },
|
|
|
|
|
+ { unified }, { visit },
|
|
|
|
|
+ { default: remarkRehype },
|
|
|
|
|
+ { default: rehypeMeta },
|
|
|
|
|
+ { default: rehypeStringify },
|
|
|
|
|
+ ] = await Promise.all([
|
|
|
dynamicImport<typeof RemarkParse>('remark-parse', __dirname),
|
|
dynamicImport<typeof RemarkParse>('remark-parse', __dirname),
|
|
|
- dynamicImport<typeof RemarkStringify>('remark-stringify', __dirname),
|
|
|
|
|
dynamicImport<typeof Unified>('unified', __dirname),
|
|
dynamicImport<typeof Unified>('unified', __dirname),
|
|
|
dynamicImport<typeof UnistUtilVisit>('unist-util-visit', __dirname),
|
|
dynamicImport<typeof UnistUtilVisit>('unist-util-visit', __dirname),
|
|
|
|
|
+ dynamicImport<typeof RemarkRehype>('remark-rehype', __dirname),
|
|
|
|
|
+ dynamicImport<typeof RehypeMeta>('rehype-meta', __dirname),
|
|
|
|
|
+ dynamicImport<typeof RehypeStringify>('rehype-stringify', __dirname),
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
moduleCache = {
|
|
moduleCache = {
|
|
|
remarkParse,
|
|
remarkParse,
|
|
|
- remarkStringify,
|
|
|
|
|
unified,
|
|
unified,
|
|
|
visit,
|
|
visit,
|
|
|
|
|
+ remarkRehype,
|
|
|
|
|
+ rehypeMeta,
|
|
|
|
|
+ rehypeStringify,
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const sanitizeMarkdown = async(markdown: string): Promise<string> => {
|
|
|
|
|
|
|
+export const convertMarkdownToHtml = async(page: HydratedDocument<PageDocument> | IPagePopulatedToShowRevision): Promise<string> => {
|
|
|
await initializeModules();
|
|
await initializeModules();
|
|
|
|
|
|
|
|
const {
|
|
const {
|
|
|
- remarkParse, remarkStringify, unified, visit,
|
|
|
|
|
|
|
+ remarkParse,
|
|
|
|
|
+ unified, visit,
|
|
|
|
|
+ remarkRehype,
|
|
|
|
|
+ rehypeMeta,
|
|
|
|
|
+ rehypeStringify,
|
|
|
} = moduleCache;
|
|
} = moduleCache;
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- if (remarkParse == null || remarkStringify == null || unified == null || visit == null) {
|
|
|
|
|
|
|
+ if (remarkParse == null
|
|
|
|
|
+ || unified == null
|
|
|
|
|
+ || visit == null
|
|
|
|
|
+ || remarkRehype == null
|
|
|
|
|
+ || rehypeMeta == null
|
|
|
|
|
+ || rehypeStringify == null) {
|
|
|
throw new Error('Failed to initialize required modules');
|
|
throw new Error('Failed to initialize required modules');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -56,10 +90,17 @@ export const sanitizeMarkdown = async(markdown: string): Promise<string> => {
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const revisionBody = page.revision != null && isPopulated(page.revision) ? page.revision.body : undefined;
|
|
|
|
|
+
|
|
|
const processor = unified()
|
|
const processor = unified()
|
|
|
.use(remarkParse)
|
|
.use(remarkParse)
|
|
|
.use(sanitize)
|
|
.use(sanitize)
|
|
|
- .use(remarkStringify);
|
|
|
|
|
|
|
+ .use(remarkRehype)
|
|
|
|
|
+ .use(rehypeMeta, {
|
|
|
|
|
+ title: page.path,
|
|
|
|
|
+ })
|
|
|
|
|
+ .use(rehypeStringify);
|
|
|
|
|
|
|
|
- return processor.processSync(markdown).toString();
|
|
|
|
|
|
|
+ return processor.processSync(revisionBody).toString();
|
|
|
};
|
|
};
|