Skip to content

Commit

Permalink
feat(md): Refactor transform index
Browse files Browse the repository at this point in the history
  • Loading branch information
3y3k0 authored and 3y3 committed Mar 28, 2023
1 parent ec1a536 commit 7df96ac
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 161 deletions.
174 changes: 24 additions & 150 deletions src/transform/index.ts
Original file line number Diff line number Diff line change
@@ -1,169 +1,43 @@
import type {OptionsType, OutputType, EnvType} from './typings';
import {bold} from 'chalk';
import attrs from 'markdown-it-attrs';
import Token from 'markdown-it/lib/token';

import {log, LogLevels} from './log';
import makeHighlight from './highlight';
import extractTitle from './title';
import getHeadings from './headings';
import {log} from './log';
import liquid from './liquid';
import sanitizeHtml, {SanitizeOptions} from './sanitize';

import notes from './plugins/notes';
import anchors from './plugins/anchors';
import code from './plugins/code';
import cut from './plugins/cut';
import deflist from './plugins/deflist';
import term from './plugins/term';
import file from './plugins/file';
import imsize from './plugins/imsize';
import meta from './plugins/meta';
import sup from './plugins/sup';
import tabs from './plugins/tabs';
import video from './plugins/video';
import monospace from './plugins/monospace';
import yfmTable from './plugins/table';
import {initMd} from './md';
import {MarkdownItPluginCb} from './plugins/typings';
import type {HighlightLangMap, Heading} from './typings';

interface OutputType {
result: {
html: string;
title?: string;
headings: Heading[];
assets?: unknown[];
meta?: object;
};
logs: Record<LogLevels, string[]>;
}
interface OptionsType {
vars?: Record<string, string>;
path?: string;
extractTitle?: boolean;
needTitle?: boolean;
allowHTML?: boolean;
linkify?: boolean;
linkifyTlds?: string | string[];
breaks?: boolean;
conditionsInCode?: boolean;
disableLiquid?: boolean;
leftDelimiter?: string;
rightDelimiter?: string;
isLiquided?: boolean;
needToSanitizeHtml?: boolean;
sanitizeOptions?: SanitizeOptions;
needFlatListHeadings?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
plugins?: MarkdownItPluginCb<any>[];
highlightLangs?: HighlightLangMap;
root?: string;
[x: string]: unknown;
}
import initMarkdownit from './md';

function transform(originInput: string, opts: OptionsType = {}): OutputType {
function applyLiquid(input: string, options: OptionsType) {
const {
vars = {},
path,
extractTitle: extractTitleOption,
needTitle,
allowHTML = false,
linkify = false,
linkifyTlds,
breaks = true,
conditionsInCode = false,
needToSanitizeHtml = false,
sanitizeOptions,
needFlatListHeadings = false,
disableLiquid = false,
leftDelimiter = '{',
rightDelimiter = '}',
isLiquided = false,
plugins = [
meta,
deflist,
cut,
notes,
anchors,
tabs,
code,
sup,
video,
monospace,
yfmTable,
file,
imsize,
term,
],
highlightLangs = {},
...customOptions
} = opts;
} = options;

const pluginOptions = {
...customOptions,
conditionsInCode,
vars,
path,
extractTitle: extractTitleOption,
disableLiquid,
log,
};
return disableLiquid || isLiquided ? input : liquid(input, vars, path, {conditionsInCode});
}

const input =
disableLiquid || isLiquided
? originInput
: liquid(originInput, vars, path, {conditionsInCode});
function handleError(error: unknown, path?: string): never {
log.error(`Error occurred${path ? ` in ${bold(path)}` : ''}`);

const highlight = makeHighlight(highlightLangs);
const md = initMd({html: allowHTML, linkify, highlight, breaks});
// Need for ids of headers
md.use(attrs, {leftDelimiter, rightDelimiter});
throw error;
}

plugins.forEach((plugin) => md.use(plugin, pluginOptions));
function emitResult(html: string, env: EnvType): OutputType {
return {
result: {...env, html},
logs: log.get(),
};
}

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

try {
let title;
let tokens;
let titleTokens;
const env = {} as {[key: string]: Token[] | unknown};

tokens = md.parse(input, env);

if (extractTitleOption) {
({title, tokens, titleTokens} = extractTitle(tokens));

// title tokens include other tokens that need to be transformed
if (titleTokens.length > 1) {
title = md.renderer.render(titleTokens, md.options, env);
}
}
if (needTitle) {
({title} = extractTitle(tokens));
}

const headings = getHeadings(tokens, needFlatListHeadings);

// add all term template tokens to the end of the html
const termTokens = (env.termTokens as Token[]) || [];
let html = md.renderer.render([...tokens, ...termTokens], md.options, env);
if (needToSanitizeHtml) {
html = sanitizeHtml(html, sanitizeOptions);
}

const assets = md.assets;
const meta = md.meta;

return {
result: {html, title, headings, assets, meta},
logs: log.get(),
};
} catch (err) {
log.error(`Error occurred${path ? ` in ${bold(path)}` : ''}`);
throw err;
return emitResult(compile(parse(input)), env);
} catch (error) {
handleError(error, options.path);
}
}

