Skip to content

Commit 7df96ac

Browse files
3y3k03y3
3y3k0
authored andcommitted
feat(md): Refactor transform index
1 parent ec1a536 commit 7df96ac

File tree

5 files changed

+221
-161
lines changed

5 files changed

+221
-161
lines changed

src/transform/index.ts

+24-150
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,43 @@
1+
import type {OptionsType, OutputType, EnvType} from './typings';
12
import {bold} from 'chalk';
2-
import attrs from 'markdown-it-attrs';
3-
import Token from 'markdown-it/lib/token';
4-
5-
import {log, LogLevels} from './log';
6-
import makeHighlight from './highlight';
7-
import extractTitle from './title';
8-
import getHeadings from './headings';
3+
import {log} from './log';
94
import liquid from './liquid';
10-
import sanitizeHtml, {SanitizeOptions} from './sanitize';
11-
12-
import notes from './plugins/notes';
13-
import anchors from './plugins/anchors';
14-
import code from './plugins/code';
15-
import cut from './plugins/cut';
16-
import deflist from './plugins/deflist';
17-
import term from './plugins/term';
18-
import file from './plugins/file';
19-
import imsize from './plugins/imsize';
20-
import meta from './plugins/meta';
21-
import sup from './plugins/sup';
22-
import tabs from './plugins/tabs';
23-
import video from './plugins/video';
24-
import monospace from './plugins/monospace';
25-
import yfmTable from './plugins/table';
26-
import {initMd} from './md';
27-
import {MarkdownItPluginCb} from './plugins/typings';
28-
import type {HighlightLangMap, Heading} from './typings';
29-
30-
interface OutputType {
31-
result: {
32-
html: string;
33-
title?: string;
34-
headings: Heading[];
35-
assets?: unknown[];
36-
meta?: object;
37-
};
38-
logs: Record<LogLevels, string[]>;
39-
}
40-
interface OptionsType {
41-
vars?: Record<string, string>;
42-
path?: string;
43-
extractTitle?: boolean;
44-
needTitle?: boolean;
45-
allowHTML?: boolean;
46-
linkify?: boolean;
47-
linkifyTlds?: string | string[];
48-
breaks?: boolean;
49-
conditionsInCode?: boolean;
50-
disableLiquid?: boolean;
51-
leftDelimiter?: string;
52-
rightDelimiter?: string;
53-
isLiquided?: boolean;
54-
needToSanitizeHtml?: boolean;
55-
sanitizeOptions?: SanitizeOptions;
56-
needFlatListHeadings?: boolean;
57-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
58-
plugins?: MarkdownItPluginCb<any>[];
59-
highlightLangs?: HighlightLangMap;
60-
root?: string;
61-
[x: string]: unknown;
62-
}
5+
import initMarkdownit from './md';
636

64-
function transform(originInput: string, opts: OptionsType = {}): OutputType {
7+
function applyLiquid(input: string, options: OptionsType) {
658
const {
669
vars = {},
6710
path,
68-
extractTitle: extractTitleOption,
69-
needTitle,
70-
allowHTML = false,
71-
linkify = false,
72-
linkifyTlds,
73-
breaks = true,
7411
conditionsInCode = false,
75-
needToSanitizeHtml = false,
76-
sanitizeOptions,
77-
needFlatListHeadings = false,
7812
disableLiquid = false,
79-
leftDelimiter = '{',
80-
rightDelimiter = '}',
8113
isLiquided = false,
82-
plugins = [
83-
meta,
84-
deflist,
85-
cut,
86-
notes,
87-
anchors,
88-
tabs,
89-
code,
90-
sup,
91-
video,
92-
monospace,
93-
yfmTable,
94-
file,
95-
imsize,
96-
term,
97-
],
98-
highlightLangs = {},
99-
...customOptions
100-
} = opts;
14+
} = options;
10115

102-
const pluginOptions = {
103-
...customOptions,
104-
conditionsInCode,
105-
vars,
106-
path,
107-
extractTitle: extractTitleOption,
108-
disableLiquid,
109-
log,
110-
};
16+
return disableLiquid || isLiquided ? input : liquid(input, vars, path, {conditionsInCode});
17+
}
11118

