Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
ae7b491107
|
|||
8599460702
|
|||
fb52d31090
|
|||
859064f00b
|
|||
7803b495c2
|
|||
f48c0c6b75
|
|||
34cacb48bb
|
|||
7d5d29dade
|
|||
6ad670ba07
|
|||
3ddde81e1c
|
|||
44315b324a
|
|||
20cc517802
|
28
package-lock.json
generated
28
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@doc-utils/markdown2html",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.17",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@doc-utils/markdown2html",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.17",
|
||||
"dependencies": {
|
||||
"bytefield-svg": "^1.6.1",
|
||||
"dompurify": "^2.3.6",
|
||||
@@ -92,9 +92,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz",
|
||||
"integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==",
|
||||
"version": "18.16.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.6.tgz",
|
||||
"integrity": "sha512-N7KINmeB8IN3vRR8dhgHEp+YpWvGFcpDoh5XZ8jB5a00AdFKCKEyyGTOPTddUf4JqU1ZKTVxkOxakDvchNVI2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/prismjs": {
|
||||
@@ -989,9 +989,9 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
||||
"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
|
||||
"integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -2021,9 +2021,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.5.tgz",
|
||||
"integrity": "sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA==",
|
||||
"version": "18.16.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.6.tgz",
|
||||
"integrity": "sha512-N7KINmeB8IN3vRR8dhgHEp+YpWvGFcpDoh5XZ8jB5a00AdFKCKEyyGTOPTddUf4JqU1ZKTVxkOxakDvchNVI2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prismjs": {
|
||||
@@ -2674,9 +2674,9 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
||||
"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
|
||||
"integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@doc-utils/markdown2html",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.17",
|
||||
"publishConfig": {
|
||||
"registry": "https://gitea.home.jbrumond.me/api/packages/doc-utils/npm/"
|
||||
},
|
||||
|
63
src/breadcrumb-nav.ts
Normal file
63
src/breadcrumb-nav.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
import { marked } from 'marked';
|
||||
import { ParsedAttributes, parse_attributes } from './attrs';
|
||||
import { MarkdownOptions } from './render';
|
||||
|
||||
export interface BreadcrumbNavToken extends marked.Tokens.Generic {
|
||||
text: string;
|
||||
attrs: ParsedAttributes;
|
||||
items: marked.Token[][];
|
||||
}
|
||||
|
||||
export function breadcrumb_nav_ext(renderer: marked.Renderer, opts: MarkdownOptions) : marked.TokenizerExtension & marked.RendererExtension {
|
||||
return {
|
||||
name: 'breadcrumb_nav',
|
||||
level: 'block',
|
||||
start: (src) => src.match(/^\/\/\//)?.index,
|
||||
tokenizer(src, tokens) {
|
||||
const rule = /^\/\/\/(\/*)([^\n]+)?(?:\n)((?:[^\/]|\/\/?(?!\/\1))+)\/\/\/\1/;
|
||||
const match = rule.exec(src);
|
||||
|
||||
if (match) {
|
||||
const token: BreadcrumbNavToken = {
|
||||
type: 'breadcrumb_nav',
|
||||
raw: match[0],
|
||||
text: match[3],
|
||||
attrs: parse_attributes(match[2] || ''),
|
||||
tokens: [ ],
|
||||
items: [ ],
|
||||
};
|
||||
|
||||
const lines = match[3].trim().split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const tokens = this.lexer.inlineTokens(line, [ ]);
|
||||
token.tokens.push(...tokens);
|
||||
token.items.push(tokens);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
},
|
||||
renderer(token: BreadcrumbNavToken) {
|
||||
return `<nav aria-label="breadcrumbs" ${token.attrs.html_attrs.join(' ')}>\n`
|
||||
+ `\t<ol typeof="https://schema.org/BreadcrumbList">\n`
|
||||
+ '\t\t'
|
||||
+ token.items.map((tokens, index) =>{
|
||||
let item = '<li property="itemListElement" typeof="https://schema.org/ListItem">\n';
|
||||
|
||||
if (index) {
|
||||
item += '\t\t\t<span class="separator" aria-hidden="true">/</span>\n';
|
||||
}
|
||||
|
||||
item += `\t\t\t<span property="name">${this.parser.parseInline(tokens, renderer)}</span>\n`;
|
||||
item += `\t\t\t<meta property="position" content="${index + 1}">\n`;
|
||||
|
||||
return item + + '\t\t</li>';
|
||||
}).join('\n\t\t')
|
||||
+ '\n'
|
||||
+ `\t</ol>\n`
|
||||
+ `</nav>`;
|
||||
}
|
||||
};
|
||||
}
|
@@ -2,13 +2,15 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import createDOMPurify = require('dompurify');
|
||||
|
||||
export function sanitize_html(html: string) : string {
|
||||
export type CustomElementHandling = createDOMPurify.Config['CUSTOM_ELEMENT_HANDLING'];
|
||||
|
||||
export function sanitize_html(html: string, custom_elements?: CustomElementHandling) : string {
|
||||
const { window } = new JSDOM('');
|
||||
const dom_purify = createDOMPurify(window as any as Window);
|
||||
return dom_purify.sanitize(html, {
|
||||
CUSTOM_ELEMENT_HANDLING: {
|
||||
tagNameCheck: (tag_name) => tag_name === 'svg-icon',
|
||||
attributeNameCheck: (attr_name) => attr_name === 'icon',
|
||||
}
|
||||
CUSTOM_ELEMENT_HANDLING: custom_elements,
|
||||
ALLOWED_TAGS: ['meta'],
|
||||
ALLOWED_ATTR: ['typeof', 'property', 'content'],
|
||||
ADD_URI_SAFE_ATTR: ['typeof']
|
||||
});
|
||||
}
|
||||
|
@@ -92,16 +92,16 @@ const shape_fill_regex = {
|
||||
const arrow_head_regex: Record<CSSVarColor, RegExp> = {
|
||||
black: /fill:rgb\(0,0,0\)/gi,
|
||||
red: /fill:rgb\(255,0,0\)/gi,
|
||||
orange: /fill:rgb:\(255,165,0\)/gi,
|
||||
yellow: /fill:rgb:\(255,255,0\)/gi,
|
||||
green: /fill:rgb:\(0,128,0\)/gi,
|
||||
teal: /fill:rgb:\(0,128,128\)/gi,
|
||||
pink: /fill:rgb:\(255,192,203\)/gi,
|
||||
purple: /fill:rgb:\(128,0,128\)/gi,
|
||||
blue: /fill:rgb:\(0,0,255\)/gi,
|
||||
indigo: /fill:rgb:\(75,0,130\)/gi,
|
||||
magenta: /fill:rgb:\(255,0,255\)/gi,
|
||||
brown: /fill:rgb:\(165,42,42\)/gi,
|
||||
orange: /fill:rgb\(255,165,0\)/gi,
|
||||
yellow: /fill:rgb\(255,255,0\)/gi,
|
||||
green: /fill:rgb\(0,128,0\)/gi,
|
||||
teal: /fill:rgb\(0,128,128\)/gi,
|
||||
pink: /fill:rgb\(255,192,203\)/gi,
|
||||
purple: /fill:rgb\(128,0,128\)/gi,
|
||||
blue: /fill:rgb\(0,0,255\)/gi,
|
||||
indigo: /fill:rgb\(75,0,130\)/gi,
|
||||
magenta: /fill:rgb\(255,0,255\)/gi,
|
||||
brown: /fill:rgb\(165,42,42\)/gi,
|
||||
};
|
||||
|
||||
const line_css_vars: Record<CSSVarColor, string> = {
|
||||
|
@@ -4,11 +4,12 @@ import { create_renderer } from './renderer';
|
||||
import { mark_ext } from './mark';
|
||||
import { section_ext } from './section';
|
||||
import { icon_ext } from './icon';
|
||||
import { sanitize_html } from './html-sanitize';
|
||||
import { CustomElementHandling, sanitize_html } from './html-sanitize';
|
||||
import { katex_block_ext, katex_inline_ext } from './katex';
|
||||
import { footnote_list_ext, footnote_ref_ext } from './footnotes';
|
||||
import { description_list_ext } from './description-list';
|
||||
import { resolve_async_bindings } from './async-steps';
|
||||
import { breadcrumb_nav_ext } from './breadcrumb-nav';
|
||||
|
||||
export interface MarkdownOptions {
|
||||
base_url?: string;
|
||||
@@ -16,6 +17,7 @@ export interface MarkdownOptions {
|
||||
inline?: boolean;
|
||||
katex_macros?: Record<string, string>;
|
||||
extensions?: MarkdownExtension[];
|
||||
custom_elements?: CustomElementHandling;
|
||||
}
|
||||
|
||||
export interface MarkdownExtension {
|
||||
@@ -39,6 +41,7 @@ export async function render_markdown_to_html(markdown: string, options: Markdow
|
||||
description_list_ext(marked_options.renderer, options),
|
||||
section_ext(marked_options.renderer, options),
|
||||
icon_ext(marked_options.renderer, options),
|
||||
breadcrumb_nav_ext(marked_options.renderer, options),
|
||||
...(options.extensions || [ ]).map((ext) => {
|
||||
return ext(marked_options.renderer, options);
|
||||
}),
|
||||
@@ -64,5 +67,5 @@ export async function render_markdown_to_html(markdown: string, options: Markdow
|
||||
});
|
||||
});
|
||||
|
||||
return sanitize_html(unsafe_html);
|
||||
return sanitize_html(unsafe_html, options.custom_elements);
|
||||
}
|
||||
|
@@ -90,6 +90,8 @@ function code(renderer: marked.Renderer, opts: MarkdownOptions) {
|
||||
case 'bash:samp': {
|
||||
// Find the first newline that is not preceeded by a "\"
|
||||
const end_of_input = /(?<!\\)(?:\r\n|\r|\n)/.exec(code);
|
||||
|
||||
// todo: handling for multi-line heredocs?
|
||||
|
||||
// If there is no such newline, the whole content is input
|
||||
if (! end_of_input) {
|
||||
@@ -195,7 +197,7 @@ function code(renderer: marked.Renderer, opts: MarkdownOptions) {
|
||||
|
||||
const arg_pattern = /^(?:[a-zA-Z0-9_:-]+|"(?:[^"\n]|(?<=\\)")*")/;
|
||||
|
||||
function parse_code_args(text: string) {
|
||||
function parse_code_args(text = '') {
|
||||
const args: string[] = [ ];
|
||||
|
||||
text = text.trim();
|
||||
|
@@ -1,18 +1,27 @@
|
||||
|
||||
// note: fallback colors come from "category10" scheme
|
||||
// https://vega.github.io/vega/docs/schemes/#category10
|
||||
// todo: css variables
|
||||
export const chart_data_colors = [
|
||||
'var(--theme-chart-data-0, #1f77b4)',
|
||||
'var(--theme-chart-data-1, #ff7f0e)',
|
||||
'var(--theme-chart-data-2, #2ca02c)',
|
||||
'var(--theme-chart-data-3, #d62728)',
|
||||
'var(--theme-chart-data-4, #9467bd)',
|
||||
'var(--theme-chart-data-5, #8c564b)',
|
||||
'var(--theme-chart-data-6, #e377c2)',
|
||||
'var(--theme-chart-data-7, #7f7f7f)',
|
||||
'var(--theme-chart-data-8, #bcbd22)',
|
||||
'var(--theme-chart-data-9, #17becf)',
|
||||
'var(--theme-chart-shape-red-fill, #feaea5)',
|
||||
'var(--theme-chart-shape-orange-fill, #fad6bc)',
|
||||
'var(--theme-chart-shape-yellow-fill, #fffec6)',
|
||||
'var(--theme-chart-shape-green-fill, #d6f9d5)',
|
||||
'var(--theme-chart-shape-teal-fill, #b0ebe9)',
|
||||
'var(--theme-chart-shape-pink-fill, #ffcae2)',
|
||||
'var(--theme-chart-shape-purple-fill, #efdeff)',
|
||||
'var(--theme-chart-shape-blue-fill, #caebff)',
|
||||
'var(--theme-chart-shape-indigo-fill, #c2d6f9)',
|
||||
'var(--theme-chart-shape-magenta-fill, #ebb5cd)',
|
||||
'var(--theme-chart-shape-brown-fill, #e1c3b8)',
|
||||
'var(--theme-chart-shape-red-line, #ff806d)',
|
||||
'var(--theme-chart-shape-orange-line, #ffb780)',
|
||||
'var(--theme-chart-shape-yellow-line, #e1dc18)',
|
||||
'var(--theme-chart-shape-green-line, #66cc66)',
|
||||
'var(--theme-chart-shape-teal-line, #66cdcc)',
|
||||
'var(--theme-chart-shape-pink-line, #ff99cb)',
|
||||
'var(--theme-chart-shape-purple-line, #cc99fe)',
|
||||
'var(--theme-chart-shape-blue-line, #66cbff)',
|
||||
'var(--theme-chart-shape-indigo-line, #6295ee)',
|
||||
'var(--theme-chart-shape-magenta-line, #cc6698)',
|
||||
'var(--theme-chart-shape-brown-line, #bf8c71)',
|
||||
];
|
||||
|
||||
export function* chart_data_color_generator() : Generator<string, never> {
|
||||
|
Reference in New Issue
Block a user