This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -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 = {
"&": "&amp;",
">": "&gt;",
"<": "&lt;",
'"': "&quot;",
"'": "&#x27;"
}, 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
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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
};
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>