112-
const input =
113-
disableLiquid || isLiquided
114-
? originInput
115-
: liquid(originInput, vars, path, {conditionsInCode});
19+
function handleError(error: unknown, path?: string): never {
20+
log.error(`Error occurred${path ? ` in ${bold(path)}` : ''}`);
11621

117-
const highlight = makeHighlight(highlightLangs);
118-
const md = initMd({html: allowHTML, linkify, highlight, breaks});
119-
// Need for ids of headers
120-
md.use(attrs, {leftDelimiter, rightDelimiter});
22+
throw error;
23+
}
12124

122-
plugins.forEach((plugin) => md.use(plugin, pluginOptions));
25+
function emitResult(html: string, env: EnvType): OutputType {
26+
return {
27+
result: {...env, html},
28+
logs: log.get(),
29+
};
30+
}
12331

124-
if (linkify && linkifyTlds) {
125-
md.linkify.tlds(linkifyTlds, true);
126-
}
32+
// eslint-disable-next-line consistent-return
33+
function transform(originInput: string, options: OptionsType = {}) {
34+
const input = applyLiquid(originInput, options);
35+
const {parse, compile, env} = initMarkdownit(options);
12736

12837
try {
129-
let title;
130-
let tokens;
131-
let titleTokens;
132-
const env = {} as {[key: string]: Token[] | unknown};
133-
134-
tokens = md.parse(input, env);
135-
136-
if (extractTitleOption) {
137-
({title, tokens, titleTokens} = extractTitle(tokens));
138-
139-
// title tokens include other tokens that need to be transformed
140-
if (titleTokens.length > 1) {
141-
title = md.renderer.render(titleTokens, md.options, env);
142-
}
143-
}
144-
if (needTitle) {
145-
({title} = extractTitle(tokens));
146-
}
147-
148-
const headings = getHeadings(tokens, needFlatListHeadings);
149-
150-
// add all term template tokens to the end of the html
151-
const termTokens = (env.termTokens as Token[]) || [];
152-
let html = md.renderer.render([...tokens, ...termTokens], md.options, env);
153-
if (needToSanitizeHtml) {
154-
html = sanitizeHtml(html, sanitizeOptions);
155-
}
156-
157-
const assets = md.assets;
158-
const meta = md.meta;
159-
160-
return {
161-
result: {html, title, headings, assets, meta},
162-
logs: log.get(),
163-
};
164-
} catch (err) {
165-
log.error(`Error occurred${path ? ` in ${bold(path)}` : ''}`);
166-
throw err;
38+
return emitResult(compile(parse(input)), env);
39+
} catch (error) {
40+
handleError(error, options.path);
16741
}
16842
}
16943

src/transform/md.ts

+116-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,117 @@
1-
import DefaultMarkdownIt, {Options} from 'markdown-it';
2-
import {MarkdownIt} from './typings';
1+
import type {MarkdownIt, OptionsType, EnvType} from './typings';
2+
import type Token from 'markdown-it/lib/token';
33

