lu_quan_dev
parent
b7039826b6
commit
d9dac1e89b
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@ |
||||
## 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) |
||||
首次发布 |
@ -1,5 +0,0 @@ |
||||
export default { |
||||
copyByClickCode: true, // 点击代码块复制
|
||||
showLanguageName: true, // 是否在代码块右上角显示语言的名称
|
||||
showLineNumber: false // 是否显示行号
|
||||
} |
@ -1,109 +0,0 @@ |
||||
/** |
||||
* @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() |
||||
// console.log('vm',node,vm)
|
||||
} |
||||
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 |
File diff suppressed because one or more lines are too long
@ -1,34 +0,0 @@ |
||||
/** |
||||
* @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 |
File diff suppressed because one or more lines are too long
@ -1,503 +0,0 @@ |
||||
<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.4.2 |
||||
* @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 style from './style/index.js' |
||||
const plugins=[markdown,highlight,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> |
@ -1,678 +0,0 @@ |
||||
<template> |
||||
<view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style"> |
||||
<block v-for="(n, i) in childs" v-bind:key="i"> |
||||
<!-- 图片 --> |
||||
<!-- 占位图 --> |
||||
<image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" /> |
||||
<!-- 显示图片 --> |
||||
<!-- #ifdef H5 || (APP-PLUS && VUE2) --> |
||||
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" /> |
||||
<!-- #endif --> |
||||
<!-- #ifndef H5 || (APP-PLUS && VUE2) --> |
||||
<!-- 表格中的图片,使用 rich-text 防止大小不正确 --> |
||||
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style,src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" /> |
||||
<!-- #endif --> |
||||
<!-- #ifndef H5 || APP-PLUS --> |
||||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" /> |
||||
<!-- #endif --> |
||||
<!-- #ifdef APP-PLUS && VUE3 --> |
||||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" /> |
||||
<!-- #endif --> |
||||
<!-- 文本 --> |
||||
<!-- #ifdef MP-WEIXIN --> |
||||
<text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text> |
||||
<!-- #endif --> |
||||
<!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO --> |
||||
<text v-else-if="n.text" decode>{{n.text}}</text> |
||||
<!-- #endif --> |
||||
<text v-else-if="n.name==='br'">\n</text> |
||||
<!-- 链接 --> |
||||
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap"> |
||||
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" /> |
||||
</view> |
||||
<!-- 视频 --> |
||||
<!-- #ifdef APP-PLUS --> |
||||
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" /> |
||||
<!-- #endif --> |
||||
<!-- #ifndef APP-PLUS --> |
||||
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" /> |
||||
<!-- #endif --> |
||||
<!-- #ifdef H5 || APP-PLUS --> |
||||
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" /> |
||||
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" /> |
||||
<!-- #endif --> |
||||
<!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) --> |
||||
<!-- 音频 --> |
||||
<audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" /> |
||||
<!-- #endif --> |
||||
<view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style"> |
||||
<node v-if="n.name==='li'" :childs="n.children" :opts="opts" /> |
||||
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style"> |
||||
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" /> |
||||
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y"> |
||||
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style"> |
||||
<node :childs="tr.children" :opts="opts" /> |
||||
</view> |
||||
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style"> |
||||
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style"> |
||||
<node :childs="td.children" :opts="opts" /> |
||||
</view> |
||||
</view> |
||||
</block> |
||||
</view> |
||||
</view> |
||||
|
||||
<!-- 富文本 --> |
||||
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) --> |
||||
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" @tap.stop="codeLongTap(n)"/> |
||||
<!-- #endif --> |
||||
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) --> |
||||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" @tap.stop="codeLongTap(n)"/> |
||||
<!-- #endif --> |
||||
<!-- 继续递归 --> |
||||
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style"> |
||||
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" /> |
||||
</view> |
||||
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts"/> |
||||
</block> |
||||
</view> |
||||
</template> |
||||
<script module="handler" lang="wxs"> |
||||
// 行内标签列表 |
||||
var inlineTags = { |
||||
abbr: true, |
||||
b: true, |
||||
big: true, |
||||
code: true, |
||||
del: true, |
||||
em: true, |
||||
i: true, |
||||
ins: true, |
||||
label: true, |
||||
q: true, |
||||
small: true, |
||||
span: true, |
||||
strong: true, |
||||
sub: true, |
||||
sup: true |
||||
} |
||||
/** |
||||
* @description 判断是否为行内标签 |
||||
*/ |
||||
module.exports = { |
||||
isInline: function (tagName, style) { |
||||
return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1 |
||||
} |
||||
} |
||||
</script> |
||||
<script> |
||||
|
||||
import node from './node' |
||||
export default { |
||||
name: 'node', |
||||
options: { |
||||
// #ifdef MP-WEIXIN |
||||
virtualHost: true, |
||||
// #endif |
||||
// #ifdef MP-TOUTIAO |
||||
addGlobalClass: false |
||||
// #endif |
||||
}, |
||||
data () { |
||||
return { |
||||
ctrl: {}, |
||||
// #ifdef MP-WEIXIN |
||||
isiOS: uni.getSystemInfoSync().system.includes('iOS') |
||||
// #endif |
||||
} |
||||
}, |
||||
props: { |
||||
name: String, |
||||
attrs: { |
||||
type: Object, |
||||
default () { |
||||
return {} |
||||
} |
||||
}, |
||||
childs: Array, |
||||
opts: Array |
||||
}, |
||||
components: { |
||||
|
||||
// #ifndef (H5 || APP-PLUS) && VUE3 |
||||
node |
||||
// #endif |
||||
}, |
||||
mounted () { |
||||
this.$nextTick(() => { |
||||
for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent); |
||||
}) |
||||
// #ifdef H5 || APP-PLUS |
||||
if (this.opts[0]) { |
||||
let i |
||||
for (i = this.childs.length; i--;) { |
||||
if (this.childs[i].name === 'img') break |
||||
} |
||||
if (i !== -1) { |
||||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({ |
||||
top: 500, |
||||
bottom: 500 |
||||
}) |
||||
this.observer.observe('._img', res => { |
||||
if (res.intersectionRatio) { |
||||
this.$set(this.ctrl, 'load', 1) |
||||
this.observer.disconnect() |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
// #endif |
||||
}, |
||||
beforeDestroy () { |
||||
// #ifdef H5 || APP-PLUS |
||||
if (this.observer) { |
||||
this.observer.disconnect() |
||||
} |
||||
// #endif |
||||
}, |
||||
methods:{ |
||||
codeLongTap(e){ |
||||
if(e.attrs.class=='hl-pre'){ |
||||
uni.setClipboardData({ |
||||
data: e.attrs['data-content'], |
||||
showToast:false, |
||||
success: () => { |
||||
uni.showToast({ |
||||
title: '代码复制成功', |
||||
duration: 1000 |
||||
}); |
||||
}, |
||||
fail: (err) => { |
||||
console.log('err', err); |
||||
} |
||||
}); |
||||
} |
||||
}, |
||||
// codeLongTap(e){ |
||||
// console.log('codeLongTap',e.attrs); |
||||
// if(e.attrs.class=='hl-pre'){ |
||||
// uni.showActionSheet({ |
||||
// itemList: ['复制代码'], |
||||
// success: function (res) { |
||||
// uni.setClipboardData({ |
||||
// data: e.attrs['data-content'], |
||||
// showToast:false, |
||||
// success: () => { |
||||
// uni.showToast({ |
||||
// title: '代码复制成功', |
||||
// duration: 1000 |
||||
// }); |
||||
// }, |
||||
// fail: (err) => { |
||||
// console.log('err', err); |
||||
// } |
||||
// }); |
||||
// }, |
||||
// fail: function (res) { |
||||
// console.log(res.errMsg); |
||||
// } |
||||
// }); |
||||
// } |
||||
// }, |
||||
// #ifdef MP-WEIXIN |
||||
toJSON () { return this }, |
||||
// #endif |
||||
/** |
||||
* @description 播放视频事件 |
||||
* @param {Event} e |
||||
*/ |
||||
play (e) { |
||||
this.root.$emit('play') |
||||
// #ifndef APP-PLUS |
||||
if (this.root.pauseVideo) { |
||||
let flag = false |
||||
const id = e.target.id |
||||
for (let i = this.root._videos.length; i--;) { |
||||
if (this.root._videos[i].id === id) { |
||||
flag = true |
||||
} else { |
||||
this.root._videos[i].pause() // 自动暂停其他视频 |
||||
} |
||||
} |
||||
// 将自己加入列表 |
||||
if (!flag) { |
||||
const ctx = uni.createVideoContext(id |
||||
// #ifndef MP-BAIDU |
||||
, this |
||||
// #endif |
||||
) |
||||
ctx.id = id |
||||
if (this.root.playbackRate) { |
||||
ctx.playbackRate(this.root.playbackRate) |
||||
} |
||||
this.root._videos.push(ctx) |
||||
} |
||||
} |
||||
// #endif |
||||
}, |
||||
|
||||
/** |
||||
* @description 图片点击事件 |
||||
* @param {Event} e |
||||
*/ |
||||
imgTap (e) { |
||||
const node = this.childs[e.currentTarget.dataset.i] |
||||
if (node.a) { |
||||
this.linkTap(node.a) |
||||
return |
||||
} |
||||
if (node.attrs.ignore) return |
||||
// #ifdef H5 || APP-PLUS |
||||
node.attrs.src = node.attrs.src || node.attrs['data-src'] |
||||
// #endif |
||||
this.root.$emit('imgtap', node.attrs) |
||||
// 自动预览图片 |
||||
if (this.root.previewImg) { |
||||
uni.previewImage({ |
||||
// #ifdef MP-WEIXIN |
||||
showmenu: this.root.showImgMenu, |
||||
// #endif |
||||
// #ifdef MP-ALIPAY |
||||
enablesavephoto: this.root.showImgMenu, |
||||
enableShowPhotoDownload: this.root.showImgMenu, |
||||
// #endif |
||||
current: parseInt(node.attrs.i), |
||||
urls: this.root.imgList |
||||
}) |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* @description 图片长按 |
||||
*/ |
||||
imgLongTap (e) { |
||||
// #ifdef APP-PLUS |
||||
const attrs = this.childs[e.currentTarget.dataset.i].attrs |
||||
if (this.opts[3] && !attrs.ignore) { |
||||
uni.showActionSheet({ |
||||
itemList: ['保存图片'], |
||||
success: () => { |
||||
const save = path => { |
||||
uni.saveImageToPhotosAlbum({ |
||||
filePath: path, |
||||
success () { |
||||
uni.showToast({ |
||||
title: '保存成功' |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
if (this.root.imgList[attrs.i].startsWith('http')) { |
||||
uni.downloadFile({ |
||||
url: this.root.imgList[attrs.i], |
||||
success: res => save(res.tempFilePath) |
||||
}) |
||||
} else { |
||||
save(this.root.imgList[attrs.i]) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
// #endif |
||||
}, |
||||
|
||||
/** |
||||
* @description 图片加载完成事件 |
||||
* @param {Event} e |
||||
*/ |
||||
imgLoad (e) { |
||||
const i = e.currentTarget.dataset.i |
||||
/* #ifndef H5 || (APP-PLUS && VUE2) */ |
||||
if (!this.childs[i].w) { |
||||
// 设置原宽度 |
||||
this.$set(this.ctrl, i, e.detail.width) |
||||
} else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) { |
||||
// 加载完毕,取消加载中占位图 |
||||
this.$set(this.ctrl, i, 1) |
||||
} |
||||
this.checkReady() |
||||
}, |
||||
|
||||
/** |
||||
* @description 检查是否所有图片加载完毕 |
||||
*/ |
||||
checkReady () { |
||||
if (this.root && !this.root.lazyLoad) { |
||||
this.root._unloadimgs -= 1 |
||||
if (!this.root._unloadimgs) { |
||||
setTimeout(() => { |
||||
this.root.getRect().then(rect => { |
||||
this.root.$emit('ready', rect) |
||||
}).catch(() => { |
||||
this.root.$emit('ready', {}) |
||||
}) |
||||
}, 350) |
||||
} |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* @description 链接点击事件 |
||||
* @param {Event} e |
||||
*/ |
||||
linkTap (e) { |
||||
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {} |
||||
const attrs = node.attrs || e |
||||
const href = attrs.href |
||||
this.root.$emit('linktap', Object.assign({ |
||||
innerText: this.root.getText(node.children || []) // 链接内的文本内容 |
||||
}, attrs)) |
||||
if (href) { |
||||
if (href[0] === '#') { |
||||
// 跳转锚点 |
||||
this.root.navigateTo(href.substring(1)).catch(() => { }) |
||||
} else if (href.split('?')[0].includes('://')) { |
||||
// 复制外部链接 |
||||
if (this.root.copyLink) { |
||||
// #ifdef H5 |
||||
window.open(href) |
||||
// #endif |
||||
// #ifdef MP |
||||
uni.setClipboardData({ |
||||
data: href, |
||||
success: () => |
||||
uni.showToast({ |
||||
title: '链接已复制' |
||||
}) |
||||
}) |
||||
// #endif |
||||
// #ifdef APP-PLUS |
||||
plus.runtime.openWeb(href) |
||||
// #endif |
||||
} |
||||
} else { |
||||
// 跳转页面 |
||||
uni.navigateTo({ |
||||
url: href, |
||||
fail () { |
||||
uni.switchTab({ |
||||
url: href, |
||||
fail () { } |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* @description 错误事件 |
||||
* @param {Event} e |
||||
*/ |
||||
mediaError (e) { |
||||
const i = e.currentTarget.dataset.i |
||||
const node = this.childs[i] |
||||
// 加载其他源 |
||||
if (node.name === 'video' || node.name === 'audio') { |
||||
let index = (this.ctrl[i] || 0) + 1 |
||||
if (index > node.src.length) { |
||||
index = 0 |
||||
} |
||||
if (index < node.src.length) { |
||||
this.$set(this.ctrl, i, index) |
||||
return |
||||
} |
||||
} else if (node.name === 'img') { |
||||
// #ifdef H5 && VUE3 |
||||
if (this.opts[0] && !this.ctrl.load) return |
||||
// #endif |
||||
// 显示错误占位图 |
||||
if (this.opts[2]) { |
||||
this.$set(this.ctrl, i, -1) |
||||
} |
||||
this.checkReady() |
||||
} |
||||
if (this.root) { |
||||
this.root.$emit('error', { |
||||
source: node.name, |
||||
attrs: node.attrs, |
||||
// #ifndef H5 && VUE3 |
||||
errMsg: e.detail.errMsg |
||||
// #endif |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
<style>/deep/ .hl-code,/deep/ .hl-pre{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}/deep/ .hl-pre{padding:1em;margin:.5em 0;overflow:auto}/deep/ .hl-pre{background:#2d2d2d}/deep/ .hl-block-comment,/deep/ .hl-cdata,/deep/ .hl-comment,/deep/ .hl-doctype,/deep/ .hl-prolog{color:#999}/deep/ .hl-punctuation{color:#ccc}/deep/ .hl-attr-name,/deep/ .hl-deleted,/deep/ .hl-namespace,/deep/ .hl-tag{color:#e2777a}/deep/ .hl-function-name{color:#6196cc}/deep/ .hl-boolean,/deep/ .hl-function,/deep/ .hl-number{color:#f08d49}/deep/ .hl-class-name,/deep/ .hl-constant,/deep/ .hl-property,/deep/ .hl-symbol{color:#f8c555}/deep/ .hl-atrule,/deep/ .hl-builtin,/deep/ .hl-important,/deep/ .hl-keyword,/deep/ .hl-selector{color:#cc99cd}/deep/ .hl-attr-value,/deep/ .hl-char,/deep/ .hl-regex,/deep/ .hl-string,/deep/ .hl-variable{color:#7ec699}/deep/ .hl-entity,/deep/ .hl-operator,/deep/ .hl-url{color:#67cdcc}/deep/ .hl-bold,/deep/ .hl-important{font-weight:700}/deep/ .hl-italic{font-style:italic}/deep/ .hl-entity{cursor:help}/deep/ .hl-inserted{color:green}/deep/ .md-p { |
||||
margin-block-start: 1em; |
||||
margin-block-end: 1em; |
||||
} |
||||
|
||||
/deep/.hl-copy{ |
||||
color:#cccccc; |
||||
} |
||||
/deep/ .md-table, |
||||
/deep/ .md-blockquote { |
||||
margin-bottom: 16px; |
||||
} |
||||
|
||||
/deep/ .md-table { |
||||
box-sizing: border-box; |
||||
width: 100%; |
||||
overflow: auto; |
||||
border-spacing: 0; |
||||
border-collapse: collapse; |
||||
} |
||||
|
||||
/deep/ .md-tr { |
||||
background-color: #fff; |
||||
border-top: 1px solid #c6cbd1; |
||||
} |
||||
|
||||
.md-table .md-tr:nth-child(2n) { |
||||
background-color: #f6f8fa; |
||||
} |
||||
|
||||
/deep/ .md-th, |
||||
/deep/ .md-td { |
||||
padding: 6px 13px !important; |
||||
border: 1px solid #dfe2e5; |
||||
} |
||||
|
||||
/deep/ .md-th { |
||||
font-weight: 600; |
||||
} |
||||
|
||||
/deep/ .md-blockquote { |
||||
padding: 0 1em; |
||||
color: #6a737d; |
||||
border-left: 0.25em solid #dfe2e5; |
||||
} |
||||
|
||||
/deep/ .md-code { |
||||
padding: 0.2em 0.4em; |
||||
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; |
||||
font-size: 85%; |
||||
background-color: rgba(27, 31, 35, 0.05); |
||||
border-radius: 3px; |
||||
} |
||||
|
||||
/deep/ .md-pre .md-code { |
||||
padding: 0; |
||||
font-size: 100%; |
||||
background: transparent; |
||||
border: 0; |
||||
} |
||||
/* a 标签默认效果 */ |
||||
._a { |
||||
padding: 1.5px 0 1.5px 0; |
||||
color: #366092; |
||||
word-break: break-all; |
||||
} |
||||
|
||||
/* a 标签点击态效果 */ |
||||
._hover { |
||||
text-decoration: underline; |
||||
opacity: 0.7; |
||||
} |
||||
|
||||
/* 图片默认效果 */ |
||||
._img { |
||||
max-width: 100%; |
||||
-webkit-touch-callout: none; |
||||
} |
||||
|
||||
/* 内部样式 */ |
||||
|
||||
._block { |
||||
display: block; |
||||
} |
||||
|
||||
._b, |
||||
._strong { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
._code { |
||||
font-family: monospace; |
||||
} |
||||
|
||||
._del { |
||||
text-decoration: line-through; |
||||
} |
||||
|
||||
._em, |
||||
._i { |
||||
font-style: italic; |
||||
} |
||||
|
||||
._h1 { |
||||
font-size: 2em; |
||||
} |
||||
|
||||
._h2 { |
||||
font-size: 1.5em; |
||||
} |
||||
|
||||
._h3 { |
||||
font-size: 1.17em; |
||||
} |
||||
|
||||
._h5 { |
||||
font-size: 0.83em; |
||||
} |
||||
|
||||
._h6 { |
||||
font-size: 0.67em; |
||||
} |
||||
|
||||
._h1, |
||||
._h2, |
||||
._h3, |
||||
._h4, |
||||
._h5, |
||||
._h6 { |
||||
display: block; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
._image { |
||||
height: 1px; |
||||
} |
||||
|
||||
._ins { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
._li { |
||||
display: list-item; |
||||
} |
||||
|
||||
._ol { |
||||
list-style-type: decimal; |
||||
} |
||||
|
||||
._ol, |
||||
._ul { |
||||
display: block; |
||||
padding-left: 40px; |
||||
margin: 1em 0; |
||||
} |
||||
|
||||
._q::before { |
||||
content: '"'; |
||||
} |
||||
|
||||
._q::after { |
||||
content: '"'; |
||||
} |
||||
|
||||
._sub { |
||||
font-size: smaller; |
||||
vertical-align: sub; |
||||
} |
||||
|
||||
._sup { |
||||
font-size: smaller; |
||||
vertical-align: super; |
||||
} |
||||
|
||||
._thead, |
||||
._tbody, |
||||
._tfoot { |
||||
display: table-row-group; |
||||
} |
||||
|
||||
._tr { |
||||
display: table-row; |
||||
} |
||||
|
||||
._td, |
||||
._th { |
||||
display: table-cell; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
._th { |
||||
font-weight: bold; |
||||
text-align: center; |
||||
} |
||||
|
||||
._ul { |
||||
list-style-type: disc; |
||||
} |
||||
|
||||
._ul ._ul { |
||||
margin: 0; |
||||
list-style-type: circle; |
||||
} |
||||
|
||||
._ul ._ul ._ul { |
||||
list-style-type: square; |
||||
} |
||||
|
||||
._abbr, |
||||
._b, |
||||
._code, |
||||
._del, |
||||
._em, |
||||
._i, |
||||
._ins, |
||||
._label, |
||||
._q, |
||||
._span, |
||||
._strong, |
||||
._sub, |
||||
._sup { |
||||
display: inline; |
||||
} |
||||
|
||||
/* #ifdef APP-PLUS */ |
||||
._video { |
||||
width: 300px; |
||||
height: 225px; |
||||
} |
||||
/* #endif */ |
||||
</style> |
File diff suppressed because it is too large
Load Diff
@ -1,129 +0,0 @@ |
||||
/** |
||||
* @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 |
@ -1,175 +0,0 @@ |
||||
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 |
@ -1,195 +0,0 @@ |
||||
<template> |
||||
<view class="zero-markdown-view"> |
||||
<mp-html :key="mpkey" :selectable="selectable" :scroll-table='scrollTable' :tag-style="tagStyle" |
||||
:markdown="true" :content="html" v-if="html"> |
||||
</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' |
||||
}, |
||||
}, |
||||
data() { |
||||
return { |
||||
html: '', |
||||
tagStyle: '', |
||||
mpkey: 'zero' |
||||
}; |
||||
}, |
||||
watch: { |
||||
markdown: function(val) { |
||||
this.html = this.markdown |
||||
} |
||||
}, |
||||
created() { |
||||
this.initTagStyle(); |
||||
}, |
||||
mounted() { |
||||
|
||||
this.html = this.markdown |
||||
}, |
||||
methods: { |
||||
|
||||
initTagStyle() { |
||||
const themeColor = this.themeColor |
||||
const codeBgColor = this.codeBgColor |
||||
let zeroStyle = { |
||||
p: ` |
||||
margin:5px 5px; |
||||
font-size: 14px; |
||||
line-height:1.25; |
||||
`, |
||||
// 一级标题 |
||||
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; |
||||
// border-top:medium solid #333; |
||||
text-align:center; |
||||
background-image:linear-gradient(to right,rgba(248,57,41,0),${themeColor},rgba(248,57,41,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 { |
||||
position: relative; |
||||
margin-top: 16rpx; |
||||
padding: 24rpx; |
||||
font-size: 28rpx; |
||||
color: #1B1B1B; |
||||
border-radius: 8rpx 16rpx 16rpx 16rpx; |
||||
background-color: #fff; |
||||
} |
||||
@keyframes loading { |
||||
0% { |
||||
opacity: 0.2; |
||||
} |
||||
20% { |
||||
opacity: 1; |
||||
} |
||||
100% { |
||||
opacity: 0.2; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
</style> |
@ -1,86 +0,0 @@ |
||||
{ |
||||
"id": "zero-markdown-view", |
||||
"displayName": "zero-markdown-view(markdown解析)", |
||||
"version": "2.0.5", |
||||
"description": "一行代码即可实现markdown解析,支持自定义主题色,支持vue2,vue3.", |
||||
"keywords": [ |
||||
"markdown", |
||||
"markdown解析", |
||||
"代码块", |
||||
"代码高亮", |
||||
"mp-html" |
||||
], |
||||
"repository": "", |
||||
"engines": { |
||||
"HBuilderX": "^3.1.0" |
||||
}, |
||||
"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" |
||||
}, |
||||
"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" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,134 +0,0 @@ |
||||
# zero-markdown-view |
||||
|
||||
|
||||
## 一. 重要更新说明 |
||||
|
||||
### v2.0.4 |
||||
- 新增点击代码块复制代码-仅小程序可用 |
||||
|
||||
### v2.0.1 |
||||
- 兼容vue2,vue3 |
||||
|
||||
### 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文本即可--> |
||||
<zero-markdown-view :markdown="content"></zero-markdown-view> |
||||
</view> |
||||
</template> |
||||
<script> |
||||
|
||||
export default { |
||||
data() { |
||||
return { |
||||
content: "\n# 一级标题\n\n## 二级标题\n\n### 三级标题\n\n### 1.2 无序列表\n\n无序列表的使用,在符号`-`后加空格使用。如下:\n- 无序列表 1\n- 无序列表 2\n - 无序列表 2.1\n - 无序列表 2.2\n\n**由于微信原因,最多支持到二级列表**。\n\n### 1.3 有序列表\n\n1. 有序列表 1\n2. 有序列表 2\n\n\n### 1.4 粗体和斜体\n\n**这个是粗体**\n\n_这个是斜体_\n\n**_这个是粗体加斜体_**\n\n\n### 1.5 链接\n\n对于该论述,欢迎读者查阅之前发过的文章,[你是《未来世界的幸存者》么?](https://mp.weixin.qq.com/s/s5IhxV2ooX3JN_X416nidA)\n\n### 1.6 引用\n\n> ### 一级引用示例\n> \n> 读一本好书,就是在和高尚的人谈话。 **——歌德**\n\n### 1.7 分割线\n\n可以在一行中用三个以上的减号来建立一个分隔线,同时需要在分隔线的上面空一行。如下:\n\n---\n\n### 1.8 删除线\n\n删除线的使用,在需要删除的文字前后各使用两个`~`,如下:\n\n~~这是要被删除的内容。~~\n\n### 1.9 表格\n\n| 姓名 | 年龄 | 工作 |\n| :--------- | :--: | -----------: |\n| 作者 | 18 | web |\n| zerojs | 20 | 前端 |\n| 太菜了 | 22 | 躺平 |\n\n\n## 2. 特殊语法\n\n### 2.1 脚注\n\n脚注与链接的区别如下所示:\n\n```markdown\n链接:[文字](链接)\n脚注:[文字](脚注解释 \"脚注名字\")\n```\n### 2.2 代码块\n\n```js\nconsole.log(\"1\");\n\nsetTimeout(function () {\n console.log(\"2\");\n process.nextTick(function () {\n console.log(\"3\");\n });\n new Promise(function (resolve) {\n console.log(\"4\");\n resolve();\n }).then(function () {\n console.log(\"5\");\n });\n});\n```\n\ndiff 不能同时和其他语言的高亮同时显示,且需要调整代码主题为微信代码主题以外的代码主题才能看到 diff 效果,使用效果如下:\n\n```diff\n+ 新增项\n- 删除项\n```\n\n**其他主题不带行号,可自定义是否换行,代码大小与当前编辑器一致**\n\n\n\n## 3 其他语法\n\n### 3.1 HTML\n\n支持原生 HTML 语法,请写内联样式,如下:\n\n<span style=\"display:block;text-align:right;color:orangered;\">橙色居右</span>\n<span style=\"display:block;text-align:center;color:orangered;\">橙色居中</span>\n\n### 3.2 UML\n\n不支持,推荐使用开源工具`https://draw.io/`制作后再导入图片" |
||||
} |
||||
}, |
||||
created() { |
||||
}, |
||||
computed: { |
||||
// 流式输出时代码块处理 , 这时候请使用 msgContent 传入组件中 |
||||
msgContent() { |
||||
if (!this.content) { |
||||
return |
||||
} |
||||
let htmlString = '' |
||||
// 判断markdown中代码块标识符的数量是否为偶数 |
||||
if (this.content.split("```").length % 2) { |
||||
let content = this.content |
||||
if (content[content.length - 1] != '\n') { |
||||
content += '\n' |
||||
} |
||||
htmlString = content |
||||
} else { |
||||
htmlString = this.content |
||||
} |
||||
return htmlString |
||||
} |
||||
}, |
||||
methods: { |
||||
|
||||
}, |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
</style> |
||||
``` |
||||
|
||||
## 三. 参数说明 |
||||
|
||||
|参数 |类型 |默认值 |描述 | |
||||
|-- |-- |-- |-- | |
||||
|markdown |String | |markdown文本 | |
||||
|themeColor |String |'#007AFF' |主题色 | |
||||
|codeBgColor|String |'#2d2d2d' |代码块背景色 (不建议修改) | |
||||
|
||||
|
||||
|
||||
## 四. 注意事项 |
||||
|
||||
### 关于代码块流式输出闪烁,可以尝试 给代码块后增加 `\n` |
||||
|
||||
|
||||
```javascript |
||||
computed: { |
||||
// 流式输出时代码块处理 , 这时候请使用 msgContent 传入组件中 |
||||
msgContent() { |
||||
if (!this.content) { |
||||
return |
||||
} |
||||
let htmlString = '' |
||||
// 判断markdown中代码块标识符的数量是否为偶数 |
||||
if (this.content.split("```").length % 2) { |
||||
let content = this.content |
||||
if (content[content.length - 1] != '\n') { |
||||
content += '\n' |
||||
} |
||||
htmlString = content |
||||
} else { |
||||
htmlString = this.content |
||||
} |
||||
return htmlString |
||||
} |
||||
}, |
||||
``` |
||||
|
||||
|
||||
|
||||
### 如何关闭点击代码块复制功能? |
||||
|
||||
找到组件文件夹 `zero-markdown-view`-`mp-html`-`highlight`-`config.js` |
||||
|
||||
**把 `copyByClickCode` 改成 false 即可** |
||||
``` |
||||
export default { |
||||
copyByClickCode: true, // 点击代码块复制 |
||||
showLanguageName: true, // 是否在代码块右上角显示语言的名称 |
||||
showLineNumber: false // 是否显示行号 |
||||
} |
||||
``` |
||||
|
||||
### 感谢 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) |
||||
|
||||
|
||||
插件预览: |
||||
 |
||||
|
||||
|
||||
> 小程序搜索: zerojs零技术 |
||||
|
||||
> 预览的小程序不一定能及时更新当前插件 |
Loading…
Reference in new issue