Expand Down
121 changes: 116 additions & 5 deletions src/transform/md.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,117 @@
import DefaultMarkdownIt, {Options} from 'markdown-it';
import {MarkdownIt} from './typings';
import type {MarkdownIt, OptionsType, EnvType} from './typings';
import type Token from 'markdown-it/lib/token';

export const initMd = ({html, linkify, highlight, breaks}: Partial<Options>) => {
return new DefaultMarkdownIt({html, linkify, highlight, breaks}) as MarkdownIt;
};
import DefaultMarkdownIt from 'markdown-it';
import DefaultPlugins from './plugins';
import {log} from './log';
import makeHighlight from './highlight';
import attrs from 'markdown-it-attrs';
import extractTitle from './title';
import getHeadings from './headings';
import sanitizeHtml from './sanitize';

function initMarkdownit(options: OptionsType) {
const {allowHTML = false, linkify = false, breaks = true, highlightLangs = {}} = options;

const highlight = makeHighlight(highlightLangs);
const md = new DefaultMarkdownIt({html: allowHTML, linkify, highlight, breaks}) as MarkdownIt;
const env = {
// TODO: move md.meta directly to env
get meta() {
return md.meta;
},
// TODO: move md.assets directly to env
get assets() {
return md.assets;
},
headings: [],
title: '',
} as EnvType;

initPlugins(md, options);

const parse = initParser(md, options, env);
const compile = initCompiler(md, options, env);

return {parse, compile, env};
}

function initPlugins(md: MarkdownIt, options: OptionsType) {
const {
vars = {},
path,
extractTitle,
conditionsInCode = false,
disableLiquid = false,
linkify = false,
linkifyTlds,
leftDelimiter = '{',
rightDelimiter = '}',
plugins = DefaultPlugins,
...customOptions
} = options;

const pluginOptions = {
...customOptions,
conditionsInCode,
vars,
path,
extractTitle,
disableLiquid,
log,
};

// Need for ids of headers
md.use(attrs, {leftDelimiter, rightDelimiter});

plugins.forEach((plugin) => md.use(plugin, pluginOptions));

if (linkify && linkifyTlds) {
md.linkify.tlds(linkifyTlds, true);
}
}

function initParser(md: MarkdownIt, options: OptionsType, env: EnvType) {
return (input: string) => {
const {extractTitle: extractTitleOption, needTitle, needFlatListHeadings = false} = options;

let tokens = md.parse(input, env);

if (extractTitleOption) {
const {title, tokens: slicedTokens, titleTokens} = extractTitle(tokens);

tokens = slicedTokens;

// title tokens include other tokens that need to be transformed
if (titleTokens.length > 1) {
env.title = md.renderer.render(titleTokens, md.options, env);
} else {
env.title = title;
}
}

if (needTitle) {
env.title = extractTitle(tokens).title;
}

env.headings = getHeadings(tokens, needFlatListHeadings);

return tokens;
};
}

function initCompiler(md: MarkdownIt, options: OptionsType, env: EnvType<{termTokens?: Token[]}>) {
const {needToSanitizeHtml = false, sanitizeOptions} = options;

return (tokens: Token[]) => {
// TODO: define postprocess step on term plugin
const {termTokens = []} = env;
delete env.termTokens;

const html = md.renderer.render([...tokens, ...termTokens], md.options, env);

return needToSanitizeHtml ? sanitizeHtml(html, sanitizeOptions) : html;
};
}

export = initMarkdownit;
35 changes: 35 additions & 0 deletions src/transform/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type {MarkdownItPluginCb} from './plugins/typings';

import meta from './plugins/meta';
import deflist from './plugins/deflist';
import cut from './plugins/cut';
import notes from './plugins/notes';
import anchors from './plugins/anchors';
import tabs from './plugins/tabs';
import code from './plugins/code';
import sup from './plugins/sup';
import video from './plugins/video';
import monospace from './plugins/monospace';
import yfmTable from './plugins/table';
import file from './plugins/file';
import imsize from './plugins/imsize';
import term from './plugins/term';

const defaultPlugins = [
meta,
deflist,
cut,
notes,
anchors,
tabs,
code,
sup,
video,
monospace,
yfmTable,
file,
imsize,
term,
] as MarkdownItPluginCb[];

export = defaultPlugins;
11 changes: 5 additions & 6 deletions src/transform/plugins/typings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Logger} from '../log';
import {MarkdownIt} from '../typings';
import type {Logger} from '../log';
import type {MarkdownIt} from '../typings';

export interface MarkdownItPluginOpts {
path: string;
Expand All @@ -9,7 +9,6 @@ export interface MarkdownItPluginOpts {
isLintRun: boolean;
}

export type MarkdownItPluginCb<T extends {} = {}> = (
md: MarkdownIt,
opts: T & MarkdownItPluginOpts,
) => void;
export type MarkdownItPluginCb<T extends {} = {}> = {
(md: MarkdownIt, opts: T & MarkdownItPluginOpts): void;
};
Loading

0 comments on commit 7df96ac

Please sign in to comment.