feat: 替换了markdown 的组件
This commit is contained in:
parent
5321b27176
commit
cb22c0531c
@ -1 +0,0 @@
|
|||||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
|
||||||
Theme: GitHub Dark
|
|
||||||
Description: Dark theme as seen on github.com
|
|
||||||
Author: github.com
|
|
||||||
Maintainer: @Hirse
|
|
||||||
Updated: 2021-05-15
|
|
||||||
|
|
||||||
Outdated base version: https://github.com/primer/github-syntax-dark
|
|
||||||
Current colors taken from GitHub's CSS
|
|
||||||
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1,352 +0,0 @@
|
|||||||
/*
|
|
||||||
* HTML5 Parser By Sam Blowes
|
|
||||||
*
|
|
||||||
* Designed for HTML5 documents
|
|
||||||
*
|
|
||||||
* Original code by John Resig (ejohn.org)
|
|
||||||
* http://ejohn.org/blog/pure-javascript-html-parser/
|
|
||||||
* Original code by Erik Arvidsson, Mozilla Public License
|
|
||||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
||||||
*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* License
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* This code is triple licensed using Apache Software License 2.0,
|
|
||||||
* Mozilla Public License or GNU Public License
|
|
||||||
*
|
|
||||||
* ////////////////////////////////////////////////////////////////////////////
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy
|
|
||||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* ////////////////////////////////////////////////////////////////////////////
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License
|
|
||||||
* Version 1.1 (the "License"); you may not use this file except in
|
|
||||||
* compliance with the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS"
|
|
||||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing rights and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* The Original Code is Simple HTML Parser.
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is Erik Arvidsson.
|
|
||||||
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
|
|
||||||
* Reserved.
|
|
||||||
*
|
|
||||||
* ////////////////////////////////////////////////////////////////////////////
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* Usage
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* // Use like so:
|
|
||||||
* HTMLParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // or to get an XML string:
|
|
||||||
* HTMLtoXML(htmlString);
|
|
||||||
*
|
|
||||||
* // or to get an XML DOM Document
|
|
||||||
* HTMLtoDOM(htmlString);
|
|
||||||
*
|
|
||||||
* // or to inject into an existing document/DOM node
|
|
||||||
* HTMLtoDOM(htmlString, document);
|
|
||||||
* HTMLtoDOM(htmlString, document.body);
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// Regular Expressions for parsing tags and attributes
|
|
||||||
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
|
|
||||||
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
|
|
||||||
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
|
|
||||||
|
|
||||||
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
|
|
||||||
// fixed by xxx 将 ins 标签从块级名单中移除
|
|
||||||
|
|
||||||
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
|
|
||||||
|
|
||||||
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
|
|
||||||
// (and which close themselves)
|
|
||||||
|
|
||||||
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
|
|
||||||
|
|
||||||
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
|
|
||||||
|
|
||||||
var special = makeMap('script,style');
|
|
||||||
function HTMLParser(html, handler) {
|
|
||||||
var index;
|
|
||||||
var chars;
|
|
||||||
var match;
|
|
||||||
var stack = [];
|
|
||||||
var last = html;
|
|
||||||
|
|
||||||
stack.last = function () {
|
|
||||||
return this[this.length - 1];
|
|
||||||
};
|
|
||||||
|
|
||||||
while (html) {
|
|
||||||
chars = true; // Make sure we're not in a script or style element
|
|
||||||
|
|
||||||
if (!stack.last() || !special[stack.last()]) {
|
|
||||||
// Comment
|
|
||||||
if (html.indexOf('<!--') == 0) {
|
|
||||||
index = html.indexOf('-->');
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
if (handler.comment) {
|
|
||||||
handler.comment(html.substring(4, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
html = html.substring(index + 3);
|
|
||||||
chars = false;
|
|
||||||
} // end tag
|
|
||||||
|
|
||||||
} else if (html.indexOf('</') == 0) {
|
|
||||||
match = html.match(endTag);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
html = html.substring(match[0].length);
|
|
||||||
match[0].replace(endTag, parseEndTag);
|
|
||||||
chars = false;
|
|
||||||
} // start tag
|
|
||||||
|
|
||||||
} else if (html.indexOf('<') == 0) {
|
|
||||||
match = html.match(startTag);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
html = html.substring(match[0].length);
|
|
||||||
match[0].replace(startTag, parseStartTag);
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chars) {
|
|
||||||
index = html.indexOf('<');
|
|
||||||
var text = index < 0 ? html : html.substring(0, index);
|
|
||||||
html = index < 0 ? '' : html.substring(index);
|
|
||||||
|
|
||||||
if (handler.chars) {
|
|
||||||
handler.chars(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
|
|
||||||
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
|
|
||||||
|
|
||||||
if (handler.chars) {
|
|
||||||
handler.chars(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
parseEndTag('', stack.last());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (html == last) {
|
|
||||||
throw 'Parse Error: ' + html;
|
|
||||||
}
|
|
||||||
|
|
||||||
last = html;
|
|
||||||
} // Clean up any remaining tags
|
|
||||||
|
|
||||||
|
|
||||||
parseEndTag();
|
|
||||||
|
|
||||||
function parseStartTag(tag, tagName, rest, unary) {
|
|
||||||
tagName = tagName.toLowerCase();
|
|
||||||
|
|
||||||
if (block[tagName]) {
|
|
||||||
while (stack.last() && inline[stack.last()]) {
|
|
||||||
parseEndTag('', stack.last());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closeSelf[tagName] && stack.last() == tagName) {
|
|
||||||
parseEndTag('', tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
unary = empty[tagName] || !!unary;
|
|
||||||
|
|
||||||
if (!unary) {
|
|
||||||
stack.push(tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler.start) {
|
|
||||||
var attrs = [];
|
|
||||||
rest.replace(attr, function (match, name) {
|
|
||||||
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
|
|
||||||
attrs.push({
|
|
||||||
name: name,
|
|
||||||
value: value,
|
|
||||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (handler.start) {
|
|
||||||
handler.start(tagName, attrs, unary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEndTag(tag, tagName) {
|
|
||||||
// If no tag name is provided, clean shop
|
|
||||||
if (!tagName) {
|
|
||||||
var pos = 0;
|
|
||||||
} // Find the closest opened tag of the same type
|
|
||||||
else {
|
|
||||||
for (var pos = stack.length - 1; pos >= 0; pos--) {
|
|
||||||
if (stack[pos] == tagName) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos >= 0) {
|
|
||||||
// Close all the open elements, up the stack
|
|
||||||
for (var i = stack.length - 1; i >= pos; i--) {
|
|
||||||
if (handler.end) {
|
|
||||||
handler.end(stack[i]);
|
|
||||||
}
|
|
||||||
} // Remove the open elements from the stack
|
|
||||||
|
|
||||||
|
|
||||||
stack.length = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeMap(str) {
|
|
||||||
var obj = {};
|
|
||||||
var items = str.split(',');
|
|
||||||
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
obj[items[i]] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeDOCTYPE(html) {
|
|
||||||
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAttrs(attrs) {
|
|
||||||
return attrs.reduce(function (pre, attr) {
|
|
||||||
var value = attr.value;
|
|
||||||
var name = attr.name;
|
|
||||||
|
|
||||||
if (pre[name]) {
|
|
||||||
pre[name] = pre[name] + " " + value;
|
|
||||||
} else {
|
|
||||||
pre[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseHtml(html) {
|
|
||||||
html = removeDOCTYPE(html);
|
|
||||||
var stacks = [];
|
|
||||||
var results = {
|
|
||||||
node: 'root',
|
|
||||||
children: []
|
|
||||||
};
|
|
||||||
HTMLParser(html, {
|
|
||||||
start: function start(tag, attrs, unary) {
|
|
||||||
var node = {
|
|
||||||
name: tag
|
|
||||||
};
|
|
||||||
|
|
||||||
if (attrs.length !== 0) {
|
|
||||||
node.attrs = parseAttrs(attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unary) {
|
|
||||||
var parent = stacks[0] || results;
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.children.push(node);
|
|
||||||
} else {
|
|
||||||
stacks.unshift(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end: function end(tag) {
|
|
||||||
var node = stacks.shift();
|
|
||||||
if (node.name !== tag) console.error('invalid state: mismatch end tag');
|
|
||||||
|
|
||||||
if (stacks.length === 0) {
|
|
||||||
results.children.push(node);
|
|
||||||
} else {
|
|
||||||
var parent = stacks[0];
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.children.push(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chars: function chars(text) {
|
|
||||||
var node = {
|
|
||||||
type: 'text',
|
|
||||||
text: text
|
|
||||||
};
|
|
||||||
|
|
||||||
if (stacks.length === 0) {
|
|
||||||
results.children.push(node);
|
|
||||||
} else {
|
|
||||||
var parent = stacks[0];
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.children.push(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
comment: function comment(text) {
|
|
||||||
var node = {
|
|
||||||
node: 'comment',
|
|
||||||
text: text
|
|
||||||
};
|
|
||||||
var parent = stacks[0];
|
|
||||||
|
|
||||||
if (!parent.children) {
|
|
||||||
parent.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.children.push(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return results.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default parseHtml;
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1,318 +0,0 @@
|
|||||||
<!-- uniapp vue3 markdown解析 -->
|
|
||||||
<template>
|
|
||||||
<view class="ua__markdown"><rich-text space="nbsp" :nodes="parseNodes(source)" @itemclick="handleItemClick"></rich-text></view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import MarkdownIt from './lib/markdown-it.min.js'
|
|
||||||
import hljs from './lib/highlight/uni-highlight.min.js'
|
|
||||||
import './lib/highlight/atom-one-dark.css'
|
|
||||||
import parseHtml from './lib/html-parser.js'
|
|
||||||
const props = defineProps({
|
|
||||||
// 解析内容
|
|
||||||
source: String,
|
|
||||||
showLine: { type: [Boolean, String], default: true }
|
|
||||||
})
|
|
||||||
|
|
||||||
let copyCodeData = []
|
|
||||||
const markdown = MarkdownIt({
|
|
||||||
html: true,
|
|
||||||
highlight: function(str, lang) {
|
|
||||||
let preCode = ""
|
|
||||||
try {
|
|
||||||
preCode = hljs.highlightAuto(str).value
|
|
||||||
} catch (err) {
|
|
||||||
preCode = markdown.utils.escapeHtml(str);
|
|
||||||
}
|
|
||||||
const lines = preCode.split(/\n/).slice(0, -1)
|
|
||||||
// 添加自定义行号
|
|
||||||
let html = lines.map((item, index) => {
|
|
||||||
if( item == ''){
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
return '<li><span class="line-num" data-line="' + (index + 1) + '"></span>' + item +'</li>'
|
|
||||||
}).join('')
|
|
||||||
if(props.showLine) {
|
|
||||||
html = '<ol style="padding: 0px 30px;">' + html + '</ol>'
|
|
||||||
}else {
|
|
||||||
html = '<ol style="padding: 0px 7px;list-style:none;">' + html + '</ol>'
|
|
||||||
}
|
|
||||||
copyCodeData.push(str)
|
|
||||||
let htmlCode = `<div class="markdown-wrap">`
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
htmlCode += `<div style="color: #aaa;text-align: right;font-size: 12px;padding:8px;">`
|
|
||||||
htmlCode += `${lang}<a class="copy-btn" code-data-index="${copyCodeData.length - 1}" style="margin-left: 8px;">复制代码</a>`
|
|
||||||
htmlCode += `</div>`
|
|
||||||
// #endif
|
|
||||||
htmlCode += `<pre class="hljs" style="padding:10px 8px 0;margin-bottom:5px;overflow: auto;display: block;border-radius: 5px;"><code>${html}</code></pre>`;
|
|
||||||
htmlCode += '</div>'
|
|
||||||
return htmlCode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const parseNodes = (value) => {
|
|
||||||
if(!value) return
|
|
||||||
// 解析<br />到\n
|
|
||||||
value = value.replace(/<br>|<br\/>|<br \/>/g, "\n")
|
|
||||||
value = value.replace(/ /g, " ")
|
|
||||||
let htmlString = ''
|
|
||||||
if (value.split("```").length % 2) {
|
|
||||||
let mdtext = value
|
|
||||||
if(mdtext[mdtext.length-1] != '\n'){
|
|
||||||
mdtext += '\n'
|
|
||||||
}
|
|
||||||
htmlString = markdown.render(mdtext)
|
|
||||||
} else {
|
|
||||||
htmlString = markdown.render(value)
|
|
||||||
}
|
|
||||||
// 解决小程序表格边框型失效问题
|
|
||||||
htmlString = htmlString.replace(/<table/g, `<table class="table"`)
|
|
||||||
htmlString = htmlString.replace(/<tr/g, `<tr class="tr"`)
|
|
||||||
htmlString = htmlString.replace(/<th>/g, `<th class="th">`)
|
|
||||||
htmlString = htmlString.replace(/<td/g, `<td class="td"`)
|
|
||||||
htmlString = htmlString.replace(/<hr>|<hr\/>|<hr \/>/g, `<hr class="hr">`)
|
|
||||||
|
|
||||||
// #ifndef APP-NVUE
|
|
||||||
return htmlString
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 将htmlString转成htmlArray,反之使用rich-text解析
|
|
||||||
// #ifdef APP-NVUE
|
|
||||||
return parseHtml(htmlString)
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制代码
|
|
||||||
const handleItemClick = (e) => {
|
|
||||||
let {attrs} = e.detail.node
|
|
||||||
let {"code-data-index":codeDataIndex,"class":className} = attrs
|
|
||||||
if(className == 'copy-btn'){
|
|
||||||
uni.setClipboardData({
|
|
||||||
data: copyCodeData[codeDataIndex],showToast: false,
|
|
||||||
success() {
|
|
||||||
uni.showToast({
|
|
||||||
title: '复制成功',icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ua__markdown {
|
|
||||||
font-size: 14px;line-height: 1.5; word-break: break-all;
|
|
||||||
h1,h2,h3,h4,h5,h6 {
|
|
||||||
font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;
|
|
||||||
}
|
|
||||||
h1,h2,h3 {margin-top: 20px;margin-bottom: 10px}
|
|
||||||
h4,h5,h6 {margin-top: 10px;margin-bottom: 10px}
|
|
||||||
.h1,h1 {font-size: 36px
|
|
||||||
}
|
|
||||||
.h2,h2 {font-size: 30px
|
|
||||||
}
|
|
||||||
.h3,h3 {font-size: 24px
|
|
||||||
}
|
|
||||||
.h4,h4 {font-size: 18px
|
|
||||||
}
|
|
||||||
.h5,h5 {font-size: 14px
|
|
||||||
}
|
|
||||||
.h6,h6 {font-size: 12px
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
background-color: transparent;color: #2196f3;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
hr, ::v-deep .hr {
|
|
||||||
margin-top: 20px;margin-bottom: 20px; border: 0; border-top: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
img { max-width: 35%;
|
|
||||||
}
|
|
||||||
p {margin: 0 0 10px}
|
|
||||||
em {
|
|
||||||
font-style: italic; font-weight: inherit;
|
|
||||||
}
|
|
||||||
ol,ul {
|
|
||||||
margin-top: 0; margin-bottom: 10px;padding-left: 40px;
|
|
||||||
}
|
|
||||||
ol ol,ol ul,ul ol,ul ul {margin-bottom: 0;
|
|
||||||
}
|
|
||||||
ol ol, ul ol {list-style-type: lower-roman;
|
|
||||||
}
|
|
||||||
ol ol ol, ul ul ol {list-style-type: lower-alpha;
|
|
||||||
}
|
|
||||||
dl {
|
|
||||||
margin-top: 0;margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
dt {font-weight: 600;
|
|
||||||
}
|
|
||||||
dt, dd {line-height: 1.4;
|
|
||||||
}
|
|
||||||
.task-list-item { list-style-type: none;
|
|
||||||
}
|
|
||||||
.task-list-item input {
|
|
||||||
margin: 0 .2em .25em -1.6em;vertical-align: middle;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
position: relative; z-index: 11;
|
|
||||||
}
|
|
||||||
code,kbd,pre,samp { font-family: Menlo,Monaco,Consolas,"Courier New",monospace;}
|
|
||||||
code:not(.hljs) {
|
|
||||||
padding: 2px 4px;font-size: 90%;color: #c7254e;background-color: #ffe7ee;border-radius: 4px;
|
|
||||||
}
|
|
||||||
code:empty {display: none;
|
|
||||||
}
|
|
||||||
pre code.hljs {
|
|
||||||
color: var(--vg__text-1); border-radius: 16px; background: var(--vg__bg-1);font-size: 12px;
|
|
||||||
}
|
|
||||||
.markdown-wrap {
|
|
||||||
font-size: 12px;margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
pre.code-block-wrapper {background: #2b2b2b;color: #f8f8f2;border-radius: 4px;overflow-x: auto;
|
|
||||||
padding: 1em;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
pre.code-block-wrapper code {
|
|
||||||
padding: auto;
|
|
||||||
font-size: inherit;
|
|
||||||
color: inherit;
|
|
||||||
background-color: inherit;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.code-block-header__copy {
|
|
||||||
font-size: 16px;margin-left: 5px;
|
|
||||||
}
|
|
||||||
abbr[data-original-title],abbr[title] {
|
|
||||||
cursor: help;border-bottom: 1px dotted #777;
|
|
||||||
}
|
|
||||||
blockquote {
|
|
||||||
padding: 10px 20px;margin: 0 0 20px;font-size: 17.5px;
|
|
||||||
border-left: 5px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child {
|
|
||||||
margin-bottom: 0
|
|
||||||
}
|
|
||||||
blockquote .small,blockquote footer,blockquote small {
|
|
||||||
display: block;font-size: 80%;line-height: 1.42857143;color: #777
|
|
||||||
}
|
|
||||||
blockquote .small:before,blockquote footer:before,blockquote small:before {
|
|
||||||
content: '\2014 \00A0'
|
|
||||||
}
|
|
||||||
.blockquote-reverse,blockquote.pull-right {
|
|
||||||
padding-right: 15px; padding-left: 0;
|
|
||||||
text-align: right;border-right: 5px solid #eee;border-left: 0
|
|
||||||
}
|
|
||||||
.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before {
|
|
||||||
content: ''
|
|
||||||
}
|
|
||||||
.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after {
|
|
||||||
content: '\00A0 \2014'
|
|
||||||
}
|
|
||||||
.footnotes {
|
|
||||||
-moz-column-count: 2;
|
|
||||||
-webkit-column-count: 2;
|
|
||||||
column-count: 2
|
|
||||||
}
|
|
||||||
.footnotes-list {padding-left: 2em}
|
|
||||||
table, ::v-deep .table {
|
|
||||||
border-spacing: 0;border-collapse: collapse; width: 100%;max-width: 65em; overflow: auto;margin-top: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
table tr, ::v-deep .table .tr {
|
|
||||||
border-top: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
table th, table td, ::v-deep .table .th, ::v-deep .table .td {
|
|
||||||
padding: 6px 13px;border: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
table th, ::v-deep .table .th {
|
|
||||||
font-weight: 600;background-color: #eee;
|
|
||||||
}
|
|
||||||
.hljs[class*=language-]:before {
|
|
||||||
position: absolute; z-index: 3;top: .8em; right: 1em; font-size: .8em; color: #999;
|
|
||||||
}
|
|
||||||
.hljs[class~=language-js]:before {
|
|
||||||
content: "js"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-ts]:before {
|
|
||||||
content: "ts"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-html]:before {
|
|
||||||
content: "html"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-md]:before {
|
|
||||||
content: "md"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-vue]:before {
|
|
||||||
content: "vue"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-css]:before {
|
|
||||||
content: "css"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-sass]:before {
|
|
||||||
content: "sass"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-scss]:before {
|
|
||||||
content: "scss"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-less]:before {
|
|
||||||
content: "less"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-stylus]:before {
|
|
||||||
content: "stylus"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-go]:before {
|
|
||||||
content: "go"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-java]:before {
|
|
||||||
content: "java"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-c]:before {
|
|
||||||
content: "c"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-sh]:before {
|
|
||||||
content: "sh"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-yaml]:before {
|
|
||||||
content: "yaml"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-py]:before {
|
|
||||||
content: "py"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-docker]:before {
|
|
||||||
content: "docker"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-dockerfile]:before {
|
|
||||||
content: "dockerfile"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-makefile]:before {
|
|
||||||
content: "makefile"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-javascript]:before {
|
|
||||||
content: "js"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-typescript]:before {
|
|
||||||
content: "ts"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-markup]:before {
|
|
||||||
content: "html"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-markdown]:before {
|
|
||||||
content: "md"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-json]:before {
|
|
||||||
content: "json"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-ruby]:before {
|
|
||||||
content: "rb"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-python]:before {
|
|
||||||
content: "py"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-bash]:before {
|
|
||||||
content: "sh"
|
|
||||||
}
|
|
||||||
.hljs[class~=language-php]:before {
|
|
||||||
content: "php"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -20,8 +20,8 @@
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.chat-ai {
|
.chat-ai {
|
||||||
margin: 6px 12px;
|
margin: 6px 12px;
|
||||||
padding: 16px;
|
padding: 0 16px;
|
||||||
min-width: 60px;
|
min-width: 80px;
|
||||||
|
|
||||||
background: rgba(255,255,255,0.4);
|
background: rgba(255,255,255,0.4);
|
||||||
box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.1);
|
box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.1);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view>
|
<view>
|
||||||
<ua-markdown :source="text" />
|
<zero-markdown-view :markdown="text" :aiMode='true'></zero-markdown-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
22
uni_modules/zero-markdown-view/changelog.md
Normal file
22
uni_modules/zero-markdown-view/changelog.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
## 3.0.0(2025-07-17)
|
||||||
|
### 新增 aiMode 模式
|
||||||
|
### aiMode模式 优化流式输出以及排版
|
||||||
|
## 2.1.0(2025-05-23)
|
||||||
|
- 新增latex公式支持
|
||||||
|
- 代码块高亮支持增加除js外的语法
|
||||||
|
- 注意:包体积增加了,建议在分包内使用
|
||||||
|
## 2.0.5(2024-04-24)
|
||||||
|
- 流式输出代码块解决方案
|
||||||
|
## 2.0.4(2023-12-06)
|
||||||
|
- 长按复制代码改为点击代码块复制
|
||||||
|
## 2.0.3(2023-10-30)
|
||||||
|
doc: 文档说明
|
||||||
|
## 2.0.2(2023-10-30)
|
||||||
|
- 新增长按复制代码-仅小程序可用
|
||||||
|
- 新增代码块语言显示
|
||||||
|
## 2.0.1(2023-10-27)
|
||||||
|
##支持vue2,vue3
|
||||||
|
## 2.0.0(2022-11-01)
|
||||||
|
使用mp-html自带的插件,重新生成uniapp包,大幅减少插件体积
|
||||||
|
## 1.0.0(2022-09-13)
|
||||||
|
首次发布
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
// copyByLongPress: false, // 是否需要长按代码块时显示复制代码内容菜单
|
||||||
|
copyByClickCode: true, // 点击代码块复制
|
||||||
|
showLanguageName: true, // 是否在代码块右上角显示语言的名称
|
||||||
|
// showLineNumber: false // 是否显示行号,需要重新打包mp-html
|
||||||
|
}
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview highlight 插件
|
||||||
|
* Include prismjs (https://prismjs.com)
|
||||||
|
*/
|
||||||
|
import prism from "./prism.min";
|
||||||
|
import config from "./config";
|
||||||
|
import Parser from "../parser";
|
||||||
|
|
||||||
|
function Highlight(vm) {
|
||||||
|
this.vm = vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
Highlight.prototype.onParse = function (node, vm) {
|
||||||
|
if (node.name === "pre") {
|
||||||
|
if (vm.options.editable) {
|
||||||
|
node.attrs.class = (node.attrs.class || "") + " hl-pre";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i;
|
||||||
|
for (i = node.children.length; i--; ) {
|
||||||
|
if (node.children[i].name === "code") break;
|
||||||
|
}
|
||||||
|
if (i === -1) return;
|
||||||
|
const code = node.children[i];
|
||||||
|
let className = code.attrs.class + " " + node.attrs.class;
|
||||||
|
i = className.indexOf("language-");
|
||||||
|
if (i === -1) {
|
||||||
|
i = className.indexOf("lang-");
|
||||||
|
if (i === -1) {
|
||||||
|
className = "language-text";
|
||||||
|
i = 9;
|
||||||
|
} else {
|
||||||
|
i += 5;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i += 9;
|
||||||
|
}
|
||||||
|
let j;
|
||||||
|
for (j = i; j < className.length; j++) {
|
||||||
|
if (className[j] === " ") break;
|
||||||
|
}
|
||||||
|
const lang = className.substring(i, j);
|
||||||
|
if (code.children.length) {
|
||||||
|
const text = this.vm.getText(code.children).replace(/&/g, "&");
|
||||||
|
if (!text) return;
|
||||||
|
if (node.c) {
|
||||||
|
node.c = undefined;
|
||||||
|
}
|
||||||
|
if (prism.languages[lang]) {
|
||||||
|
code.children = new Parser(this.vm).parse(
|
||||||
|
// 加一层 pre 保留空白符
|
||||||
|
"<pre>" +
|
||||||
|
prism
|
||||||
|
.highlight(text, prism.languages[lang], lang)
|
||||||
|
.replace(/token /g, "hl-") +
|
||||||
|
"</pre>"
|
||||||
|
)[0].children;
|
||||||
|
}
|
||||||
|
node.attrs.class = "hl-pre";
|
||||||
|
code.attrs.class = "hl-code";
|
||||||
|
code.attrs.style = "display:block;overflow: auto;";
|
||||||
|
|
||||||
|
if (config.showLanguageName) {
|
||||||
|
node.children.push({
|
||||||
|
name: "div",
|
||||||
|
attrs: {
|
||||||
|
class: "hl-language",
|
||||||
|
style:
|
||||||
|
"user-select:none;position:absolute;top:0;right:2px;font-size:10px;",
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: lang,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.copyByClickCode) {
|
||||||
|
node.attrs.style += (node.attrs.style || "") + ";user-select:none";
|
||||||
|
node.attrs["data-content"] = text;
|
||||||
|
node.children.push({
|
||||||
|
name: "div",
|
||||||
|
attrs: {
|
||||||
|
class: "hl-copy",
|
||||||
|
style:
|
||||||
|
"user-select:none;position:absolute;top:0;right:3px;font-size:10px;",
|
||||||
|
},
|
||||||
|
// children: [{
|
||||||
|
// type: 'text',
|
||||||
|
// text: '复制'
|
||||||
|
// }]
|
||||||
|
});
|
||||||
|
vm.expose();
|
||||||
|
}
|
||||||
|
if (config.showLineNumber) {
|
||||||
|
const line = text.split("\n").length;
|
||||||
|
const children = [];
|
||||||
|
for (let k = line; k--; ) {
|
||||||
|
children.push({
|
||||||
|
name: "span",
|
||||||
|
attrs: {
|
||||||
|
class: "span",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
node.children.push({
|
||||||
|
name: "span",
|
||||||
|
attrs: {
|
||||||
|
class: "line-numbers-rows",
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Highlight;
|
||||||
15
uni_modules/zero-markdown-view/components/mp-html/highlight/prism.min.js
vendored
Normal file
15
uni_modules/zero-markdown-view/components/mp-html/highlight/prism.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview latex 插件
|
||||||
|
* katex.min.js来源 https://github.com/rojer95/katex-mini
|
||||||
|
*/
|
||||||
|
import parse from './katex.min'
|
||||||
|
|
||||||
|
function Latex () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Latex.prototype.onParse = function (node, vm) {
|
||||||
|
// $...$包裹的内容为latex公式
|
||||||
|
if (!vm.options.editable && node.type === 'text' && node.text.includes('$')) {
|
||||||
|
const part = node.text.split(/(\${1,2})/)
|
||||||
|
const children = []
|
||||||
|
let status = 0
|
||||||
|
for (let i = 0; i < part.length; i++) {
|
||||||
|
if (i % 2 === 0) {
|
||||||
|
// 文本内容
|
||||||
|
if (part[i]) {
|
||||||
|
if (status === 0) {
|
||||||
|
children.push({
|
||||||
|
type: 'text',
|
||||||
|
text: part[i]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (status === 1) {
|
||||||
|
// 行内公式
|
||||||
|
const nodes = parse.default(part[i])
|
||||||
|
children.push({
|
||||||
|
name: 'span',
|
||||||
|
attrs: {},
|
||||||
|
l: 'T',
|
||||||
|
f: 'display:inline-block',
|
||||||
|
children: nodes
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 块公式
|
||||||
|
const nodes = parse.default(part[i], {
|
||||||
|
displayMode: true
|
||||||
|
})
|
||||||
|
children.push({
|
||||||
|
name: 'div',
|
||||||
|
attrs: {
|
||||||
|
style: 'text-align:center'
|
||||||
|
},
|
||||||
|
children: nodes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 分隔符
|
||||||
|
if (part[i] === '$' && part[i + 2] === '$') {
|
||||||
|
// 行内公式
|
||||||
|
status = 1
|
||||||
|
part[i + 2] = ''
|
||||||
|
} else if (part[i] === '$$' && part[i + 2] === '$$') {
|
||||||
|
// 块公式
|
||||||
|
status = 2
|
||||||
|
part[i + 2] = ''
|
||||||
|
} else {
|
||||||
|
if (part[i] && part[i] !== '$$') {
|
||||||
|
// 普通$符号
|
||||||
|
part[i + 1] = part[i] + part[i + 1]
|
||||||
|
}
|
||||||
|
// 重置状态
|
||||||
|
status = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete node.type
|
||||||
|
delete node.text
|
||||||
|
node.name = 'span'
|
||||||
|
node.attrs = {}
|
||||||
|
node.children = children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Latex
|
||||||
1
uni_modules/zero-markdown-view/components/mp-html/latex/katex.min.js
vendored
Normal file
1
uni_modules/zero-markdown-view/components/mp-html/latex/katex.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview markdown 插件
|
||||||
|
* Include marked (https://github.com/markedjs/marked)
|
||||||
|
* Include github-markdown-css (https://github.com/sindresorhus/github-markdown-css)
|
||||||
|
*/
|
||||||
|
import marked from './marked.min'
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
function Markdown (vm) {
|
||||||
|
this.vm = vm
|
||||||
|
vm._ids = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Markdown.prototype.onUpdate = function (content) {
|
||||||
|
if (this.vm.markdown) {
|
||||||
|
return marked(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Markdown.prototype.onParse = function (node, vm) {
|
||||||
|
if (vm.options.markdown) {
|
||||||
|
// 中文 id 需要转换,否则无法跳转
|
||||||
|
if (vm.options.useAnchor && node.attrs && /[\u4e00-\u9fa5]/.test(node.attrs.id)) {
|
||||||
|
const id = 't' + index++
|
||||||
|
this.vm._ids[node.attrs.id] = id
|
||||||
|
node.attrs.id = id
|
||||||
|
}
|
||||||
|
if (node.name === 'p' || node.name === 'table' || node.name === 'tr' || node.name === 'th' || node.name === 'td' || node.name === 'blockquote' || node.name === 'pre' || node.name === 'code') {
|
||||||
|
node.attrs.class = `md-${node.name} ${node.attrs.class || ''}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Markdown
|
||||||
6
uni_modules/zero-markdown-view/components/mp-html/markdown/marked.min.js
vendored
Normal file
6
uni_modules/zero-markdown-view/components/mp-html/markdown/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
504
uni_modules/zero-markdown-view/components/mp-html/mp-html.vue
Normal file
504
uni_modules/zero-markdown-view/components/mp-html/mp-html.vue
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
<template>
|
||||||
|
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
|
||||||
|
<slot v-if="!nodes[0]" />
|
||||||
|
<!-- #ifndef APP-PLUS-NVUE -->
|
||||||
|
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef APP-PLUS-NVUE -->
|
||||||
|
<web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* mp-html v2.5.1
|
||||||
|
* @description 富文本组件
|
||||||
|
* @tutorial https://github.com/jin-yufeng/mp-html
|
||||||
|
* @property {String} container-style 容器的样式
|
||||||
|
* @property {String} content 用于渲染的 html 字符串
|
||||||
|
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
|
||||||
|
* @property {String} domain 主域名,用于拼接链接
|
||||||
|
* @property {String} error-img 图片出错时的占位图链接
|
||||||
|
* @property {Boolean} lazy-load 是否开启图片懒加载
|
||||||
|
* @property {string} loading-img 图片加载过程中的占位图链接
|
||||||
|
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
|
||||||
|
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
|
||||||
|
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
|
||||||
|
* @property {Boolean | String} selectable 是否开启长按复制
|
||||||
|
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
|
||||||
|
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
|
||||||
|
* @property {Object} tag-style 标签的默认样式
|
||||||
|
* @property {Boolean | Number} use-anchor 是否使用锚点链接
|
||||||
|
* @event {Function} load dom 结构加载完毕时触发
|
||||||
|
* @event {Function} ready 所有图片加载完毕时触发
|
||||||
|
* @event {Function} imgtap 图片被点击时触发
|
||||||
|
* @event {Function} linktap 链接被点击时触发
|
||||||
|
* @event {Function} play 音视频播放时触发
|
||||||
|
* @event {Function} error 媒体加载出错时触发
|
||||||
|
*/
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
import node from './node/node'
|
||||||
|
// #endif
|
||||||
|
import Parser from './parser'
|
||||||
|
import markdown from './markdown/index.js'
|
||||||
|
import highlight from './highlight/index.js'
|
||||||
|
import latex from './latex/index.js'
|
||||||
|
import style from './style/index.js'
|
||||||
|
const plugins=[markdown,highlight,latex,style,]
|
||||||
|
// #ifdef APP-PLUS-NVUE
|
||||||
|
const dom = weex.requireModule('dom')
|
||||||
|
// #endif
|
||||||
|
export default {
|
||||||
|
name: 'mp-html',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
nodes: [],
|
||||||
|
// #ifdef APP-PLUS-NVUE
|
||||||
|
height: 3
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
markdown: Boolean,
|
||||||
|
containerStyle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
copyLink: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
domain: String,
|
||||||
|
errorImg: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
lazyLoad: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
loadingImg: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
pauseVideo: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
previewImg: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
scrollTable: [Boolean, String],
|
||||||
|
selectable: [Boolean, String],
|
||||||
|
setTitle: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showImgMenu: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
tagStyle: Object,
|
||||||
|
useAnchor: [Boolean, Number]
|
||||||
|
},
|
||||||
|
// #ifdef VUE3
|
||||||
|
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
components: {
|
||||||
|
node
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
watch: {
|
||||||
|
content (content) {
|
||||||
|
this.setContent(content)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.plugins = []
|
||||||
|
for (let i = plugins.length; i--;) {
|
||||||
|
this.plugins.push(new plugins[i](this))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.content && !this.nodes.length) {
|
||||||
|
this.setContent(this.content)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this._hook('onDetached')
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* @description 将锚点跳转的范围限定在一个 scroll-view 内
|
||||||
|
* @param {Object} page scroll-view 所在页面的示例
|
||||||
|
* @param {String} selector scroll-view 的选择器
|
||||||
|
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
|
||||||
|
*/
|
||||||
|
in (page, selector, scrollTop) {
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
if (page && selector && scrollTop) {
|
||||||
|
this._in = {
|
||||||
|
page,
|
||||||
|
selector,
|
||||||
|
scrollTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 锚点跳转
|
||||||
|
* @param {String} id 要跳转的锚点 id
|
||||||
|
* @param {Number} offset 跳转位置的偏移量
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
navigateTo (id, offset) {
|
||||||
|
id = this._ids[decodeURI(id)] || id
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.useAnchor) {
|
||||||
|
reject(Error('Anchor is disabled'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
offset = offset || parseInt(this.useAnchor) || 0
|
||||||
|
// #ifdef APP-PLUS-NVUE
|
||||||
|
if (!id) {
|
||||||
|
dom.scrollToElement(this.$refs.web, {
|
||||||
|
offset
|
||||||
|
})
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
this._navigateTo = {
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
let deep = ' '
|
||||||
|
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
||||||
|
deep = '>>>'
|
||||||
|
// #endif
|
||||||
|
const selector = uni.createSelectorQuery()
|
||||||
|
// #ifndef MP-ALIPAY
|
||||||
|
.in(this._in ? this._in.page : this)
|
||||||
|
// #endif
|
||||||
|
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
|
||||||
|
if (this._in) {
|
||||||
|
selector.select(this._in.selector).scrollOffset()
|
||||||
|
.select(this._in.selector).boundingClientRect()
|
||||||
|
} else {
|
||||||
|
// 获取 scroll-view 的位置和滚动距离
|
||||||
|
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
|
||||||
|
}
|
||||||
|
selector.exec(res => {
|
||||||
|
if (!res[0]) {
|
||||||
|
reject(Error('Label not found'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
|
||||||
|
if (this._in) {
|
||||||
|
// scroll-view 跳转
|
||||||
|
this._in.page[this._in.scrollTop] = scrollTop
|
||||||
|
} else {
|
||||||
|
// 页面跳转
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop,
|
||||||
|
duration: 300
|
||||||
|
})
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取文本内容
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
getText (nodes) {
|
||||||
|
let text = '';
|
||||||
|
(function traversal (nodes) {
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i]
|
||||||
|
if (node.type === 'text') {
|
||||||
|
text += node.text.replace(/&/g, '&')
|
||||||
|
} else if (node.name === 'br') {
|
||||||
|
text += '\n'
|
||||||
|
} else {
|
||||||
|
// 块级标签前后加换行
|
||||||
|
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
|
||||||
|
if (isBlock && text && text[text.length - 1] !== '\n') {
|
||||||
|
text += '\n'
|
||||||
|
}
|
||||||
|
// 递归获取子节点的文本
|
||||||
|
if (node.children) {
|
||||||
|
traversal(node.children)
|
||||||
|
}
|
||||||
|
if (isBlock && text[text.length - 1] !== '\n') {
|
||||||
|
text += '\n'
|
||||||
|
} else if (node.name === 'td' || node.name === 'th') {
|
||||||
|
text += '\t'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(nodes || this.nodes)
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取内容大小和位置
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
getRect () {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
// #ifndef MP-ALIPAY
|
||||||
|
.in(this)
|
||||||
|
// #endif
|
||||||
|
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 暂停播放媒体
|
||||||
|
*/
|
||||||
|
pauseMedia () {
|
||||||
|
for (let i = (this._videos || []).length; i--;) {
|
||||||
|
this._videos[i].pause()
|
||||||
|
}
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
let page = this.$parent
|
||||||
|
while (!page.$scope) page = page.$parent
|
||||||
|
page.$scope.$getAppWebview().evalJS(command)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-PLUS-NVUE
|
||||||
|
this.$refs.web.evalJs(command)
|
||||||
|
// #endif
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 设置媒体播放速率
|
||||||
|
* @param {Number} rate 播放速率
|
||||||
|
*/
|
||||||
|
setPlaybackRate (rate) {
|
||||||
|
this.playbackRate = rate
|
||||||
|
for (let i = (this._videos || []).length; i--;) {
|
||||||
|
this._videos[i].playbackRate(rate)
|
||||||
|
}
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
let page = this.$parent
|
||||||
|
while (!page.$scope) page = page.$parent
|
||||||
|
page.$scope.$getAppWebview().evalJS(command)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-PLUS-NVUE
|
||||||
|
this.$refs.web.evalJs(command)
|
||||||
|
// #endif
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 设置内容
|
||||||
|
* @param {String} content html 内容
|
||||||
|
* @param {Boolean} append 是否在尾部追加
|
||||||
|
*/
|
||||||
|
setContent (content, append) {
|
||||||
|
if (!append || !this.imgList) {
|
||||||
|
this.imgList = []
|
||||||
|
}
|
||||||
|
const nodes = new Parser(this).parse(content)
|
||||||
|
// #ifdef APP-PLUS-NVUE
|
||||||
|
if (this._ready) {
|
||||||
|
this._set(nodes, append)
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
|
||||||
|
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
this._videos = []
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this._hook('onLoad')
|
||||||
|
this.$emit('load')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
|
||||||
|
// 设置懒加载,每 350ms 获取高度,不变则认为加载完毕
|
||||||
|
let height = 0
|
||||||
|
const callback = rect => {
|
||||||
|
if (!rect || !rect.height) rect = {}
|
||||||
|
// 350ms 总高度无变化就触发 ready 事件
|
||||||
|
if (rect.height === height) {
|
||||||
|
this.$emit('ready', rect)
|
||||||
|
} else {
|
||||||
|
height = rect.height
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getRect().then(callback).catch(callback)
|
||||||
|
}, 350)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.getRect().then(callback).catch(callback)
|
||||||
|
} else {
|
||||||
|
// 未设置懒加载,等待所有图片加载完毕
|
||||||
|
if (!this.imgList._unloadimgs) {
|
||||||
|
this.getRect().then(rect => {
|
||||||
|
this.$emit('ready', rect)
|
||||||
|
}).catch(() => {
|
||||||
|
this.$emit('ready', {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 调用插件钩子函数
|
||||||
|
*/
|
||||||
|
_hook (name) {
|
||||||
|
for (let i = plugins.length; i--;) {
|
||||||
|
if (this.plugins[i][name]) {
|
||||||
|
this.plugins[i][name]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS-NVUE
|
||||||
|
/**
|
||||||
|
* @description 设置内容
|
||||||
|
*/
|
||||||
|
_set (nodes, append) {
|
||||||
|
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 接收到 web-view 消息
|
||||||
|
*/
|
||||||
|
_onMessage (e) {
|
||||||
|
const message = e.detail.data[0]
|
||||||
|
switch (message.action) {
|
||||||
|
// web-view 初始化完毕
|
||||||
|
case 'onJSBridgeReady':
|
||||||
|
this._ready = true
|
||||||
|
if (this.nodes) {
|
||||||
|
this._set(this.nodes)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
// 内容 dom 加载完毕
|
||||||
|
case 'onLoad':
|
||||||
|
this.height = message.height
|
||||||
|
this._hook('onLoad')
|
||||||
|
this.$emit('load')
|
||||||
|
break
|
||||||
|
// 所有图片加载完毕
|
||||||
|
case 'onReady':
|
||||||
|
this.getRect().then(res => {
|
||||||
|
this.$emit('ready', res)
|
||||||
|
}).catch(() => {
|
||||||
|
this.$emit('ready', {})
|
||||||
|
})
|
||||||
|
break
|
||||||
|
// 总高度发生变化
|
||||||
|
case 'onHeightChange':
|
||||||
|
this.height = message.height
|
||||||
|
break
|
||||||
|
// 图片点击
|
||||||
|
case 'onImgTap':
|
||||||
|
this.$emit('imgtap', message.attrs)
|
||||||
|
if (this.previewImg) {
|
||||||
|
uni.previewImage({
|
||||||
|
current: parseInt(message.attrs.i),
|
||||||
|
urls: this.imgList
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
// 链接点击
|
||||||
|
case 'onLinkTap': {
|
||||||
|
const href = message.attrs.href
|
||||||
|
this.$emit('linktap', message.attrs)
|
||||||
|
if (href) {
|
||||||
|
// 锚点跳转
|
||||||
|
if (href[0] === '#') {
|
||||||
|
if (this.useAnchor) {
|
||||||
|
dom.scrollToElement(this.$refs.web, {
|
||||||
|
offset: message.offset
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (href.includes('://')) {
|
||||||
|
// 打开外链
|
||||||
|
if (this.copyLink) {
|
||||||
|
plus.runtime.openWeb(href)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: href,
|
||||||
|
fail () {
|
||||||
|
uni.switchTab({
|
||||||
|
url: href
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'onPlay':
|
||||||
|
this.$emit('play')
|
||||||
|
break
|
||||||
|
// 获取到锚点的偏移量
|
||||||
|
case 'getOffset':
|
||||||
|
if (typeof message.offset === 'number') {
|
||||||
|
dom.scrollToElement(this.$refs.web, {
|
||||||
|
offset: message.offset + this._navigateTo.offset
|
||||||
|
})
|
||||||
|
this._navigateTo.resolve()
|
||||||
|
} else {
|
||||||
|
this._navigateTo.reject(Error('Label not found'))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
// 点击
|
||||||
|
case 'onClick':
|
||||||
|
this.$emit('tap')
|
||||||
|
this.$emit('click')
|
||||||
|
break
|
||||||
|
// 出错
|
||||||
|
case 'onError':
|
||||||
|
this.$emit('error', {
|
||||||
|
source: message.source,
|
||||||
|
attrs: message.attrs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* #ifndef APP-PLUS-NVUE */
|
||||||
|
/* 根节点样式 */
|
||||||
|
._root {
|
||||||
|
padding: 1px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 长按复制 */
|
||||||
|
._select {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
</style>
|
||||||
670
uni_modules/zero-markdown-view/components/mp-html/node/node.vue
Normal file
670
uni_modules/zero-markdown-view/components/mp-html/node/node.vue
Normal file
File diff suppressed because one or more lines are too long
1400
uni_modules/zero-markdown-view/components/mp-html/parser.js
Normal file
1400
uni_modules/zero-markdown-view/components/mp-html/parser.js
Normal file
File diff suppressed because it is too large
Load Diff
129
uni_modules/zero-markdown-view/components/mp-html/style/index.js
Normal file
129
uni_modules/zero-markdown-view/components/mp-html/style/index.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview style 插件
|
||||||
|
*/
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
import Parser from './parser'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
function Style () {
|
||||||
|
this.styles = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifndef APP-PLUS-NVUE
|
||||||
|
Style.prototype.onParse = function (node, vm) {
|
||||||
|
// 获取样式
|
||||||
|
if (node.name === 'style' && node.children.length && node.children[0].type === 'text') {
|
||||||
|
this.styles = this.styles.concat(new Parser().parse(node.children[0].text))
|
||||||
|
} else if (node.name) {
|
||||||
|
// 匹配样式(对非文本标签)
|
||||||
|
// 存储不同优先级的样式 name < class < id < 后代
|
||||||
|
let matched = ['', '', '', '']
|
||||||
|
for (let i = 0, len = this.styles.length; i < len; i++) {
|
||||||
|
const item = this.styles[i]
|
||||||
|
let res = match(node, item.key || item.list[item.list.length - 1])
|
||||||
|
let j
|
||||||
|
if (res) {
|
||||||
|
// 后代选择器
|
||||||
|
if (!item.key) {
|
||||||
|
j = item.list.length - 2
|
||||||
|
for (let k = vm.stack.length; j >= 0 && k--;) {
|
||||||
|
// 子选择器
|
||||||
|
if (item.list[j] === '>') {
|
||||||
|
// 错误情况
|
||||||
|
if (j < 1 || j > item.list.length - 2) break
|
||||||
|
if (match(vm.stack[k], item.list[j - 1])) {
|
||||||
|
j -= 2
|
||||||
|
} else {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
} else if (match(vm.stack[k], item.list[j])) {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = 4
|
||||||
|
}
|
||||||
|
if (item.key || j < 0) {
|
||||||
|
// 添加伪类
|
||||||
|
if (item.pseudo && node.children) {
|
||||||
|
let text
|
||||||
|
item.style = item.style.replace(/content:([^;]+)/, (_, $1) => {
|
||||||
|
text = $1.replace(/['"]/g, '')
|
||||||
|
// 处理 attr 函数
|
||||||
|
.replace(/attr\((.+?)\)/, (_, $1) => node.attrs[$1.trim()] || '')
|
||||||
|
// 编码 \xxx
|
||||||
|
.replace(/\\(\w{4})/, (_, $1) => String.fromCharCode(parseInt($1, 16)))
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const pseudo = {
|
||||||
|
name: 'span',
|
||||||
|
attrs: {
|
||||||
|
style: item.style
|
||||||
|
},
|
||||||
|
children: [{
|
||||||
|
type: 'text',
|
||||||
|
text
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
if (item.pseudo === 'before') {
|
||||||
|
node.children.unshift(pseudo)
|
||||||
|
} else {
|
||||||
|
node.children.push(pseudo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
matched[res - 1] += item.style + (item.style[item.style.length - 1] === ';' ? '' : ';')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matched = matched.join('')
|
||||||
|
if (matched.length > 2) {
|
||||||
|
node.attrs.style = matched + (node.attrs.style || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 匹配样式
|
||||||
|
* @param {object} node 要匹配的标签
|
||||||
|
* @param {string|string[]} keys 选择器
|
||||||
|
* @returns {number} 0:不匹配;1:name 匹配;2:class 匹配;3:id 匹配
|
||||||
|
*/
|
||||||
|
function match (node, keys) {
|
||||||
|
function matchItem (key) {
|
||||||
|
if (key[0] === '#') {
|
||||||
|
// 匹配 id
|
||||||
|
if (node.attrs.id && node.attrs.id.trim() === key.substr(1)) return 3
|
||||||
|
} else if (key[0] === '.') {
|
||||||
|
// 匹配 class
|
||||||
|
key = key.substr(1)
|
||||||
|
const selectors = (node.attrs.class || '').split(' ')
|
||||||
|
for (let i = 0; i < selectors.length; i++) {
|
||||||
|
if (selectors[i].trim() === key) return 2
|
||||||
|
}
|
||||||
|
} else if (node.name === key) {
|
||||||
|
// 匹配 name
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多选择器交集
|
||||||
|
if (keys instanceof Array) {
|
||||||
|
let res = 0
|
||||||
|
for (let j = 0; j < keys.length; j++) {
|
||||||
|
const tmp = matchItem(keys[j])
|
||||||
|
// 任意一个不匹配就失败
|
||||||
|
if (!tmp) return 0
|
||||||
|
// 优先级最大的一个作为最终优先级
|
||||||
|
if (tmp > res) {
|
||||||
|
res = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchItem(keys)
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
export default Style
|
||||||
@ -0,0 +1,175 @@
|
|||||||
|
const blank = {
|
||||||
|
' ': true,
|
||||||
|
'\n': true,
|
||||||
|
'\t': true,
|
||||||
|
'\r': true,
|
||||||
|
'\f': true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Parser () {
|
||||||
|
this.styles = []
|
||||||
|
this.selectors = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 解析 css 字符串
|
||||||
|
* @param {string} content css 内容
|
||||||
|
*/
|
||||||
|
Parser.prototype.parse = function (content) {
|
||||||
|
new Lexer(this).parse(content)
|
||||||
|
return this.styles
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 解析到一个选择器
|
||||||
|
* @param {string} name 名称
|
||||||
|
*/
|
||||||
|
Parser.prototype.onSelector = function (name) {
|
||||||
|
// 不支持的选择器
|
||||||
|
if (name.includes('[') || name.includes('*') || name.includes('@')) return
|
||||||
|
const selector = {}
|
||||||
|
// 伪类
|
||||||
|
if (name.includes(':')) {
|
||||||
|
const info = name.split(':')
|
||||||
|
const pseudo = info.pop()
|
||||||
|
if (pseudo === 'before' || pseudo === 'after') {
|
||||||
|
selector.pseudo = pseudo
|
||||||
|
name = info[0]
|
||||||
|
} else return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割交集选择器
|
||||||
|
function splitItem (str) {
|
||||||
|
const arr = []
|
||||||
|
let i, start
|
||||||
|
for (i = 1, start = 0; i < str.length; i++) {
|
||||||
|
if (str[i] === '.' || str[i] === '#') {
|
||||||
|
arr.push(str.substring(start, i))
|
||||||
|
start = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!arr.length) {
|
||||||
|
return str
|
||||||
|
} else {
|
||||||
|
arr.push(str.substring(start, i))
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后代选择器
|
||||||
|
if (name.includes(' ')) {
|
||||||
|
selector.list = []
|
||||||
|
const list = name.split(' ')
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (list[i].length) {
|
||||||
|
// 拆分子选择器
|
||||||
|
const arr = list[i].split('>')
|
||||||
|
for (let j = 0; j < arr.length; j++) {
|
||||||
|
selector.list.push(splitItem(arr[j]))
|
||||||
|
if (j < arr.length - 1) {
|
||||||
|
selector.list.push('>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selector.key = splitItem(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectors.push(selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 解析到选择器内容
|
||||||
|
* @param {string} content 内容
|
||||||
|
*/
|
||||||
|
Parser.prototype.onContent = function (content) {
|
||||||
|
// 并集选择器
|
||||||
|
for (let i = 0; i < this.selectors.length; i++) {
|
||||||
|
this.selectors[i].style = content
|
||||||
|
}
|
||||||
|
this.styles = this.styles.concat(this.selectors)
|
||||||
|
this.selectors = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description css 词法分析器
|
||||||
|
* @param {object} handler 高层处理器
|
||||||
|
*/
|
||||||
|
function Lexer (handler) {
|
||||||
|
this.selector = ''
|
||||||
|
this.style = ''
|
||||||
|
this.handler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer.prototype.parse = function (content) {
|
||||||
|
this.i = 0
|
||||||
|
this.content = content
|
||||||
|
this.state = this.blank
|
||||||
|
for (let len = content.length; this.i < len; this.i++) {
|
||||||
|
this.state(content[this.i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer.prototype.comment = function () {
|
||||||
|
this.i = this.content.indexOf('*/', this.i) + 1
|
||||||
|
if (!this.i) {
|
||||||
|
this.i = this.content.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer.prototype.blank = function (c) {
|
||||||
|
if (!blank[c]) {
|
||||||
|
if (c === '/' && this.content[this.i + 1] === '*') {
|
||||||
|
this.comment()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.selector += c
|
||||||
|
this.state = this.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer.prototype.name = function (c) {
|
||||||
|
if (c === '/' && this.content[this.i + 1] === '*') {
|
||||||
|
this.comment()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (c === '{' || c === ',' || c === ';') {
|
||||||
|
this.handler.onSelector(this.selector.trimEnd())
|
||||||
|
this.selector = ''
|
||||||
|
if (c !== '{') {
|
||||||
|
while (blank[this.content[++this.i]]);
|
||||||
|
}
|
||||||
|
if (this.content[this.i] === '{') {
|
||||||
|
this.floor = 1
|
||||||
|
this.state = this.val
|
||||||
|
} else {
|
||||||
|
this.selector += this.content[this.i]
|
||||||
|
}
|
||||||
|
} else if (blank[c]) {
|
||||||
|
this.selector += ' '
|
||||||
|
} else {
|
||||||
|
this.selector += c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer.prototype.val = function (c) {
|
||||||
|
if (c === '/' && this.content[this.i + 1] === '*') {
|
||||||
|
this.comment()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (c === '{') {
|
||||||
|
this.floor++
|
||||||
|
} else if (c === '}') {
|
||||||
|
this.floor--
|
||||||
|
if (!this.floor) {
|
||||||
|
this.handler.onContent(this.style)
|
||||||
|
this.style = ''
|
||||||
|
this.state = this.blank
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.style += c
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Parser
|
||||||
@ -0,0 +1,312 @@
|
|||||||
|
<template>
|
||||||
|
<view class="zero-markdown-view">
|
||||||
|
<mp-html :key="mpkey" :selectable="selectable" :scroll-table='scrollTable' :tag-style="tagStyle"
|
||||||
|
:markdown="true" :content="contentAi">
|
||||||
|
</mp-html>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mpHtml from '../mp-html/mp-html';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'zero-markdown-view',
|
||||||
|
components: {
|
||||||
|
mpHtml
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
markdown: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
selectable: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
scrollTable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
themeColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#007AFF'
|
||||||
|
},
|
||||||
|
codeBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#2d2d2d'
|
||||||
|
},
|
||||||
|
aiMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
content: '',
|
||||||
|
tagStyle: '',
|
||||||
|
mpkey: 'zero'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
contentAi() {
|
||||||
|
if (!this.content) {
|
||||||
|
return //处理特殊情况,比如网络异常导致的响应的 content 的值为空
|
||||||
|
}
|
||||||
|
let htmlString = ''
|
||||||
|
// 检查是否有未闭合的代码块
|
||||||
|
const codeBlocks = this.content.match(/```[\s\S]*?```|```[\s\S]*?$/g) || []
|
||||||
|
const lastBlock = codeBlocks[codeBlocks.length - 1]
|
||||||
|
if (lastBlock && !lastBlock.endsWith('```')) {
|
||||||
|
// 最后一个代码块未闭合,需要补上结束标识符
|
||||||
|
htmlString = this.content + '\n'
|
||||||
|
} else {
|
||||||
|
htmlString = this.content
|
||||||
|
}
|
||||||
|
return htmlString
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
markdown: function (val) {
|
||||||
|
this.content = this.markdown
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.aiMode) {
|
||||||
|
this.initTagStyleForAi();
|
||||||
|
} else {
|
||||||
|
this.initTagStyle();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.content = this.markdown
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
initTagStyle() {
|
||||||
|
const themeColor = this.themeColor
|
||||||
|
const codeBgColor = this.codeBgColor
|
||||||
|
let zeroStyle = {
|
||||||
|
p: `
|
||||||
|
margin:5px 5px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height:1.75;
|
||||||
|
letter-spacing:0.2em;
|
||||||
|
word-spacing:0.1em;
|
||||||
|
`,
|
||||||
|
// 一级标题
|
||||||
|
h1: `
|
||||||
|
margin:25px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color: ${themeColor};
|
||||||
|
padding:3px 10px 1px;
|
||||||
|
border-bottom: 2px solid ${themeColor};
|
||||||
|
border-top-right-radius:3px;
|
||||||
|
border-top-left-radius:3px;
|
||||||
|
`,
|
||||||
|
// 二级标题
|
||||||
|
h2: `
|
||||||
|
margin:40px 0 20px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align:center;
|
||||||
|
color:${themeColor};
|
||||||
|
font-weight:bolder;
|
||||||
|
padding-left:10px;
|
||||||
|
// border:1px solid ${themeColor};
|
||||||
|
`,
|
||||||
|
// 三级标题
|
||||||
|
h3: `
|
||||||
|
margin:30px 0 10px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: ${themeColor};
|
||||||
|
padding-left:10px;
|
||||||
|
border-left:3px solid ${themeColor};
|
||||||
|
`,
|
||||||
|
// 引用
|
||||||
|
blockquote: `
|
||||||
|
margin:15px 0;
|
||||||
|
font-size:15px;
|
||||||
|
color: #777777;
|
||||||
|
border-left: 4px solid #dddddd;
|
||||||
|
padding: 0 10px;
|
||||||
|
`,
|
||||||
|
// 列表
|
||||||
|
ul: `
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #555;
|
||||||
|
`,
|
||||||
|
li: `
|
||||||
|
margin: 5px 0;
|
||||||
|
color: #555;
|
||||||
|
`,
|
||||||
|
// 链接
|
||||||
|
a: `
|
||||||
|
// color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 加粗
|
||||||
|
strong: `
|
||||||
|
font-weight: border;
|
||||||
|
color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 斜体
|
||||||
|
em: `
|
||||||
|
color: ${themeColor};
|
||||||
|
letter-spacing:0.3em;
|
||||||
|
`,
|
||||||
|
// 分割线
|
||||||
|
hr: `
|
||||||
|
height:1px;
|
||||||
|
padding:0;
|
||||||
|
border:none;
|
||||||
|
text-align:center;
|
||||||
|
background-image:linear-gradient(to right,rgba(248,57,41,0),${themeColor},rgba(248,57,41,0));
|
||||||
|
margin:10px 0;
|
||||||
|
`,
|
||||||
|
// 表格
|
||||||
|
table: `
|
||||||
|
border-spacing:0;
|
||||||
|
overflow:auto;
|
||||||
|
min-width:100%;
|
||||||
|
margin:10px 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
`,
|
||||||
|
th: `
|
||||||
|
border: 1px solid #202121;
|
||||||
|
color: #555;
|
||||||
|
`,
|
||||||
|
td: `
|
||||||
|
color:#555;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
`,
|
||||||
|
pre: `
|
||||||
|
border-radius: 5px;
|
||||||
|
white-space: pre;
|
||||||
|
background: ${codeBgColor};
|
||||||
|
font-size:12px;
|
||||||
|
position: relative;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
this.tagStyle = zeroStyle
|
||||||
|
},
|
||||||
|
initTagStyleForAi() {
|
||||||
|
const themeColor = this.themeColor
|
||||||
|
const codeBgColor = this.codeBgColor
|
||||||
|
let zeroStyle = {
|
||||||
|
p: `
|
||||||
|
font-size: 16px;
|
||||||
|
`,
|
||||||
|
// 一级标题
|
||||||
|
h1: `
|
||||||
|
margin:18px 0 10px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 二级标题
|
||||||
|
h2: `
|
||||||
|
margin:14px 0 10px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 三级标题
|
||||||
|
h3: `
|
||||||
|
margin:12x 0 8px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 四级标题
|
||||||
|
h4: `
|
||||||
|
margin:12px 0 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 五级标题
|
||||||
|
h5: `
|
||||||
|
margin:10px 0 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 六级标题
|
||||||
|
h6: `
|
||||||
|
margin:8px 0 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${themeColor}
|
||||||
|
`,
|
||||||
|
// 引用
|
||||||
|
blockquote: `
|
||||||
|
margin:15px 0;
|
||||||
|
font-size:15px;
|
||||||
|
color: #777777;
|
||||||
|
border-left: 4px solid #dddddd;
|
||||||
|
padding: 0 10px;
|
||||||
|
`,
|
||||||
|
// 列表
|
||||||
|
ul: `
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #555;
|
||||||
|
`,
|
||||||
|
li: `
|
||||||
|
margin: 5px 0;
|
||||||
|
color: #555;
|
||||||
|
`,
|
||||||
|
// 链接
|
||||||
|
a: `
|
||||||
|
// color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 加粗
|
||||||
|
strong: `
|
||||||
|
font-weight: border;
|
||||||
|
color: ${themeColor};
|
||||||
|
`,
|
||||||
|
// 斜体
|
||||||
|
em: `
|
||||||
|
color: ${themeColor};
|
||||||
|
letter-spacing:0.3em;
|
||||||
|
`,
|
||||||
|
// 分割线
|
||||||
|
hr: `
|
||||||
|
height:1px;
|
||||||
|
padding:0;
|
||||||
|
border:none;
|
||||||
|
text-align:center;
|
||||||
|
background-image:linear-gradient(to right,rgba(248,57,41,0),${themeColor},rgba(248,57,41,0));
|
||||||
|
margin:10px 0;
|
||||||
|
`,
|
||||||
|
// 表格
|
||||||
|
table: `
|
||||||
|
border-spacing:0;
|
||||||
|
overflow:auto;
|
||||||
|
min-width:100%;
|
||||||
|
margin:10px 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
`,
|
||||||
|
th: `
|
||||||
|
border: 1px solid #202121;
|
||||||
|
color: #555;
|
||||||
|
`,
|
||||||
|
td: `
|
||||||
|
color:#555;
|
||||||
|
border: 1px solid #555555;
|
||||||
|
`,
|
||||||
|
pre: `
|
||||||
|
border-radius: 5px;
|
||||||
|
white-space: pre;
|
||||||
|
background: ${codeBgColor};
|
||||||
|
font-size:12px;
|
||||||
|
position: relative;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
this.tagStyle = zeroStyle
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.zero-markdown-view {
|
||||||
|
padding: 15rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
87
uni_modules/zero-markdown-view/package.json
Normal file
87
uni_modules/zero-markdown-view/package.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"id": "zero-markdown-view",
|
||||||
|
"displayName": "zero-markdown-view(markdown解析)",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"description": "一行代码即可实现markdown解析,支持自定义主题色,支持vue2,vue3,支持流式输出。",
|
||||||
|
"keywords": [
|
||||||
|
"markdown",
|
||||||
|
"markdown解析",
|
||||||
|
"代码块",
|
||||||
|
"代码高亮",
|
||||||
|
"mp-html"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "插件不采集任何数据",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y",
|
||||||
|
"alipay": "n"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
},
|
||||||
|
"App": {
|
||||||
|
"app-vue": "u",
|
||||||
|
"app-nvue": "u",
|
||||||
|
"app-harmony": "u",
|
||||||
|
"app-uvue": "u"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "u",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "u",
|
||||||
|
"百度": "u",
|
||||||
|
"字节跳动": "u",
|
||||||
|
"QQ": "u",
|
||||||
|
"钉钉": "u",
|
||||||
|
"快手": "u",
|
||||||
|
"飞书": "u",
|
||||||
|
"京东": "u"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
uni_modules/zero-markdown-view/readme.md
Normal file
133
uni_modules/zero-markdown-view/readme.md
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# zero-markdown-view
|
||||||
|
|
||||||
|
## 一. 重要更新说明
|
||||||
|
|
||||||
|
### v3.0.0
|
||||||
|
|
||||||
|
- 优化了代码块流式输出
|
||||||
|
- 增加aiMode模式(内容靠左对齐的,样式区别于普通模式)
|
||||||
|
|
||||||
|
|
||||||
|
### v2.1.0
|
||||||
|
|
||||||
|
- 新增 latex 公式支持
|
||||||
|
- 代码块高亮支持: languages=markup+css+clike+javascript+c+cpp+go+java+markup-templating+php+python+rust
|
||||||
|
- 代码包体积增加了不少(注意)
|
||||||
|
|
||||||
|
### v2.0.4 (仅需要基础功能可以使用此版本)
|
||||||
|
|
||||||
|
- 新增点击代码块复制代码-仅小程序可用
|
||||||
|
|
||||||
|
### v2.0.0
|
||||||
|
|
||||||
|
- 省去了 npm install marked
|
||||||
|
- 省去了 npm install highlight.js
|
||||||
|
- 使用 mp-html 自带的插件,重新生成 uniapp 包,大幅减少插件体积
|
||||||
|
传送门: [优化思路及详细过程](https://juejin.cn/post/7160995270476431373/) https://juejin.cn/post/7160995270476431373/
|
||||||
|
|
||||||
|
## 二. 使用方法
|
||||||
|
|
||||||
|
> 建议放到分包里手动引入该插件
|
||||||
|
|
||||||
|
**符合`easycom`组件模式, 导入 `uni_modules` 后直接使用即可 **
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 默认用法 直接传入md文本即可 普通md展示-->
|
||||||
|
<zero-markdown-view :markdown="content"></zero-markdown-view>
|
||||||
|
|
||||||
|
<!-- ai对话模式 -->
|
||||||
|
<zero-markdown-view :markdown="msgContent" :aiMode='true'></zero-markdown-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
content: "## md格式的文本内容",
|
||||||
|
msgContent: "## ai回复的内容,兼容流式输出",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三. 参数说明
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 描述 |
|
||||||
|
| ----------- | ------ | --------- | ------------- |
|
||||||
|
| markdown | String | | markdown 文本 |
|
||||||
|
| themeColor | String | '#007AFF' | 主题色 |
|
||||||
|
| codeBgColor | String | '#2d2d2d' | 代码块背景色 |
|
||||||
|
| aiMode | Boolean| false | 是否为 AI 模式(内容靠左对齐) |
|
||||||
|
|
||||||
|
|
||||||
|
## 四. 注意事项
|
||||||
|
|
||||||
|
### 关于代码块高亮
|
||||||
|
|
||||||
|
如果需要更多语言或更换主题请前往 prism 官网 下载对应的 prism.min.js 和 prism.css 并替换 plugins/highlight/ 目录下的文件,重新运行打包命令并替换 mp-html. 不建议这样做,因为本插件还修改过其他地方,可能会导致插件不能正常使用.
|
||||||
|
具体请参考 mp-html 的文档,在文末有链接
|
||||||
|
|
||||||
|
### 关于代码块流式输出闪烁,可以尝试 给代码块后增加 `\n`
|
||||||
|
|
||||||
|
#### 已经在组件内的 `aiMode` 解决,不需要自行处理,也就是说,如何你是是用在ai上,直接把ai回复内容传给组件就可以了
|
||||||
|
#### 已经在组件内的 `aiMode` 解决,不需要自行处理,也就是说,如何你是是用在ai上,直接把ai回复内容传给组件就可以了
|
||||||
|
#### 已经在组件内的 `aiMode` 解决,不需要自行处理,也就是说,如何你是是用在ai上,直接把ai回复内容传给组件就可以了
|
||||||
|
|
||||||
|
|
||||||
|
> 注意小程序上的流逝输出需要结合unipush2使用
|
||||||
|
|
||||||
|
> 感谢评论区的一个朋友提供的代码
|
||||||
|
|
||||||
|
````javascript
|
||||||
|
computed: {
|
||||||
|
// 流式输出时代码块处理 , 这时候请使用 msgContent 传入组件中
|
||||||
|
msgContent() {
|
||||||
|
if (!this.markdown) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let htmlString = ''
|
||||||
|
// 检查是否有未闭合的代码块
|
||||||
|
const codeBlocks = this.markdown.match(/```[\s\S]*?```|```[\s\S]*?$/g) || []
|
||||||
|
const lastBlock = codeBlocks[codeBlocks.length - 1]
|
||||||
|
if (lastBlock && !lastBlock.endsWith('```')) {
|
||||||
|
// 最后一个代码块未闭合,需要补上结束标识符
|
||||||
|
htmlString = this.markdown + '\n'
|
||||||
|
} else {
|
||||||
|
htmlString = this.markdown
|
||||||
|
}
|
||||||
|
return htmlString
|
||||||
|
},
|
||||||
|
},
|
||||||
|
````
|
||||||
|
|
||||||
|
### 如何关闭点击代码块复制功能?
|
||||||
|
|
||||||
|
找到组件文件夹 `zero-markdown-view`-`mp-html`-`highlight`-`config.js`
|
||||||
|
|
||||||
|
**把 `copyByClickCode` 改成 false 即可**
|
||||||
|
|
||||||
|
```
|
||||||
|
export default {
|
||||||
|
copyByClickCode: true, // 点击代码块复制
|
||||||
|
showLanguageName: true, // 是否在代码块右上角显示语言的名称
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 感谢 mp-html 插件
|
||||||
|
|
||||||
|
插件地址: [https://ext.dcloud.net.cn/plugin?id=805](https://ext.dcloud.net.cn/plugin?id=805)
|
||||||
|
|
||||||
|
文档地址: [https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart)
|
||||||
|
|
||||||
|
插件预览:
|
||||||
|

|
||||||
|
|
||||||
|
> 小程序搜索: 零技术
|
||||||
|
|
||||||
|
> 预览的小程序不一定能及时更新当前插件
|
||||||
Loading…
Reference in New Issue
Block a user