4-
export const initMd = ({html, linkify, highlight, breaks}: Partial<Options>) => {
5-
return new DefaultMarkdownIt({html, linkify, highlight, breaks}) as MarkdownIt;
6-
};
4+
import DefaultMarkdownIt from 'markdown-it';
5+
import DefaultPlugins from './plugins';
6+
import {log} from './log';
7+
import makeHighlight from './highlight';
8+
import attrs from 'markdown-it-attrs';
9+
import extractTitle from './title';
10+
import getHeadings from './headings';
11+
import sanitizeHtml from './sanitize';
12+
13+
function initMarkdownit(options: OptionsType) {
14+
const {allowHTML = false, linkify = false, breaks = true, highlightLangs = {}} = options;
15+
16+
const highlight = makeHighlight(highlightLangs);
17+
const md = new DefaultMarkdownIt({html: allowHTML, linkify, highlight, breaks}) as MarkdownIt;
18+
const env = {
19+
// TODO: move md.meta directly to env
20+
get meta() {
21+
return md.meta;
22+
},
23+
// TODO: move md.assets directly to env
24+
get assets() {
25+
return md.assets;
26+
},
27+
headings: [],
28+
title: '',
29+
} as EnvType;
30+
31+
initPlugins(md, options);
32+
33+
const parse = initParser(md, options, env);
34+
const compile = initCompiler(md, options, env);
35+
36+
return {parse, compile, env};
37+
}
38+
39+
function initPlugins(md: MarkdownIt, options: OptionsType) {
40+
const {
41+
vars = {},
42+
path,
43+
extractTitle,
44+
conditionsInCode = false,
45+
disableLiquid = false,
46+
linkify = false,
47+
linkifyTlds,
48+
leftDelimiter = '{',
49+
rightDelimiter = '}',
50+
plugins = DefaultPlugins,
51+
...customOptions
52+
} = options;
53+
54+
const pluginOptions = {
55+
...customOptions,
56+
conditionsInCode,
57+
vars,
58+
path,
59+
extractTitle,
60+
disableLiquid,
61+
log,
62+
};
63+
64+
// Need for ids of headers
65+
md.use(attrs, {leftDelimiter, rightDelimiter});
66+
67+
plugins.forEach((plugin) => md.use(plugin, pluginOptions));
68+
69+
if (linkify && linkifyTlds) {
70+
md.linkify.tlds(linkifyTlds, true);
71+
}
72+
}
73+
74+
function initParser(md: MarkdownIt, options: OptionsType, env: EnvType) {
75+
return (input: string) => {
76+
const {extractTitle: extractTitleOption, needTitle, needFlatListHeadings = false} = options;
77+
78+
let tokens = md.parse(input, env);
79+
80+
if (extractTitleOption) {
81+
const {title, tokens: slicedTokens, titleTokens} = extractTitle(tokens);
82+
83+
tokens = slicedTokens;
84+
85+
// title tokens include other tokens that need to be transformed
86+
if (titleTokens.length > 1) {
87+
env.title = md.renderer.render(titleTokens, md.options, env);
88+
} else {
89+
env.title = title;
90+
}
91+
}
92+
93+
if (needTitle) {
94+
env.title = extractTitle(tokens).title;
95+
}
96+
97+
env.headings = getHeadings(tokens, needFlatListHeadings);
98+
99+
return tokens;
100+
};
101+
}
102+
103+
function initCompiler(md: MarkdownIt, options: OptionsType, env: EnvType<{termTokens?: Token[]}>) {
104+
const {needToSanitizeHtml = false, sanitizeOptions} = options;
105+
106+
return (tokens: Token[]) => {
107+
// TODO: define postprocess step on term plugin
108+
const {termTokens = []} = env;
109+
delete env.termTokens;
110+
111+
const html = md.renderer.render([...tokens, ...termTokens], md.options, env);
112+
113+
return needToSanitizeHtml ? sanitizeHtml(html, sanitizeOptions) : html;
114+
};
115+
}
116+
117+
export = initMarkdownit;

src/transform/plugins.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type {MarkdownItPluginCb} from './plugins/typings';
2+
3+
import meta from './plugins/meta';
4+
import deflist from './plugins/deflist';
5+
import cut from './plugins/cut';
6+
import notes from './plugins/notes';
7+
import anchors from './plugins/anchors';
8+
import tabs from './plugins/tabs';
9+
import code from './plugins/code';
10+
import sup from './plugins/sup';
11+
import video from './plugins/video';
12+
import monospace from './plugins/monospace';
13+
import yfmTable from './plugins/table';
14+
import file from './plugins/file';
15+
import imsize from './plugins/imsize';
16+
import term from './plugins/term';
17+
18+
const defaultPlugins = [
19+
meta,
20+
deflist,
21+
cut,
22+
notes,
23+
anchors,
24+
tabs,
25+
code,
26+
sup,
27+
video,
28+
monospace,
29+
yfmTable,
30+
file,
31+
imsize,
32+
term,
33+
] as MarkdownItPluginCb[];
34+
35+
export = defaultPlugins;

src/transform/plugins/typings.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {Logger} from '../log';
2-
import {MarkdownIt} from '../typings';
1+
import type {Logger} from '../log';
2+
import type {MarkdownIt} from '../typings';
33

44
export interface MarkdownItPluginOpts {
55
path: string;
@@ -9,7 +9,6 @@ export interface MarkdownItPluginOpts {
99
isLintRun: boolean;
1010
}
1111

12-
export type MarkdownItPluginCb<T extends {} = {}> = (
13-
md: MarkdownIt,
14-
opts: T & MarkdownItPluginOpts,
15-
) => void;
12+
export type MarkdownItPluginCb<T extends {} = {}> = {
13+
(md: MarkdownIt, opts: T & MarkdownItPluginOpts): void;
14+
};

0 commit comments

Comments
 (0)