1
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
Theme: Default
|
||||
Description: Original highlight.js style
|
||||
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
|
||||
Maintainer: @highlightjs/core-team
|
||||
Website: https://highlightjs.org/
|
||||
License: see project LICENSE
|
||||
Touched: 2021
|
||||
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,201 @@
|
||||
import * as l from "./katex.js"
|
||||
const g = /([A-Z])/g, o = function(r) {
|
||||
return r.replace(g, "-$1").toLowerCase();
|
||||
}, p = {
|
||||
"&": "&",
|
||||
">": ">",
|
||||
"<": "<",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
}, d = /[&><"']/g;
|
||||
function h(r) {
|
||||
return String(r).replace(d, (t) => p[t]);
|
||||
}
|
||||
const y = (r) => "data:image/svg+xml," + encodeURIComponent(r.replace(/\s+/g, " ")), x = (r, t, e) => {
|
||||
let n = !1;
|
||||
t.classes && t.classes.length > 0 && (n = !0);
|
||||
const i = h(b(t.classes));
|
||||
let a = "";
|
||||
r === "text" && t.italic > 0 && (a += "margin-right:" + t.italic + "em;");
|
||||
for (const s in t.style)
|
||||
t.style.hasOwnProperty(s) && (a += `${o(s)}:${t.style[s]};`);
|
||||
a && (n = !0);
|
||||
for (const s in t.attributes)
|
||||
t.attributes.hasOwnProperty(s) && h(t.attributes[s]);
|
||||
if (r === "span")
|
||||
return {
|
||||
name: "span",
|
||||
attrs: {
|
||||
class: i + " katex-span",
|
||||
style: a
|
||||
},
|
||||
children: e
|
||||
};
|
||||
if (r === "img")
|
||||
return {
|
||||
name: "img",
|
||||
attrs: {
|
||||
class: i + " katex-img",
|
||||
style: a
|
||||
},
|
||||
children: e
|
||||
};
|
||||
if (r === "text") {
|
||||
const s = h(t.text);
|
||||
return n ? {
|
||||
name: "span",
|
||||
attrs: {
|
||||
class: i,
|
||||
style: a
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
text: s
|
||||
}
|
||||
]
|
||||
} : {
|
||||
type: "text",
|
||||
text: s
|
||||
};
|
||||
}
|
||||
if (r === "svg") {
|
||||
const s = t.toMarkup();
|
||||
return {
|
||||
name: "img",
|
||||
attrs: {
|
||||
src: y(s),
|
||||
class: "katex-svg"
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}, b = function(r) {
|
||||
return (r == null ? void 0 : r.filter((t) => t).join(" ")) ?? "";
|
||||
}, u = (r, t) => r.map((e) => {
|
||||
var s;
|
||||
let n = t;
|
||||
(s = e == null ? void 0 : e.style) != null && s.color && (n = e.style.color);
|
||||
let i;
|
||||
e instanceof l.__domTree.Span && (i = "span"), e instanceof l.__domTree.Anchor && (i = "anchor"), e instanceof l.__domTree.LineNode && (i = "line"), e instanceof l.__domTree.PathNode && (i = "path"), e instanceof l.__domTree.SvgNode && (i = "svg", n && (e.attributes.fill = n)), e instanceof l.__domTree.SymbolNode && (i = "text");
|
||||
const a = e.children && e.children.length > 0 ? u(e.children, n) : [];
|
||||
return i ? x(i, e, a) : a;
|
||||
}).reduce((e, n) => (Array.isArray(n) ? e.push(...n) : e.push(n), e), []).filter((e) => !!e), _ = (r, t = {}) => {
|
||||
const { throwError: e, ...n } = t || {};
|
||||
try {
|
||||
const i = l.__renderToDomTree(r, {
|
||||
...n,
|
||||
output: "html"
|
||||
});
|
||||
return u([i]);
|
||||
} catch (i) {
|
||||
if (e) throw i;
|
||||
return [
|
||||
{
|
||||
name: "span",
|
||||
attrs: {
|
||||
style: "color:red;"
|
||||
},
|
||||
children: [{ type: "text", text: i.message }]
|
||||
}
|
||||
];
|
||||
}
|
||||
}, w = function(r, t, e) {
|
||||
let n = e, i = 0;
|
||||
const a = r.length;
|
||||
for (; n < t.length; ) {
|
||||
const s = t[n];
|
||||
if (i <= 0 && t.slice(n, n + a) === r)
|
||||
return n;
|
||||
s === "\\" ? n++ : s === "{" ? i++ : s === "}" && i--, n++;
|
||||
}
|
||||
return -1;
|
||||
}, k = function(r) {
|
||||
return r.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
}, E = /^\\begin{/, T = function(r, t) {
|
||||
let e;
|
||||
const n = [], i = new RegExp(
|
||||
"(" + t.map((a) => k(a.left)).join("|") + ")"
|
||||
);
|
||||
for (; e = r.search(i), e !== -1; ) {
|
||||
e > 0 && (n.push({
|
||||
type: "text",
|
||||
data: r.slice(0, e)
|
||||
}), r = r.slice(e));
|
||||
const a = t.findIndex((c) => r.startsWith(c.left));
|
||||
if (e = w(t[a].right, r, t[a].left.length), e === -1)
|
||||
break;
|
||||
const s = r.slice(0, e + t[a].right.length), f = E.test(s) ? s : r.slice(t[a].left.length, e);
|
||||
n.push({
|
||||
type: "math",
|
||||
data: f,
|
||||
rawData: s,
|
||||
display: t[a].display
|
||||
}), r = r.slice(e + t[a].right.length);
|
||||
}
|
||||
return r !== "" && n.push({
|
||||
type: "text",
|
||||
data: r
|
||||
}), n;
|
||||
}, P = function(r, t) {
|
||||
var i;
|
||||
const e = T(
|
||||
r,
|
||||
(t == null ? void 0 : t.delimiters) ?? [
|
||||
{ left: "$$", right: "$$", display: !0 },
|
||||
{ left: "\\(", right: "\\)", display: !1 },
|
||||
{ left: "\\begin{equation}", right: "\\end{equation}", display: !0 },
|
||||
{ left: "\\begin{align}", right: "\\end{align}", display: !0 },
|
||||
{ left: "\\begin{alignat}", right: "\\end{alignat}", display: !0 },
|
||||
{ left: "\\begin{gather}", right: "\\end{gather}", display: !0 },
|
||||
{ left: "\\begin{CD}", right: "\\end{CD}", display: !0 },
|
||||
{ left: "\\[", right: "\\]", display: !0 }
|
||||
]
|
||||
);
|
||||
if (e.length === 1 && e[0].type === "text")
|
||||
return e[0].data;
|
||||
const n = [];
|
||||
for (let a = 0; a < e.length; a++)
|
||||
if (e[a].type === "text")
|
||||
n.push({
|
||||
type: "node",
|
||||
name: "span",
|
||||
attrs: {
|
||||
style: "white-space: pre-wrap;"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
text: e[a].data
|
||||
}
|
||||
]
|
||||
});
|
||||
else {
|
||||
const s = {
|
||||
...t
|
||||
};
|
||||
let f = e[a].data;
|
||||
s.displayMode = e[a].display;
|
||||
try {
|
||||
s.preProcess && (f = s.preProcess(f));
|
||||
const c = _(f, s);
|
||||
n.push(...c);
|
||||
} catch (c) {
|
||||
if (!(c instanceof l.ParseError))
|
||||
throw c;
|
||||
(i = t.errorCallback) == null || i.call(
|
||||
t,
|
||||
"KaTeX auto-render: Failed to parse `" + e[a].data + "` with ",
|
||||
c
|
||||
), n.push(e[a].rawData);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
};
|
||||
export {
|
||||
b as createClass,
|
||||
_ as default,
|
||||
_ as parseLatex,
|
||||
P as renderMathInText
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
+1316
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
|
||||
export function markedHighlight(options) {
|
||||
if (typeof options === 'function') {
|
||||
options = {
|
||||
highlight: options,
|
||||
};
|
||||
}
|
||||
|
||||
if (!options || typeof options.highlight !== 'function') {
|
||||
throw new Error('Must provide highlight function');
|
||||
}
|
||||
|
||||
if (typeof options.langPrefix !== 'string') {
|
||||
options.langPrefix = 'language-';
|
||||
}
|
||||
|
||||
if (typeof options.emptyLangClass !== 'string') {
|
||||
options.emptyLangClass = '';
|
||||
}
|
||||
|
||||
return {
|
||||
async: !!options.async,
|
||||
walkTokens(token) {
|
||||
if (token.type !== 'code') {
|
||||
return;
|
||||
}
|
||||
|
||||
const lang = getLang(token.lang);
|
||||
|
||||
if (options.async) {
|
||||
return Promise.resolve(options.highlight(token.text, lang, token.lang || '')).then(
|
||||
updateToken(token));
|
||||
}
|
||||
|
||||
const code = options.highlight(token.text, lang, token.lang || '');
|
||||
if (code instanceof Promise) {
|
||||
throw new Error(
|
||||
'markedHighlight is not set to async but the highlight function is async. Set the async option to true on markedHighlight to await the async highlight function.'
|
||||
);
|
||||
}
|
||||
updateToken(token)(code);
|
||||
},
|
||||
useNewRenderer: true,
|
||||
renderer: {
|
||||
code(code, infoString, escaped) {
|
||||
// istanbul ignore next
|
||||
if (typeof code === 'object') {
|
||||
escaped = code.escaped;
|
||||
infoString = code.lang;
|
||||
code = code.text;
|
||||
}
|
||||
const lang = getLang(infoString);
|
||||
const classValue = lang ? options.langPrefix + escape(lang) : options.emptyLangClass;
|
||||
const classAttr = classValue ?
|
||||
` class="${classValue}"` :
|
||||
'';
|
||||
code = code.replace(/\n$/, '');
|
||||
return `<pre class="pre"><code${classAttr} class="code">${escaped ? code : escape(code, true)}\n</code></pre>`;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getLang(lang) {
|
||||
return (lang || '').match(/\S*/)[0];
|
||||
}
|
||||
|
||||
function updateToken(token) {
|
||||
return (code) => {
|
||||
if (typeof code === 'string' && code !== token.text) {
|
||||
token.escaped = true;
|
||||
token.text = code;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// copied from marked helpers
|
||||
const escapeTest = /[&<>"']/;
|
||||
const escapeReplace = new RegExp(escapeTest.source, 'g');
|
||||
const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
|
||||
const escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, 'g');
|
||||
const escapeReplacements = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
};
|
||||
const getEscapeReplacement = (ch) => escapeReplacements[ch];
|
||||
|
||||
function escape(html, encode) {
|
||||
if (encode) {
|
||||
if (escapeTest.test(html)) {
|
||||
return html.replace(escapeReplace, getEscapeReplacement);
|
||||
}
|
||||
} else {
|
||||
if (escapeTestNoEncode.test(html)) {
|
||||
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
createClass,
|
||||
parseLatex,
|
||||
renderMathInText
|
||||
} from "./index.js"
|
||||
|
||||
export function treeToHtml(nodes) {
|
||||
// 如果输入是数组,处理数组中的每个节点
|
||||
if (Array.isArray(nodes)) {
|
||||
return nodes.map(node => treeToHtml(node)).join('');
|
||||
}
|
||||
|
||||
// 如果是文本节点
|
||||
if (nodes.type === 'text') {
|
||||
return nodes.text;
|
||||
}
|
||||
|
||||
// 处理HTML节点
|
||||
if (nodes.name) {
|
||||
// 构建开始标签
|
||||
let html = `<${nodes.name}`;
|
||||
|
||||
// 添加属性
|
||||
if (nodes.attrs) {
|
||||
for (const [key, value] of Object.entries(nodes.attrs)) {
|
||||
html += ` ${key}="${value}"`;
|
||||
}
|
||||
}
|
||||
|
||||
html += '>';
|
||||
|
||||
// 处理子节点
|
||||
if (nodes.children) {
|
||||
html += treeToHtml(nodes.children);
|
||||
}
|
||||
|
||||
// 添加结束标签
|
||||
html += `</${nodes.name}>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export function markedKatex(katex) {
|
||||
'use strict';
|
||||
|
||||
const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
|
||||
const inlineRuleNonStandard =
|
||||
/^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1/; // Non-standard, even if there are no spaces before and after $ or $$, try to parse
|
||||
|
||||
const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;
|
||||
|
||||
function index(options = {}) {
|
||||
return {
|
||||
extensions: [
|
||||
inlineKatex(options, createRenderer(options, false)),
|
||||
blockKatex(options, createRenderer(options, true)),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function createRenderer(options, newlineAfter) {
|
||||
|
||||
return (token) => katex.renderToString(token.text, {
|
||||
...options,
|
||||
displayMode: token.displayMode
|
||||
}) + (newlineAfter ? '\n' : '');
|
||||
}
|
||||
|
||||
function inlineKatex(options, renderer) {
|
||||
const nonStandard = options && options.nonStandard;
|
||||
const ruleReg = nonStandard ? inlineRuleNonStandard : inlineRule;
|
||||
return {
|
||||
name: 'inlineKatex',
|
||||
level: 'inline',
|
||||
start(src) {
|
||||
let index;
|
||||
let indexSrc = src;
|
||||
|
||||
while (indexSrc) {
|
||||
index = indexSrc.indexOf('$');
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
const f = nonStandard ? index > -1 : index === 0 || indexSrc.charAt(index - 1) === ' ';
|
||||
if (f) {
|
||||
const possibleKatex = indexSrc.substring(index);
|
||||
|
||||
if (possibleKatex.match(ruleReg)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, '');
|
||||
}
|
||||
},
|
||||
tokenizer(src, tokens) {
|
||||
const match = src.match(ruleReg);
|
||||
if (match) {
|
||||
return {
|
||||
type: 'inlineKatex',
|
||||
raw: match[0],
|
||||
text: match[2].trim(),
|
||||
displayMode: match[1].length === 2,
|
||||
};
|
||||
}
|
||||
},
|
||||
renderer,
|
||||
};
|
||||
}
|
||||
|
||||
function blockKatex(options, renderer) {
|
||||
return {
|
||||
name: 'blockKatex',
|
||||
level: 'block',
|
||||
tokenizer(src, tokens) {
|
||||
const match = src.match(blockRule);
|
||||
if (match) {
|
||||
return {
|
||||
type: 'blockKatex',
|
||||
raw: match[0],
|
||||
text: match[2].trim(),
|
||||
displayMode: match[1].length === 2,
|
||||
};
|
||||
}
|
||||
},
|
||||
renderer,
|
||||
};
|
||||
}
|
||||
|
||||
return index;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
|
||||
export function extendedTables({
|
||||
interruptPatterns = [],
|
||||
skipEmptyRows = true
|
||||
} = {}) {
|
||||
return {
|
||||
extensions: [{
|
||||
name: 'spanTable',
|
||||
level: 'block', // Is this a block-level or inline-level tokenizer?
|
||||
start(src) {
|
||||
return src.match(/\n *([^\n ].*\|.*)\n/m)?.index;
|
||||
}, // Hint to Marked.js to stop and check for a match
|
||||
tokenizer(src, tokens) {
|
||||
// const regex = this.tokenizer.rules.block.table;
|
||||
let regexString = '^ *([^\\n ].*\\|.*\\n(?: *[^\\s].*\\n)*?)' // Header
|
||||
+
|
||||
' {0,3}(?:\\| *)?(:?-+(?: *(?:100|[1-9][0-9]?%) *-+)?:? *(?:\\| *:?-+(?: *(?:100|[1-9][0-9]?%) *-+)?:? *)*)(?:\\| *)?' // Align
|
||||
+
|
||||
'(?:\\n((?:(?! *\\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})' // Cells
|
||||
+
|
||||
'(?:\\n+|$)| {0,3}#{1,6}(?:\\s|$)| {0,3}>| {4}[^\\n]| {0,3}(?:`{3,}' +
|
||||
'(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n| {0,3}(?:[*+-]|1[.)]) |' +
|
||||
'<\\/?(?:address|article|aside|base|basefont|blockquote|body' +
|
||||
'|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt' +
|
||||
'|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]' +
|
||||
'|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem' +
|
||||
'|meta|nav|noframes|ol|optgroup|option|p|param|section|source' +
|
||||
'|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)' +
|
||||
'(?: +|\\n|\\/?>)|<(?:script|pre|style|textarea|!--)endRegex).*(?:\\n|$))*)\\n*|$)'; // Cells
|
||||
|
||||
regexString = regexString.replace('endRegex', interruptPatterns.map(str =>
|
||||
`|(?:${str})`).join(''));
|
||||
const widthRegex = / *(?:100|[1-9][0-9]?%) */g;
|
||||
const regex = new RegExp(regexString);
|
||||
const cap = regex.exec(src);
|
||||
|
||||
if (cap) {
|
||||
const item = {
|
||||
type: 'spanTable',
|
||||
header: cap[1].replace(/\n$/, '').split('\n'),
|
||||
align: cap[2].replace(widthRegex, '').replace(/^ *|\| *$/g, '').split(
|
||||
/ *\| */),
|
||||
rows: cap[3]?.trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : [],
|
||||
width: cap[2].replace(/:/g, '').replace(/-+| /g, '').split('|')
|
||||
};
|
||||
|
||||
// Get first header row to determine how many columns
|
||||
item.header[0] = splitCells(item.header[0]);
|
||||
|
||||
const colCount = item.header[0].reduce((length, header) => {
|
||||
return length + header.colspan;
|
||||
}, 0);
|
||||
|
||||
if (colCount === item.align.length) {
|
||||
item.raw = cap[0];
|
||||
|
||||
let i, j, k, row;
|
||||
|
||||
// Get alignment row (:---:)
|
||||
let l = item.align.length;
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
if (/^ *-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = 'right';
|
||||
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = 'center';
|
||||
} else if (/^ *:-+ *$/.test(item.align[i])) {
|
||||
item.align[i] = 'left';
|
||||
} else {
|
||||
item.align[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Get any remaining header rows
|
||||
l = item.header.length;
|
||||
for (i = 1; i < l; i++) {
|
||||
item.header[i] = splitCells(item.header[i], colCount, item.header[i -
|
||||
1], skipEmptyRows);
|
||||
}
|
||||
|
||||
// Get main table cells
|
||||
l = item.rows.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
item.rows[i] = splitCells(item.rows[i], colCount, item.rows[i - 1],
|
||||
skipEmptyRows);
|
||||
}
|
||||
|
||||
// header child tokens
|
||||
l = item.header.length;
|
||||
for (j = 0; j < l; j++) {
|
||||
row = item.header[j];
|
||||
for (k = 0; k < row.length; k++) {
|
||||
row[k].tokens = [];
|
||||
this.lexer.inline(row[k].text, row[k].tokens);
|
||||
}
|
||||
}
|
||||
|
||||
// cell child tokens
|
||||
l = item.rows.length;
|
||||
for (j = 0; j < l; j++) {
|
||||
row = item.rows[j];
|
||||
for (k = 0; k < row.length; k++) {
|
||||
row[k].tokens = [];
|
||||
this.lexer.inline(row[k].text, row[k].tokens);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
},
|
||||
renderer(token) {
|
||||
|
||||
let i, j, row, cell, col, text;
|
||||
let output = '<table class="table">';
|
||||
output += '<thead class="thead">';
|
||||
for (i = 0; i < token.header.length; i++) {
|
||||
row = token.header[i];
|
||||
let col = 0;
|
||||
output += '<tr class="tr">';
|
||||
for (j = 0; j < row.length; j++) {
|
||||
cell = row[j];
|
||||
text = this.parser.parseInline(cell.tokens);
|
||||
output += getTableCell(text, cell, 'th', token.align[col], token.width[
|
||||
col]);
|
||||
col += cell.colspan;
|
||||
}
|
||||
output += '</tr>';
|
||||
}
|
||||
output += '</thead>';
|
||||
if (token.rows.length) {
|
||||
output += '<tbody class="tbody">';
|
||||
for (i = 0; i < token.rows.length; i++) {
|
||||
row = token.rows[i];
|
||||
col = 0;
|
||||
if (!row[0].emptyRow) {
|
||||
output += '<tr class="tr">';
|
||||
for (j = 0; j < row.length; j++) {
|
||||
cell = row[j];
|
||||
text = this.parser.parseInline(cell.tokens);
|
||||
output += getTableCell(text, cell, 'td', token.align[col], token
|
||||
.width[col]);
|
||||
col += cell.colspan;
|
||||
}
|
||||
output += '</tr>';
|
||||
}
|
||||
}
|
||||
output += '</tbody>';
|
||||
}
|
||||
output += '</table>';
|
||||
return output;
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const getTableCell = (text, cell, type, align, width) => {
|
||||
if (!cell.rowspan) {
|
||||
return '';
|
||||
}
|
||||
const tag = `<${type} class="${type}"` +
|
||||
`${cell.colspan > 1 ? ` colspan=${cell.colspan}` : ''}` +
|
||||
`${cell.rowspan > 1 ? ` rowspan=${cell.rowspan}` : ''}` +
|
||||
`${align ? ` align=${align}` : ''}` +
|
||||
`${width ? ` width=${width}` : ''}>`;
|
||||
return `${tag + text}</${type}>\n`;
|
||||
};
|
||||
|
||||
const splitCells = (tableRow, count, prevRow = [], skipEmptyRows) => {
|
||||
const cells = [...tableRow.trim().matchAll(/(?:[^|\\]|\\.?)+(?:\|+|$)/g)].map((x) => x[0]);
|
||||
|
||||
// Remove first/last cell in a row if whitespace only and no leading/trailing pipe
|
||||
if (!cells[0]?.trim()) {
|
||||
cells.shift();
|
||||
}
|
||||
if (!cells[cells.length - 1]?.trim()) {
|
||||
cells.pop();
|
||||
}
|
||||
|
||||
let numCols = 0;
|
||||
let i, j, trimmedCell, prevCell, prevCols;
|
||||
|
||||
for (i = 0; i < cells.length; i++) {
|
||||
trimmedCell = cells[i].split(/\|+$/)[0];
|
||||
cells[i] = {
|
||||
rowspan: 1,
|
||||
colspan: Math.max(cells[i].length - trimmedCell.length, 1),
|
||||
text: trimmedCell.trim().replace(/\\\|/g, '|')
|
||||
// display escaped pipes as normal character
|
||||
};
|
||||
|
||||
// Handle Rowspan
|
||||
if (trimmedCell.slice(-1) === '^' && prevRow.length) {
|
||||
// Find matching cell in previous row
|
||||
prevCols = 0;
|
||||
for (j = 0; j < prevRow.length; j++) {
|
||||
prevCell = prevRow[j];
|
||||
if ((prevCols === numCols) && (prevCell.colspan === cells[i].colspan)) {
|
||||
// merge into matching cell in previous row (the "target")
|
||||
cells[i].rowSpanTarget = prevCell.rowSpanTarget ?? prevCell;
|
||||
cells[i].rowSpanTarget.text += ` ${cells[i].text.slice(0, -1)}`;
|
||||
cells[i].rowSpanTarget.rowspan += 1;
|
||||
cells[i].rowspan = 0;
|
||||
break;
|
||||
}
|
||||
prevCols += prevCell.colspan;
|
||||
if (prevCols > numCols) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
numCols += cells[i].colspan;
|
||||
}
|
||||
|
||||
// If all cells have been merged, flag as an empty row
|
||||
if (cells.length > 0 && skipEmptyRows && cells.length === cells.filter((cell) => {
|
||||
return cell.rowspan === 0;
|
||||
}).length) {
|
||||
cells[0].emptyRow = true;
|
||||
for (i = 0; i < cells.length; i++) {
|
||||
cells[i].rowSpanTarget.rowspan -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Force main cell rows to match header column count
|
||||
if (numCols > count) {
|
||||
cells.splice(count);
|
||||
} else {
|
||||
while (numCols < count) {
|
||||
cells.push({
|
||||
rowspan: 1,
|
||||
colspan: 1,
|
||||
text: ''
|
||||
});
|
||||
numCols += 1;
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
};
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,469 @@
|
||||
<script lang="ts">
|
||||
import { type PropType } from "vue"
|
||||
import { getUid } from "../../core/util/xCoreUtil.uts"
|
||||
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
|
||||
import { checkIsCssUnit } from "../../core/util/xCoreUtil.uts"
|
||||
import { xConfig } from "../../config/xConfig.uts"
|
||||
// #ifdef MP
|
||||
import { Marked } from "./marked.uts"
|
||||
import { hljs } from "./highlight.min.js"
|
||||
import { markedHighlight } from "./marked-highlight.js"
|
||||
import * as katex from "./katex.js"
|
||||
import {
|
||||
createClass,
|
||||
parseLatex,
|
||||
renderMathInText
|
||||
} from "./index.js"
|
||||
import {markedKatex,treeToHtml} from "./marked-katex-extension.js"
|
||||
import { extendedTables } from "./marked-tables.js"
|
||||
|
||||
// #endif
|
||||
type DATATYP = {
|
||||
action : string
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 富文本 xMarkdown
|
||||
* @description 这是一个预览markdown的组件。当前支持markdown:表格,及数学公式的展示。
|
||||
* 【小程序端请注意】从1.1.14开始支持LaTex数学公式,但要注意数学公式暂时不要混合在表格内,会造成表格断列,样式异常。
|
||||
* 【组件目录内】有一个fonts.zip字体压缩包,请上传到你自己的服务器,并打开katex.min.css,把里面的字体链接换成你的字体连接,如果不换可能我服务器一关你就用不了了。
|
||||
* 传递正常markdown或者html内容即可,已经支持了暗黑适配,请自行配置样式。
|
||||
* 预览md:支持流式解析,支持动态解析内容.方便大家对话用.同时也支持了动态高,自动适配.
|
||||
* 同时也放开了内容复制(但会导致安卓(截止sdk4.53)页面滚动不了,需要你们自己解决:给这个组件盖个view屏蔽webview的事件,这是sdk底层问题,我修复不了.)
|
||||
* 1.1.10废弃了函数:getMarkdown
|
||||
* 所有平台的样式已经重置为none,你需要通过属性nodeStyle来设置默认样式.
|
||||
* @page /pages/zhanshi/markdown
|
||||
* @category 展示组件
|
||||
* @constant 平台兼容
|
||||
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
// #ifdef MP
|
||||
|
||||
const md = new Marked(markedHighlight({
|
||||
emptyLangClass: 'hljs',
|
||||
langPrefix: 'hljs language-',
|
||||
highlight(code, lang, info) {
|
||||
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
||||
return hljs.highlight(code, {
|
||||
language:"css"
|
||||
}).value;
|
||||
}
|
||||
}));
|
||||
|
||||
md.use(extendedTables())
|
||||
// md.use(markedKatex(katex)({
|
||||
// throwOnError: false
|
||||
// }));
|
||||
// #endif
|
||||
return {
|
||||
id: ("xEchart-" + getUid()) as string,
|
||||
webviewContext: null as WebviewContext | null,
|
||||
isLoaded: false,
|
||||
boxWidth: 10,
|
||||
boxHeight: 0,
|
||||
tid: 0,
|
||||
tid2: 0,
|
||||
realLoaded: false,
|
||||
isMp: false,
|
||||
// #ifdef MP
|
||||
markdownObj: md,
|
||||
htmlMpContent: ""
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
/**
|
||||
* 特定的a,img标签被点击触发,小程序不支持,其它平台支持.
|
||||
* @return {Object<{text,tag,attr}>}
|
||||
*/
|
||||
'tagClick',
|
||||
/**
|
||||
* 是否初始化成功
|
||||
*/
|
||||
'init',
|
||||
/**
|
||||
* 需要通过ref函数调用getHtml才会触发此函数
|
||||
*/
|
||||
'getValue'
|
||||
],
|
||||
props: {
|
||||
/**
|
||||
* 窗口宽
|
||||
*/
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
/**
|
||||
* 窗口高,auto时会自动适配高.
|
||||
*/
|
||||
height: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
/**
|
||||
* 需要渲染的markdow或者html内容。
|
||||
*/
|
||||
value: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否启用纯html渲染。如果你的内容含有特殊字符比如:%,^&%这种不要出现在里面
|
||||
* 此时你启用isHtml会经过数据处理直接跳过插件,直接赋值内容到html.就不要启用Markdown了.
|
||||
*/
|
||||
isHtml: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* 富文本的style样式,不可以动态更改.
|
||||
* 为了对齐所有端,默认已经把所有平台的样式删除.因此你可以自己设置默认样式来对齐所有平台.
|
||||
*/
|
||||
nodeStyle: {
|
||||
type: String,
|
||||
default: "line-height:1.6;color:#000"
|
||||
},
|
||||
/**
|
||||
* 同上,暗黑时的样式.
|
||||
*/
|
||||
nodeDarkStyle: {
|
||||
type: String,
|
||||
default: "line-height:1.6;color:#fff"
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
_width() : string {
|
||||
return checkIsCssUnit(this.width, xConfig.unit)
|
||||
},
|
||||
_height() : string {
|
||||
return checkIsCssUnit(this.height, xConfig.unit)
|
||||
},
|
||||
_value() : string {
|
||||
return this.value
|
||||
},
|
||||
_nodeStyle() : string {
|
||||
return xConfig.dark == 'dark' ? this.nodeDarkStyle : this.nodeStyle
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
// #ifdef APP||WEB
|
||||
this.drawer();
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
this.setContent(this.value);
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let t = this;
|
||||
// #ifdef MP
|
||||
t.isMp = true;
|
||||
// #endif
|
||||
t.isLoaded = true;
|
||||
this.tid = setTimeout(function () {
|
||||
// #ifdef APP
|
||||
t.webviewContext = uni.createWebviewContext(t.id, t);
|
||||
// #endif
|
||||
// #ifdef WEB
|
||||
t.webviewContext = document.getElementById(t.id) as HTMLElement;
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
|
||||
t.mpOnInit()
|
||||
// #endif
|
||||
}, 50);
|
||||
|
||||
this.onAddlisentMesage();
|
||||
|
||||
|
||||
},
|
||||
beforeMount() {
|
||||
clearTimeout(this.tid)
|
||||
},
|
||||
beforeUnmount() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
// #ifdef MP
|
||||
mpOnInit() {
|
||||
this.realLoaded = true;
|
||||
this.setContent(this.value);
|
||||
/**
|
||||
* 图表加载初始化完成后触发此事件。
|
||||
*/
|
||||
this.$emit("init")
|
||||
},
|
||||
htmlContentAddClass(content:string){
|
||||
// 匹配所有HTML标签
|
||||
const regex = /<([a-zA-Z0-9]+)([^>]*?)>/g;
|
||||
|
||||
return content.replace(regex, (match, tag, attributes) => {
|
||||
// 如果标签已经有class属性
|
||||
if(attributes.includes('class=')){
|
||||
// 在已有的class后面追加标签名作为类名
|
||||
return match.replace(/class=["']([^"']*)["']/, (m, cls) => {
|
||||
return `class="${cls} ${tag}"`;
|
||||
});
|
||||
}
|
||||
// 如果没有class属性,则添加一个新的class属性
|
||||
else {
|
||||
// 在标签结束前插入class属性
|
||||
return `<${tag}${attributes} class="${tag}">`;
|
||||
}
|
||||
});
|
||||
},
|
||||
setContent(str) {
|
||||
if (this.isHtml) {
|
||||
this.htmlMpContent = this.htmlContentAddClass(str)
|
||||
} else {
|
||||
let thtml = this.markdownObj.parse(this.value)
|
||||
// thtml = this.htmlContentAddClass(thtml)
|
||||
const nodes = renderMathInText(thtml, {
|
||||
delimiters: [
|
||||
{ left: "$$", right: "$$", display: true },
|
||||
{ left: "$", right: "$", display: false },
|
||||
],
|
||||
});
|
||||
|
||||
// const htmlcontent = this.markdownObj.parse(treeToHtml(thtml))
|
||||
this.htmlMpContent = treeToHtml(nodes)
|
||||
}
|
||||
|
||||
},
|
||||
onItemClick(event) {
|
||||
console.log(event)
|
||||
},
|
||||
|
||||
// #endif
|
||||
// h5端
|
||||
onAddlisentMesage() {
|
||||
// #ifdef WEB
|
||||
let t = this;
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.data.iframeId == t.id && event.data.action == 'onJSBridgeReady') {
|
||||
clearTimeout(t.tid2)
|
||||
t.tid2 = setTimeout(function () {
|
||||
t.realLoaded = true;
|
||||
t.drawer()
|
||||
t.$emit("init")
|
||||
t.eventJsCall('setBodyStyle', t._nodeStyle)
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (event.data.iframeId == t.id && event.data.action == 'offsetHeight') {
|
||||
t.boxHeight = event.data.data + 20
|
||||
|
||||
} else if (event.data.iframeId == t.id && event.data.action == 'toValue') {
|
||||
t.$emit('getValue', event.data.data)
|
||||
} else if (event.data.iframeId == t.id && event.data.action == 'click') {
|
||||
let dataStr = JSON.stringify(event.data.data);
|
||||
let dataJson = JSON.parseObject(dataStr)!
|
||||
t.$emit('tagClick', dataJson)
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
|
||||
onMessage(event : WebViewMessageEvent) {
|
||||
let t = this;
|
||||
|
||||
let msgdatas = event.detail.data
|
||||
|
||||
if (msgdatas.length == 0) return;
|
||||
|
||||
// #ifdef APP-ANDROID || APP-HARMONY
|
||||
if (msgdatas.length > 0) {
|
||||
let dataStr = JSON.stringify(event.detail);
|
||||
let dataJson = JSON.parseObject(dataStr)!
|
||||
let msgeAr = dataJson.getArray<UTSJSONObject>('data')!
|
||||
|
||||
let msg = msgeAr[0]!
|
||||
|
||||
let ac = msg["action"] as string;
|
||||
if (ac == 'offsetHeight') {
|
||||
const h = msg["data"]! as number;
|
||||
t.boxHeight = h + 25
|
||||
} else if (ac == 'toValue') {
|
||||
t.$emit('getValue', msg["data"]! as string)
|
||||
} else if (ac == 'click') {
|
||||
|
||||
t.$emit('tagClick', msg['data']! as UTSJSONObject)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-IOS
|
||||
if (msgdatas.length > 0) {
|
||||
let msg = msgdatas[0]
|
||||
let ac = msg['action'] as string;
|
||||
if (ac == 'offsetHeight') {
|
||||
t.boxHeight = (msg['data']! as Number) + 25
|
||||
} else if (ac == 'toValue') {
|
||||
t.$emit('getValue', msg['data'])
|
||||
} else if (ac == 'click') {
|
||||
let dataStr = JSON.stringify(msg['data']);
|
||||
let dataJson = JSON.parseObject(dataStr)!
|
||||
t.$emit('tagClick', dataJson)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
},
|
||||
|
||||
drawer() {
|
||||
if (!this.realLoaded) {
|
||||
uni.showToast({ title: "未初始化完成", icon: 'none' })
|
||||
return;
|
||||
}
|
||||
// #ifdef WEB
|
||||
this.eventJsCall('markdown', JSON.stringify({ value: encodeURIComponent(this._value), render: true }))
|
||||
// #endif
|
||||
// #ifdef APP-IOS
|
||||
this.eventJsCall('markdown', JSON.stringify({ value: encodeURIComponent(this._value), render: true }))
|
||||
// #endif
|
||||
// #ifdef APP-ANDROID || APP-HARMONY
|
||||
|
||||
this.eventJsCall('markdown', JSON.stringify({ value: this.isHtml ? btoa(encodeURIComponent(this._value)!) : this._value, render: this.isHtml ? 'android' : '' }))
|
||||
// #endif
|
||||
},
|
||||
|
||||
eventJsCall(callfun : string, str : string) {
|
||||
// #ifdef WEB
|
||||
var iframe = document.getElementById(this.id);
|
||||
if (!iframe) return;
|
||||
if ((iframe.contentWindow[callfun] || null)) {
|
||||
iframe.contentWindow[callfun](str, this.isHtml);
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
this.webviewContext?.evalJS(`${callfun}(${str},${this.isHtml})`)
|
||||
// #endif
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取html内容。注意本函数不会返回内容,你要通过事件getValue得到html内容.
|
||||
* @public
|
||||
*/
|
||||
getHtml() {
|
||||
// #ifdef WEB
|
||||
var iframe = document.getElementById(this.id);
|
||||
if (!iframe) return;
|
||||
iframe.contentWindow['getHtml']()
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
this.webviewContext?.evalJS(`getHtml()`)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
this.$emit('getValue', this.htmlContentAddClass(this.htmlMpContent))
|
||||
// #endif
|
||||
},
|
||||
appWebViewLoaded() {
|
||||
this.realLoaded = true;
|
||||
this.drawer()
|
||||
/**
|
||||
* 文档插件加载初始完成触发,这里响应完成后,你才要吧更新和修改数据。否则会报:未初始化。
|
||||
*/
|
||||
this.$emit("init")
|
||||
// setBodyStyle
|
||||
this.eventJsCall('setBodyStyle', this._nodeStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<view :style="{width:_width,height:boxHeight==0||isMp?_height:(boxHeight+'px')}">
|
||||
<view v-if="!isLoaded"
|
||||
style="width:100%;min-height:36px;display: flex;justify-content: center;align-items: center;flex-direction: row;">
|
||||
<x-icon color="primary" :spin="true" name="loader-4-line"></x-icon>
|
||||
</view>
|
||||
|
||||
<!-- #ifdef APP||WEB -->
|
||||
<web-view v-if="isLoaded" :horizontalScrollBarAccess="true" :verticalScrollBarAccess="false"
|
||||
class="xMarkdownNoevents" @load="appWebViewLoaded" :id="id" src="/hybrid/html/preview.html"
|
||||
:style="{width:'100%',height:'100%',opacity:isLoaded?1:0}" @message="onMessage"></web-view>
|
||||
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<rich-text class="richText" :style="_nodeStyle" @itemclick="onItemClick" :selectable="true"
|
||||
:nodes="htmlMpContent"></rich-text>
|
||||
<!-- #endif -->
|
||||
<!-- 兼容安卓。webview到4.19+页面无法滚动 -->
|
||||
<!-- #ifdef APP-ANDROID -->
|
||||
<!-- <view class="xMarkdownAndrod"></view> -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
/* #ifdef MP */
|
||||
@import url(./default.min.css);
|
||||
@import url(./katex.min.css);
|
||||
.table,.p,.div,.ul,.li,.h1,.h2,.h3,.h4,.h5,.h6,.blockquote,.code,.pre,.ol,.em,.strong,.hr,.img{
|
||||
margin:8px 0;
|
||||
}
|
||||
.pre{
|
||||
overflow:auto;
|
||||
background-color:#f3f3f3;
|
||||
padding:10px;
|
||||
|
||||
}
|
||||
|
||||
.hljs{
|
||||
&.language-css{
|
||||
|
||||
}
|
||||
.hljs-number{
|
||||
color:red;
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #ddd;
|
||||
/* 表格隔行变色 */
|
||||
.tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
}
|
||||
|
||||
.tr {
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.td,
|
||||
.th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px 8px;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.xMarkdownAndrod {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.xMarkdownNoevents {
|
||||
/* pointer-events: none; */
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user