220 lines
5.6 KiB
TypeScript
220 lines
5.6 KiB
TypeScript
import { _defaults } from './defaults.ts';
|
|
import {
|
|
cleanUrl,
|
|
escape,
|
|
} from './helpers.ts';
|
|
import { other } from './rules.ts';
|
|
import type { MarkedOptions } from './MarkedOptions.ts';
|
|
import type { Tokens } from './Tokens.ts';
|
|
import type { _Parser } from './Parser.ts';
|
|
|
|
/**
|
|
* Renderer
|
|
*/
|
|
export class _Renderer {
|
|
options: MarkedOptions;
|
|
parser!: _Parser; // set by the parser
|
|
constructor(options?: MarkedOptions) {
|
|
this.options = options || _defaults;
|
|
}
|
|
|
|
space(token: Tokens.Space): string {
|
|
return '';
|
|
}
|
|
|
|
code({ text, lang, escaped }: Tokens.Code): string {
|
|
const langString = (lang || '').match(other.notSpaceStart)?.[0];
|
|
|
|
const code = text.replace(other.endingNewline, '') + '\n';
|
|
|
|
if (!langString) {
|
|
return '<pre><code>'
|
|
+ (escaped ? code : escape(code, true))
|
|
+ '</code></pre>\n';
|
|
}
|
|
|
|
return '<pre><code class="language-'
|
|
+ escape(langString)
|
|
+ '">'
|
|
+ (escaped ? code : escape(code, true))
|
|
+ '</code></pre>\n';
|
|
}
|
|
|
|
blockquote({ tokens }: Tokens.Blockquote): string {
|
|
const body = this.parser.parse(tokens);
|
|
return `<blockquote>\n${body}</blockquote>\n`;
|
|
}
|
|
|
|
html({ text }: Tokens.HTML | Tokens.Tag) : string {
|
|
return text;
|
|
}
|
|
|
|
heading({ tokens, depth }: Tokens.Heading): string {
|
|
return `<h${depth}>${this.parser.parseInline(tokens)}</h${depth}>\n`;
|
|
}
|
|
|
|
hr(token: Tokens.Hr): string {
|
|
return '<hr>\n';
|
|
}
|
|
|
|
list(token: Tokens.List): string {
|
|
const ordered = token.ordered;
|
|
const start = token.start;
|
|
|
|
let body = '';
|
|
for (let j = 0; j < token.items.length; j++) {
|
|
const item = token.items[j];
|
|
body += this.listitem(item);
|
|
}
|
|
|
|
const type = ordered ? 'ol' : 'ul';
|
|
const startAttr = (ordered && start !== 1) ? (' start="' + start + '"') : '';
|
|
return '<' + type + startAttr + '>\n' + body + '</' + type + '>\n';
|
|
}
|
|
|
|
listitem(item: Tokens.ListItem): string {
|
|
let itemBody = '';
|
|
if (item.task) {
|
|
const checkbox = this.checkbox({ checked: !!item.checked });
|
|
if (item.loose) {
|
|
if (item.tokens[0]?.type === 'paragraph') {
|
|
item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
|
|
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
|
|
item.tokens[0].tokens[0].text = checkbox + ' ' + escape(item.tokens[0].tokens[0].text);
|
|
item.tokens[0].tokens[0].escaped = true;
|
|
}
|
|
} else {
|
|
item.tokens.unshift({
|
|
type: 'text',
|
|
raw: checkbox + ' ',
|
|
text: checkbox + ' ',
|
|
escaped: true,
|
|
});
|
|
}
|
|
} else {
|
|
itemBody += checkbox + ' ';
|
|
}
|
|
}
|
|
|
|
itemBody += this.parser.parse(item.tokens, !!item.loose);
|
|
|
|
return `<li>${itemBody}</li>\n`;
|
|
}
|
|
|
|
checkbox({ checked }: Tokens.Checkbox): string {
|
|
return '<input '
|
|
+ (checked ? 'checked="" ' : '')
|
|
+ 'disabled="" type="checkbox">';
|
|
}
|
|
|
|
paragraph({ tokens }: Tokens.Paragraph): string {
|
|
return `<p>${this.parser.parseInline(tokens)}</p>\n`;
|
|
}
|
|
|
|
table(token: Tokens.Table): string {
|
|
let header = '';
|
|
|
|
// header
|
|
let cell = '';
|
|
for (let j = 0; j < token.header.length; j++) {
|
|
cell += this.tablecell(token.header[j]);
|
|
}
|
|
header += this.tablerow({ text: cell });
|
|
|
|
let body = '';
|
|
for (let j = 0; j < token.rows.length; j++) {
|
|
const row = token.rows[j];
|
|
|
|
cell = '';
|
|
for (let k = 0; k < row.length; k++) {
|
|
cell += this.tablecell(row[k]);
|
|
}
|
|
|
|
body += this.tablerow({ text: cell });
|
|
}
|
|
if (body) body = `<tbody>${body}</tbody>`;
|
|
|
|
return '<table>\n'
|
|
+ '<thead>\n'
|
|
+ header
|
|
+ '</thead>\n'
|
|
+ body
|
|
+ '</table>\n';
|
|
}
|
|
|
|
tablerow({ text }: Tokens.TableRow): string {
|
|
return `<tr>\n${text}</tr>\n`;
|
|
}
|
|
|
|
tablecell(token: Tokens.TableCell): string {
|
|
const content = this.parser.parseInline(token.tokens);
|
|
const type = token.header ? 'th' : 'td';
|
|
const tag = token.align
|
|
? `<${type} align="${token.align}">`
|
|
: `<${type}>`;
|
|
return tag + content + `</${type}>\n`;
|
|
}
|
|
|
|
/**
|
|
* span level renderer
|
|
*/
|
|
strong({ tokens }: Tokens.Strong): string {
|
|
return `<strong>${this.parser.parseInline(tokens)}</strong>`;
|
|
}
|
|
|
|
em({ tokens }: Tokens.Em): string {
|
|
return `<em>${this.parser.parseInline(tokens)}</em>`;
|
|
}
|
|
|
|
codespan({ text }: Tokens.Codespan): string {
|
|
return `<code>${escape(text, true)}</code>`;
|
|
}
|
|
|
|
br(token: Tokens.Br): string {
|
|
return '<br>';
|
|
}
|
|
|
|
del({ tokens }: Tokens.Del): string {
|
|
return `<del>${this.parser.parseInline(tokens)}</del>`;
|
|
}
|
|
|
|
link({ href, title, tokens }: Tokens.Link): string {
|
|
const text = this.parser.parseInline(tokens);
|
|
const cleanHref = cleanUrl(href);
|
|
if (cleanHref === null) {
|
|
return text;
|
|
}
|
|
href = cleanHref;
|
|
let out = '<a href="' + href + '"';
|
|
if (title) {
|
|
out += ' title="' + (escape(title)) + '"';
|
|
}
|
|
out += '>' + text + '</a>';
|
|
return out;
|
|
}
|
|
|
|
image({ href, title, text, tokens }: Tokens.Image): string {
|
|
if (tokens) {
|
|
text = this.parser.parseInline(tokens, this.parser.textRenderer);
|
|
}
|
|
const cleanHref = cleanUrl(href);
|
|
if (cleanHref === null) {
|
|
return escape(text);
|
|
}
|
|
href = cleanHref;
|
|
|
|
let out = `<img src="${href}" alt="${text}"`;
|
|
if (title) {
|
|
out += ` title="${escape(title)}"`;
|
|
}
|
|
out += '>';
|
|
return out;
|
|
}
|
|
|
|
text(token: Tokens.Text | Tokens.Escape) : string {
|
|
return 'tokens' in token && token.tokens
|
|
? this.parser.parseInline(token.tokens)
|
|
: ('escaped' in token && token.escaped ? token.text : escape(token.text));
|
|
}
|
|
}
|