Template
移动端更新
This commit is contained in:
1 parent
7f8e68bb0a
commit
c0d27be99b
432 files changed
+100843
No files matched your search
+2
@@ -0,0 +1,2 @@
|
||||
# 插件
|
||||
本目录下是一些扩展插件,可以按照需要选用以实现更加丰富的功能
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# audio
|
||||
功能:音乐播放器
|
||||
大小:*≈4KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √(nvue 不支持) |
|
||||
|
||||
百度小程序原生包在此 [问题](https://smartprogram.baidu.com/forum/topic/show/125787) 未解决前无法使用
|
||||
|
||||
说明:
|
||||
在大多数小程序平台,*audio* 标签已被废弃或无法使用,本插件可以代替 *audio* 标签播放音乐,并实现以下优化:
|
||||
1. *pause-video* 属性也可以应用于音频,即播放一个音视频时可以自动暂停其他正在播放的音视频
|
||||
2. 增加了一个可以拖动的进度条
|
||||
3. 组件大小可以根据页面宽度自动调整
|
||||
4. 支持 *autoplay* 属性
|
||||
5. 播放被后台打断时,页面显示后自动继续播放
|
||||
|
||||
基础库要求:
|
||||
支付宝 *1.23.4+* ,其余平台满足最低要求即可
|
||||
第 *5* 条仅微信 *2.2.3+* 、*QQ*、百度支持
|
||||
|
||||
?> 如果希望页面上使用本组件,组件的路径为 *path/to/mp-html/audio/audio*
|
||||
属性和事件基本同 *audio* 组件,组件实例上提供了 *setSrc*、*play*、*seek*、*pause*、*stop* 方法可供控制播放状态
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
usingComponents: {
|
||||
'my-audio': '../audio/audio'
|
||||
},
|
||||
handler (file) {
|
||||
// 删去原来的 audio 标签
|
||||
if (file.basename === 'node.wxml' || file.basename === 'node.vue') {
|
||||
file.contents = Buffer.from(file.contents.toString().replace(/<audio[\s\S]+?>/, ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
const ctx = {}
|
||||
|
||||
module.exports = {
|
||||
get: id => ctx[id],
|
||||
set: (id, vm) => { ctx[id] = vm },
|
||||
remove: id => { ctx[id] = undefined }
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @fileoverview audio 插件
|
||||
*/
|
||||
const context = require('./context')
|
||||
let index = 0
|
||||
|
||||
function Audio (vm) {
|
||||
this.vm = vm
|
||||
}
|
||||
|
||||
Audio.prototype.onUpdate = function () {
|
||||
this.audios = []
|
||||
}
|
||||
|
||||
Audio.prototype.onParse = function (node) {
|
||||
if (node.name === 'audio') {
|
||||
if (!node.attrs.id) {
|
||||
node.attrs.id = 'a' + index++
|
||||
}
|
||||
this.audios.push(node.attrs.id)
|
||||
}
|
||||
}
|
||||
|
||||
Audio.prototype.onLoad = function () {
|
||||
setTimeout(() => {
|
||||
for (let i = 0; i < this.audios.length; i++) {
|
||||
const ctx = context.get(this.audios[i])
|
||||
ctx.id = this.audios[i]
|
||||
this.vm._videos.push(ctx)
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
module.exports = Audio
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* @fileoverview audio 组件
|
||||
*/
|
||||
const context = require('./context')
|
||||
|
||||
Component({
|
||||
data: {
|
||||
time: '00:00'
|
||||
},
|
||||
properties: {
|
||||
name: String, // 音乐名
|
||||
author: String, // 作者
|
||||
poster: String, // 海报图片地址
|
||||
autoplay: Boolean, // 是否自动播放
|
||||
controls: Boolean, // 是否显示控件
|
||||
loop: Boolean, // 是否循环播放
|
||||
src: { // 源地址
|
||||
type: String,
|
||||
observer (src) {
|
||||
this.setSrc(src)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// 创建内部 context
|
||||
this._ctx = wx.createInnerAudioContext()
|
||||
this._ctx.onError(err => {
|
||||
this.setData({
|
||||
error: true
|
||||
})
|
||||
this.triggerEvent('error', err)
|
||||
})
|
||||
this._ctx.onTimeUpdate(() => {
|
||||
const time = this._ctx.currentTime
|
||||
const min = parseInt(time / 60)
|
||||
const sec = Math.ceil(time % 60)
|
||||
const data = {}
|
||||
data.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
|
||||
// 不在拖动状态下需要更新进度条
|
||||
if (!this.lastTime) {
|
||||
data.value = time / this._ctx.duration * 100
|
||||
}
|
||||
this.setData(data)
|
||||
})
|
||||
this._ctx.onEnded(() => {
|
||||
if (!this.properties.loop) {
|
||||
this.setData({
|
||||
playing: false
|
||||
})
|
||||
}
|
||||
})
|
||||
// #ifndef ALIPAY
|
||||
},
|
||||
attached () {
|
||||
context.set(this.id, this)
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
context.set(this.properties.id, this)
|
||||
this.setSrc(this.properties.src)
|
||||
// #endif
|
||||
},
|
||||
// #ifdef MP-ALIPAY
|
||||
didUpdate (e) {
|
||||
if (e.src !== this.properties.src) {
|
||||
this.setSrc(this.properties.src)
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
detached () {
|
||||
this._ctx.destroy()
|
||||
// #ifndef MP-ALIPAY
|
||||
context.remove(this.id)
|
||||
// #endif
|
||||
// #ifdef MP_ALIPAY
|
||||
context.remove(this.properties.id)
|
||||
// #endif
|
||||
},
|
||||
// #ifndef ALIPAY | TOUTIAO
|
||||
pageLifetimes: {
|
||||
show () {
|
||||
// 播放被后台打断时,页面显示后自动继续播放
|
||||
if (this.data.playing && this._ctx.paused) {
|
||||
this._ctx.play()
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
/**
|
||||
* @description 设置源
|
||||
* @param {string} src 源地址
|
||||
*/
|
||||
setSrc (src) {
|
||||
this._ctx.autoplay = this.properties.autoplay
|
||||
this._ctx.loop = this.properties.loop
|
||||
this._ctx.src = src
|
||||
if (this.properties.autoplay && !this.data.playing) {
|
||||
this.setData({
|
||||
playing: true
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 播放音乐
|
||||
*/
|
||||
play () {
|
||||
this._ctx.play()
|
||||
this.setData({
|
||||
playing: true
|
||||
})
|
||||
this.triggerEvent('play'
|
||||
// #ifdef MP-ALIPAY
|
||||
, {
|
||||
target: {
|
||||
id: this.props.id
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 暂停音乐
|
||||
*/
|
||||
pause () {
|
||||
this._ctx.pause()
|
||||
this.setData({
|
||||
playing: false
|
||||
})
|
||||
this.triggerEvent('pause')
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置播放速率
|
||||
* @param {Number} rate 播放速率
|
||||
*/
|
||||
playbackRate (rate) {
|
||||
this._ctx.playbackRate = rate
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 停止音乐
|
||||
*/
|
||||
stop () {
|
||||
this._ctx.stop()
|
||||
this.setData({
|
||||
playing: false,
|
||||
time: '00:00'
|
||||
})
|
||||
this.triggerEvent('stop')
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 控制进度
|
||||
* @param {number} sec 秒数
|
||||
*/
|
||||
seek (sec) {
|
||||
this._ctx.seek(sec)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 移动进度条
|
||||
* @param {event} e
|
||||
* @private
|
||||
*/
|
||||
_seeking (e) {
|
||||
// 避免过于频繁 setData
|
||||
if (e.timeStamp - this.lastTime < 200) return
|
||||
const time = Math.round(e.detail.value / 100 * this._ctx.duration)
|
||||
const min = parseInt(time / 60)
|
||||
const sec = time % 60
|
||||
this.setData({
|
||||
time: (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
|
||||
})
|
||||
this.lastTime = e.timeStamp
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 进度条移动完毕
|
||||
* @param {event} e
|
||||
* @private
|
||||
*/
|
||||
_seeked (e) {
|
||||
this._ctx.seek(e.detail.value / 100 * this._ctx.duration)
|
||||
this.lastTime = undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<view wx:if="{{controls}}" class="_contain">
|
||||
<!-- 海报和按钮 -->
|
||||
<view class="_poster" style="background-image:url('{{poster}}')">
|
||||
<view class="_button" bindtap="{{playing?'pause':'play'}}">
|
||||
<view class="{{playing?'_pause':'_play'}}" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 曲名和作者 -->
|
||||
<view class="_title">
|
||||
<view class="_name">{{name||'未知音频'}}</view>
|
||||
<view class="_author">{{author||'未知作者'}}</view>
|
||||
</view>
|
||||
<!-- 进度条 -->
|
||||
<slider class="_slider" activeColor="#585959" block-size="12" disabled="{{error}}" value="{{value}}" bindchanging="_seeking" bindchange="_seeked" />
|
||||
<!--播放时间-->
|
||||
<view class="_time">{{time}}</view>
|
||||
</view>
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/* 顶层容器 */
|
||||
._contain {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 290px;
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 播放、暂停按钮 */
|
||||
._button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
background-color: rgb(0, 0, 0, 0.2);
|
||||
border: 1px solid white;
|
||||
border-radius: 50%;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
._play {
|
||||
margin-left: 2px;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 8px solid white;
|
||||
}
|
||||
|
||||
._pause {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* 海报 */
|
||||
._poster {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background-color: #e6e6e6;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
/* 标题栏 */
|
||||
._title {
|
||||
flex: 1;
|
||||
margin: 4px 0 0 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
._author {
|
||||
width: 45px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
._name {
|
||||
width: 140px;
|
||||
font-size: 15px;
|
||||
line-height: 39px;
|
||||
}
|
||||
|
||||
._author,
|
||||
._name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
._slider {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 8px;
|
||||
width: 140px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 播放时间 */
|
||||
._time {
|
||||
margin: 7px 14px 0 0;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 响应式布局,大屏幕用更大的尺寸 */
|
||||
@media (min-width: 400px) {
|
||||
._contain {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
._button {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
._poster {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
._author {
|
||||
width: 60px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
._name {
|
||||
width: 180px;
|
||||
font-size: 19px;
|
||||
line-height: 55px;
|
||||
}
|
||||
|
||||
._slider {
|
||||
right: 20px;
|
||||
bottom: 10px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
._time {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
template: '<my-audio wx:if="{{n.name==\'audio\'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" author="{{n.attrs.author}}" controls="{{n.attrs.controls}}" autoplay="{{n.attrs.autoplay}}" loop="{{n.attrs.loop}}" name="{{n.attrs.name}}" poster="{{n.attrs.poster}}" src="{{n.src[ctrl[i]||0]}}" data-i="{{i}}" data-source="audio" bindplay="play" binderror="mediaError" />'
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<view v-if="controls" @click="onClick" class="_contain">
|
||||
<!-- 海报和按钮 -->
|
||||
<view class="_poster" :style="'background-image:url('+poster+')'">
|
||||
<view class="_button" @tap="_buttonTap">
|
||||
<view :class="playing?'_pause':'_play'" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 曲名和作者 -->
|
||||
<view class="_title">
|
||||
<view class="_name">{{name||'未知音频'}}</view>
|
||||
<view class="_author">{{author||'未知作者'}}</view>
|
||||
</view>
|
||||
<!-- 进度条 -->
|
||||
<slider class="_slider" activeColor="#585959" block-size="12" handle-size="12" :disabled="error" :value="value" @changing="_seeking" @change="_seeked" />
|
||||
<!--播放时间-->
|
||||
<view class="_time">{{time||'00:00'}}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @fileoverview audio 组件
|
||||
*/
|
||||
import context from './context'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
error: false,
|
||||
playing: false,
|
||||
time: '00:00',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
aid: String,
|
||||
name: String, // 音乐名
|
||||
author: String, // 作者
|
||||
poster: String, // 海报图片地址
|
||||
autoplay: [Boolean, String], // 是否自动播放
|
||||
controls: [Boolean, String], // 是否显示控件
|
||||
loop: [Boolean, String], // 是否循环播放
|
||||
src: String // 源地址
|
||||
},
|
||||
watch: {
|
||||
src (src) {
|
||||
this.setSrc(src)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this._ctx = uni.createInnerAudioContext()
|
||||
this._ctx.onError((err) => {
|
||||
this.error = true
|
||||
this.$emit('error', err)
|
||||
})
|
||||
this._ctx.onTimeUpdate(() => {
|
||||
const time = this._ctx.currentTime
|
||||
const min = parseInt(time / 60)
|
||||
const sec = Math.ceil(time % 60)
|
||||
this.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
|
||||
if (!this.lastTime) {
|
||||
this.value = time / this._ctx.duration * 100 // 不在拖动状态下
|
||||
}
|
||||
})
|
||||
this._ctx.onEnded(() => {
|
||||
if (!this.loop) {
|
||||
this.playing = false
|
||||
}
|
||||
})
|
||||
context.set(this.aid, this)
|
||||
this.setSrc(this.src)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this._ctx.destroy()
|
||||
context.remove(this.aid)
|
||||
},
|
||||
onPageShow () {
|
||||
if (this.playing && this._ctx.paused) {
|
||||
this._ctx.play()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 设置源
|
||||
setSrc (src) {
|
||||
this._ctx.autoplay = this.autoplay
|
||||
this._ctx.loop = this.loop
|
||||
this._ctx.src = src
|
||||
if (this.autoplay && !this.playing) {
|
||||
this.playing = true
|
||||
}
|
||||
},
|
||||
// 播放
|
||||
play () {
|
||||
this._ctx.play()
|
||||
this.playing = true
|
||||
this.$emit('play', {
|
||||
target: {
|
||||
id: this.aid
|
||||
}
|
||||
})
|
||||
},
|
||||
// 暂停
|
||||
pause () {
|
||||
this._ctx.pause()
|
||||
this.playing = false
|
||||
this.$emit('pause')
|
||||
},
|
||||
// 设置播放速率
|
||||
playbackRate (rate) {
|
||||
this._ctx.playbackRate = rate
|
||||
},
|
||||
// 移动进度条
|
||||
seek (sec) {
|
||||
this._ctx.seek(sec)
|
||||
},
|
||||
// 内部方法
|
||||
_buttonTap () {
|
||||
if (this.playing) this.pause()
|
||||
else this.play()
|
||||
},
|
||||
_seeking (e) {
|
||||
// 避免过于频繁 setData
|
||||
if (e.timeStamp - this.lastTime < 200) return
|
||||
const time = Math.round(e.detail.value / 100 * this._ctx.duration)
|
||||
const min = parseInt(time / 60)
|
||||
const sec = time % 60
|
||||
this.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
|
||||
this.lastTime = e.timeStamp
|
||||
},
|
||||
_seeked (e) {
|
||||
this.seek(e.detail.value / 100 * this._ctx.duration)
|
||||
this.lastTime = undefined
|
||||
},
|
||||
onClick(e) {
|
||||
this.$emit('onClick', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 顶层容器 */
|
||||
._contain {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 290px;
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 播放、暂停按钮 */
|
||||
._button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
background-color: rgb(0, 0, 0, 0.2);
|
||||
border: 1px solid white;
|
||||
border-radius: 50%;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
._play {
|
||||
margin-left: 2px;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 8px solid white;
|
||||
}
|
||||
|
||||
._pause {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* 海报 */
|
||||
._poster {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background-color: #e6e6e6;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
/* 标题栏 */
|
||||
._title {
|
||||
flex: 1;
|
||||
margin: 4px 0 0 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
._author {
|
||||
width: 45px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
._name {
|
||||
width: 140px;
|
||||
font-size: 15px;
|
||||
line-height: 39px;
|
||||
}
|
||||
|
||||
._author,
|
||||
._name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
._slider {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 8px;
|
||||
width: 140px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 播放时间 */
|
||||
._time {
|
||||
margin: 7px 14px 0 0;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 响应式布局,大屏幕用更大的尺寸 */
|
||||
@media (min-width: 400px) {
|
||||
._contain {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
._button {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
._poster {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
._author {
|
||||
width: 60px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
._name {
|
||||
width: 180px;
|
||||
font-size: 19px;
|
||||
line-height: 55px;
|
||||
}
|
||||
|
||||
._slider {
|
||||
right: 20px;
|
||||
bottom: 10px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
._time {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
template: '<my-audio v-if="n.name==\'audio\'" :class="n.attrs.class" :style="n.attrs.style" :aid="n.attrs.id" :author="n.attrs.author" :controls="n.attrs.controls" :autoplay="n.attrs.autoplay" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" data-source="audio" @play="play" @error="mediaError" />'
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# card
|
||||
功能:商品(联络人)信息卡
|
||||
大小:*≈7KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √(nvue 不支持) |
|
||||
|
||||
### 效果图
|
||||

|
||||
|
||||
### 参数列表
|
||||
|参数名|是否必须|类型|说明|
|
||||
|:---- |:---|:----- |----- |
|
||||
|src|是|String|图片Url|
|
||||
|title|是|String|标题|
|
||||
|desc|是|String|描述|
|
||||
|url|是|String|跳转url|
|
||||
|color|是|String|文字颜色|
|
||||
|bgcolor|是|String|卡片背景颜色|
|
||||
|border|是|String|卡片边框颜色|
|
||||
|
||||
### 说明:
|
||||
1. 可以显示商品信息卡片/联络人信息卡片
|
||||
|
||||
### 基础库要求:
|
||||
满足最低要求即可
|
||||
|
||||
?> 如果希望页面上使用本组件,组件的路径为 *path/to/mp-html/card/card*
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
usingComponents: {
|
||||
'my-card': '../card/card'
|
||||
},
|
||||
handler (file) {
|
||||
if (file.isBuffer()) {
|
||||
let content = file.contents.toString()
|
||||
if (file.path.includes('parser.js')) {
|
||||
content = content.replace(/trustTags\s*:\s*makeMap\('/, "trustTags: makeMap('card,").replace(/voidTags\s*:\s*makeMap\('/, "voidTags: makeMap('card,")
|
||||
}
|
||||
file.contents = Buffer.from(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @fileoverview Card 插件
|
||||
*/
|
||||
function Card (vm) {
|
||||
}
|
||||
|
||||
module.exports = Card
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
template: '<my-card wx:if="{{n.name==\'card\'}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" mode="{{opts[5]}}" src="{{n.attrs.src}}" title="{{n.attrs.title}}" desc="{{n.attrs.desc}}" url="{{n.attrs.url}}" color="{{n.attrs.color}}" bgcolor="{{n.attrs.bgcolor}}" border="{{n.attrs.border}}" name="{{n.attrs.name}}" data-i="{{i}}" data-source="card" />'
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @fileoverview card 组件
|
||||
*/
|
||||
Component({
|
||||
properties: {
|
||||
mode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
src: String,
|
||||
title: String,
|
||||
desc: String,
|
||||
url: String,
|
||||
color: String,
|
||||
bgcolor: String,
|
||||
border: String
|
||||
},
|
||||
data: {},
|
||||
methods: {
|
||||
onClick (e) {
|
||||
if (this.properties.url && this.properties.url.trim().length > 6 && !this.properties.mode) {
|
||||
wx.navigateTo({ url: this.properties.url })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<view class="card" bindtap="onClick" style="background-color:{{bgcolor||'#a4d0ff'}};border:{{border||'1px solid #FFF'}};color:{{color||'#000'}}">
|
||||
<image class="card-img" mode="aspectFill" src="{{src}}" />
|
||||
<view class="text-wrap text-wrap-width" wx:if="{{!!desc}}">
|
||||
<view class="title one-t">{{title}}</view>
|
||||
<view class="desc one-t">{{desc}}</view>
|
||||
</view>
|
||||
<view wx:else class="text-wrap-width title more-t">{{title}}</view>
|
||||
<image class="card-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAABCFBMVEUAAAC/v7+qqqqZmZmLi6KJnZ2ImZmHlpaGlKGMjJmSkp6Li5eQkJuKlZ+Pj5mOjpeJkpuNjZ6IkJmHj5eLi5uKkpmGjZqJj5uLi5eIjpmIjZiKj5qKj5mJjpiHjJqJjZaGj5iKj5eIjJmKjpqHi5eGjZeIi5eHjZiIi5eHjJiIjpaHjJeIjZiGjJeIjZiGjJaGjJeIi5aGjJeHi5eHi5aHjJeHjJaHjJeGi5eHjJaHjJeGi5aGi5aHjJeHjJaGjJeGjJeHjJaHi5eGi5eHjJaHjJeGjJaGjJeHi5aHjJeHjJeHi5aGjJeHi5aGjJaHi5eGjJaHi5eHjJaGi5eGi5aHjJeGi5aGi5apAvjmAAAAV3RSTlMABAYKCw0PERMUFRYXGBkbHB0eICEjJiksLS8wMjQ1ODk7PD9ATFZXWFlaW1xdXl+Hi6msu7/Dx8vMzs/R0tTV19na3N3f4uTn6evs7e7v8PHy9PX7/P18cCTXAAABEklEQVRo3u2YWU5CQRQFn4qCM4LzhIoDAorzrIgCigiCimf/O/Gj3UIlmJxaQFXSea/T90aRMcYYY4zpG0ZPu9cZMnAi6SsLBjqS9LnJBcqSpC53Sjs/kqSPNaxwGAqtFbrQXKILjQW68DpPF17m6EI9TRdqM3TheZouVCbpQnkcK5RC4T5BF27jdOFqhC5cDtOFixhdOB+iC2cDdOEoggttLrDbk6QW5/+WJB1T/r1e+FAHWT/2q/35scsiF/w3cdZ/R13Y+8H/MMb6Hycgfz74n6ZYfzXJ+mspyF8I/vos68cep0X4eV2EB4SD4H9bZP3vy+yTtL3KjrGddXgQ34BXCVvwMmT7P69zjDHGGGP6gF83lHISOctsKQAAAABJRU5ErkJggg=="></image>
|
||||
</view>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
.one-t {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
transition: all linear 0.2s;
|
||||
}
|
||||
.more-t {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
transition: all linear 0.2s;
|
||||
}
|
||||
.card {
|
||||
width: 80%;
|
||||
margin: 10rpx auto;
|
||||
max-width: 700rpx;
|
||||
max-height: 140rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0 20rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.card-img {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 12rpx;
|
||||
flex: 0 0 96rpx;
|
||||
}
|
||||
.card-icon {
|
||||
width: 30rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
.card .text-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card .text-wrap-width {
|
||||
width: 72%;
|
||||
}
|
||||
.card .title {
|
||||
font-weight: bold;
|
||||
font-size: 34rpx;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
.card .desc {
|
||||
font-size: 27rpx;
|
||||
line-height: 37rpx;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
template: '<my-card v-if="n.name==\'card\'" :class="n.attrs.class" :style="n.attrs.style" :mode="opts[5]" :src="n.attrs.src" :title="n.attrs.title" :desc="n.attrs.desc" :url="n.attrs.url" :color="n.attrs.color" :bgcolor="n.attrs.bgcolor" :border="n.attrs.border" :name="n.attrs.name" :data-i="i" data-source="card" />'
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<view class="card" @click="onClick" :style="[customStyle]" :data-i="$attrs['data-i']">
|
||||
<image class="card-img" mode="aspectFill" :src="src" />
|
||||
<view class="text-wrap text-wrap-width" v-if="!!desc">
|
||||
<view class="title one-t">{{title}}</view>
|
||||
<view class="desc one-t">{{desc}}</view>
|
||||
</view>
|
||||
<view v-else class="text-wrap-width title more-t">{{title}}</view>
|
||||
<image class="card-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAABCFBMVEUAAAC/v7+qqqqZmZmLi6KJnZ2ImZmHlpaGlKGMjJmSkp6Li5eQkJuKlZ+Pj5mOjpeJkpuNjZ6IkJmHj5eLi5uKkpmGjZqJj5uLi5eIjpmIjZiKj5qKj5mJjpiHjJqJjZaGj5iKj5eIjJmKjpqHi5eGjZeIi5eHjZiIi5eHjJiIjpaHjJeIjZiGjJeIjZiGjJaGjJeIi5aGjJeHi5eHi5aHjJeHjJaHjJeGi5eHjJaHjJeGi5aGi5aHjJeHjJaGjJeGjJeHjJaHi5eGi5eHjJaHjJeGjJaGjJeHi5aHjJeHjJeHi5aGjJeHi5aGjJaHi5eGjJaHi5eHjJaGi5eGi5aHjJeGi5aGi5apAvjmAAAAV3RSTlMABAYKCw0PERMUFRYXGBkbHB0eICEjJiksLS8wMjQ1ODk7PD9ATFZXWFlaW1xdXl+Hi6msu7/Dx8vMzs/R0tTV19na3N3f4uTn6evs7e7v8PHy9PX7/P18cCTXAAABEklEQVRo3u2YWU5CQRQFn4qCM4LzhIoDAorzrIgCigiCimf/O/Gj3UIlmJxaQFXSea/T90aRMcYYY4zpG0ZPu9cZMnAi6SsLBjqS9LnJBcqSpC53Sjs/kqSPNaxwGAqtFbrQXKILjQW68DpPF17m6EI9TRdqM3TheZouVCbpQnkcK5RC4T5BF27jdOFqhC5cDtOFixhdOB+iC2cDdOEoggttLrDbk6QW5/+WJB1T/r1e+FAHWT/2q/35scsiF/w3cdZ/R13Y+8H/MMb6Hycgfz74n6ZYfzXJ+mspyF8I/vos68cep0X4eV2EB4SD4H9bZP3vy+yTtL3KjrGddXgQ34BXCVvwMmT7P69zjDHGGGP6gF83lHISOctsKQAAAABJRU5ErkJggg=="></image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
mode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
src: String,
|
||||
title: String,
|
||||
desc: String,
|
||||
url: String,
|
||||
color: String,
|
||||
bgcolor: String,
|
||||
border: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customStyle () {
|
||||
return {
|
||||
'background-color': this.bgColor || '#a4d0ff',
|
||||
border: this.border || '1px solid #FFF',
|
||||
color: this.color || '#000'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick (e) {
|
||||
if (this.url && this.url.trim().length > 6 && !this.mode) {
|
||||
uni.navigateTo({ url: this.url })
|
||||
}
|
||||
this.$emit('click', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.one-t {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
transition: all linear 0.2s;
|
||||
}
|
||||
|
||||
.more-t {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break:break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
transition: all linear 0.2s;
|
||||
}
|
||||
|
||||
.card {
|
||||
|
||||
width: 80%;
|
||||
margin: 10rpx auto;
|
||||
max-width: 700rpx;
|
||||
max-height: 140rpx;
|
||||
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
padding: 20rpx 0 20rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
|
||||
&-img {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 12rpx;
|
||||
flex: 0 0 96rpx;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
width: 30rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
&-width {
|
||||
width: 72%;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 34rpx;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 27rpx;
|
||||
line-height: 37rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# editable
|
||||
功能:富文本编辑
|
||||
下表列出了本插件与原生 *editor* 组件的功能差异,可按需选用
|
||||
|
||||
| 组件 | 优点 | 缺点 |
|
||||
|:---:|:---:|:---:|
|
||||
| 原生 *editor* | 底层通过 *contenteditable* 实现,编辑流畅 | 支持标签少(不支持音视频、表格以及 *section* 等常用标签)、部分小程序平台不支持或低版本不兼容 |
|
||||
| 本插件 | 支持标签全面、支持平台全面 | 编辑灵活性不够强 |
|
||||
|
||||
大小:*≈17.5KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √(nvue 不支持) |
|
||||
|
||||
说明:
|
||||
引入本插件后,会给组件添加以下属性:
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| editable | Boolean | false | 是否开启内容编辑 |
|
||||
| placeholder | String | 请输入 | 输入框为空时占位符(`2.1.0+`) |
|
||||
|
||||
添加以下事件:
|
||||
|
||||
| 事件名 | 触发时机 | 用途 |
|
||||
|:---:|:---:|:---:|
|
||||
| remove(`2.2.0+`) | 删除图片/视频/音频标签时 | 删除已上传的线上文件 |
|
||||
|
||||
支持以下操作:
|
||||
|
||||
| 类型 | 操作 |
|
||||
|:---:|:---:|
|
||||
| 文本 | 修改 |
|
||||
| 图片 | 更换链接、调整宽度、设置成超链接(`2.0.4+`)、设置预览图链接、禁用预览、删除 |
|
||||
| 链接 | 更换链接、删除 |
|
||||
| 音视频 | 设置封面、设置循环播放、设置自动播放(`2.2.0+`)、删除 |
|
||||
| 普通标签 | 设置字体大小、斜体、粗体、下划线(`2.0.4+`)、居中、缩进、删除 |
|
||||
|
||||
> `2.2.1` 版本起所有标签支持上下移动操作,但仅限同级标签间移动,即在有同级标签且非第一个(或最后一个)时可以上移(或下移)
|
||||
|
||||
> 在支付宝小程序中使用时需要在页面样式中添加 *page { position: relative; }* 避免 *tooltip* 错位
|
||||
|
||||
> 菜单项可以通过编辑 *plugins/editable/config.js* 进行修改,仅可以删减或调整顺序,添加或更名无效
|
||||
|
||||
组件实例上提供了以下方法(*editable* 属性为 *true* 时才可以调用):
|
||||
|
||||
| 名称 | 功能 |
|
||||
|:---:|:---:|
|
||||
| undo | 撤销一个操作 |
|
||||
| redo | 重做一个操作 |
|
||||
| insertHtml | 在光标处插入指定 html 内容(`2.1.0+`) |
|
||||
| insertImg | 在光标处插入一张图片 |
|
||||
| insertTable(rows, cols) | 在光标处插入一个 rows 行 cols 列的表格(`2.1.3+`) |
|
||||
| insertVideo | 在光标处插入一个视频 |
|
||||
| insertAudio | 在光标处插入一个音频 |
|
||||
| insertLink | 在光标处插入一个链接 |
|
||||
| insertText | 在光标处插入一段文本 |
|
||||
| clear | 清空内容 |
|
||||
| getContent | 获取编辑后的 html 内容 |
|
||||
|
||||
> 考虑到不同场景下希望获取链接的方法不同,需要在初始时给组件设置一个 *getSrc* 方法(否则插入图片、音视频、链接或修改链接等操作无法使用),每次组件内需要链接时会调用此方法,开发者可在此方法中自行决定如何获取链接,返回 **线上地址** 即可(具体用法见下方示例)
|
||||
|
||||
编辑完成后,通过 *getContent* 方法获取编辑后的 *html*,最后将 *editable* 属性设置为 *false* 即可正常渲染
|
||||
|
||||
> 点击保存按钮时,部分平台 *tap* 事件早于 *blur* 事件触发,直接获取内容可能导致无法获取当前编辑的文本内容,因此建议设置一个小的延时后获取(可参考下方示例,[详细](https://github.com/jin-yufeng/mp-html/issues/368))
|
||||
|
||||
示例:
|
||||
```javascript
|
||||
Page({
|
||||
onLoad () {
|
||||
// ctx 为组件实例,获取方法见上
|
||||
/**
|
||||
* @description 设置获取链接的方法
|
||||
* @param {String} type 链接的类型(img/video/audio/link)
|
||||
* @param {String} value 修改链接时,这里会传入旧值
|
||||
* @returns {Promise} 返回线上地址(2.2.0 版本起设置了 domain 属性时,可以缺省主域名)
|
||||
* type 为 audio/video 时,可以返回一个源地址数组
|
||||
* 2.1.3 版本起 type 为 audio 时,可以返回一个 object,包含 src、name、author、poster 等字段
|
||||
* 2.2.0 版本起 type 为 img 时,可以返回一个源地址数组,表示插入多张图片(修改链接时仅限一张)
|
||||
*/
|
||||
this.ctx.getSrc = (type, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 以图片为例
|
||||
if (type == 'img') {
|
||||
wx.chooseImage({
|
||||
count: value === undefined ? 9 : 1, // 2.2.0 版本起插入图片时支持多张(修改图片链接时仅限一张)
|
||||
success: res => {
|
||||
wx.showLoading({
|
||||
title: '上传中'
|
||||
});
|
||||
(async ()=>{
|
||||
const arr = []
|
||||
for (let item of res.tempFilePaths) {
|
||||
// 依次上传
|
||||
const src = await upload(item)
|
||||
arr.push(src)
|
||||
}
|
||||
return arr
|
||||
})().then(res => {
|
||||
wx.hideLoading()
|
||||
resolve(res)
|
||||
})
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
finishEdit () {
|
||||
setTimeout(() => {
|
||||
var html = ctx.getContent() // 获取编辑好的 html
|
||||
// 上传 html
|
||||
wx.request({
|
||||
url: 'xxx',
|
||||
data: {
|
||||
html
|
||||
},
|
||||
success: () => {
|
||||
this.setData({
|
||||
editable: false // 结束编辑
|
||||
})
|
||||
}
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**示例项目**:
|
||||
微信小程序点击 [代码片段](https://developers.weixin.qq.com/s/GFbJKum77eBy) 即可在微信开发者工具中导入;*uni-app* 下载 [示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip) 在 *HBuilder X* 中打开即可体验;注意示例项目中不一定包含最新版本,仅供参考使用方法
|
||||
|
||||
注意事项:
|
||||
1. 不要在 *editable* 属性被设置为 *true* 前通过 *setContent* 方法(用 *content* 属性)设置内容,否则在切换为 *true* 后会变成空白
|
||||
2. *editable* 属性为 *true* 时不支持在 *scroll-view* 中使用,否则提示框的位置可能不正确
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// 以下项目可以删减或更换顺序,但不能添加或更改名字
|
||||
module.exports = {
|
||||
// 普通标签的菜单项
|
||||
node: ['大小', '颜色', '斜体', '粗体', '下划线', '居中', '缩进', '上移', '下移', '删除'],
|
||||
// 可以设置的文字颜色,此项可以添加 css 颜色
|
||||
color: ['red', 'yellow', 'blue', 'green', 'gray', 'white', 'black'],
|
||||
// 图片的菜单项
|
||||
img: ['换图', '宽度', '超链接', '预览图', '禁用预览', '上移', '下移', '删除'],
|
||||
// 链接的菜单项
|
||||
link: ['更换链接', '上移', '下移', '删除'],
|
||||
// 音视频的菜单项
|
||||
media: ['封面', '循环', '自动播放', '上移', '下移', '删除'],
|
||||
// 卡片的菜单项
|
||||
card: ['上移', '下移', '删除']
|
||||
}
|
||||
+813
@@ -0,0 +1,813 @@
|
||||
const path = require('path')
|
||||
/* global getTop */
|
||||
module.exports = {
|
||||
style: `/* #ifndef MP-ALIPAY */
|
||||
._address,
|
||||
._article,
|
||||
._aside,
|
||||
._body,
|
||||
._caption,
|
||||
._center,
|
||||
._cite,
|
||||
._footer,
|
||||
._header,
|
||||
._html,
|
||||
._nav,
|
||||
._pre,
|
||||
._section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* #endif */`,
|
||||
methods: {
|
||||
/**
|
||||
* @description 开始编辑文本
|
||||
* @param {Event} e
|
||||
*/
|
||||
editStart (e) {
|
||||
if (this.properties.opts[5]) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
if (!this.data.ctrl['e' + i] && this.properties.opts[5] !== 'simple') {
|
||||
// 显示虚线框
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 1
|
||||
})
|
||||
// 点击其他地方则取消虚线框
|
||||
setTimeout(() => {
|
||||
this.root._mask.push(() => {
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 0
|
||||
})
|
||||
})
|
||||
}, 50)
|
||||
this.root._edit = this
|
||||
this.i = i
|
||||
this.cursor = this.getNode(i).text.length
|
||||
} else {
|
||||
if (this.properties.opts[5] === 'simple') {
|
||||
this.root._edit = this
|
||||
this.i = i
|
||||
this.cursor = this.getNode(i).text.length
|
||||
}
|
||||
this.root._mask.pop()
|
||||
this.root._maskTap()
|
||||
// 将 text 转为 textarea
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 2
|
||||
})
|
||||
// 延时对焦,避免高度错误
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 3
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 输入文本
|
||||
* @param {Event} e
|
||||
*/
|
||||
editInput (e) {
|
||||
const i = e.target.dataset.i
|
||||
// 替换连续空格
|
||||
const value = e.detail.value.replace(/ {2,}/, $ => {
|
||||
let res = '\xa0'
|
||||
for (let i = 1; i < $.length; i++) {
|
||||
res += '\xa0'
|
||||
}
|
||||
return res
|
||||
})
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].text', this.getNode(i).text, value) // 记录编辑历史
|
||||
this.cursor = e.detail.cursor
|
||||
},
|
||||
/**
|
||||
* @description 完成编辑文本
|
||||
* @param {Event} e
|
||||
*/
|
||||
editEnd (e) {
|
||||
const i = e.target.dataset.i
|
||||
// 更新到视图
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 0
|
||||
})
|
||||
this.root.setData({
|
||||
['nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].text']: e.detail.value.replace(/ {2}/g, '\xa0 ')
|
||||
})
|
||||
if (e.detail.cursor !== undefined) {
|
||||
this.cursor = e.detail.cursor
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 插入一个标签
|
||||
* @param {Object} node 要插入的标签
|
||||
*/
|
||||
insert (node) {
|
||||
setTimeout(() => {
|
||||
const arr = this.i.split('_')
|
||||
const i = parseInt(arr.pop())
|
||||
let path = arr.join('_')
|
||||
const children = path ? this.getNode(path).children : this.properties.childs
|
||||
const childs = children.slice(0)
|
||||
if (!childs[i]) {
|
||||
childs.push(node)
|
||||
} else if (childs[i].text) {
|
||||
// 在文本中插入
|
||||
const text = childs[i].text
|
||||
if (node.type === 'text') {
|
||||
if (this.cursor) {
|
||||
childs[i].text = text.substring(0, this.cursor) + node.text + text.substring(this.cursor)
|
||||
} else {
|
||||
childs[i].text += node.text
|
||||
}
|
||||
} else {
|
||||
const list = []
|
||||
if (this.cursor) {
|
||||
list.push({
|
||||
type: 'text',
|
||||
text: text.substring(0, this.cursor)
|
||||
})
|
||||
}
|
||||
list.push(node)
|
||||
if (this.cursor < text.length) {
|
||||
list.push({
|
||||
type: 'text',
|
||||
text: text.substring(this.cursor)
|
||||
})
|
||||
}
|
||||
childs.splice(i, 1, ...list)
|
||||
}
|
||||
} else {
|
||||
childs.splice(i + 1, 0, node)
|
||||
}
|
||||
path = this.properties.opts[7] + path
|
||||
if (path[path.length - 1] === '_') {
|
||||
path = path.slice(0, -1)
|
||||
}
|
||||
this.root._editVal('nodes' + (path ? '[' + path.replace(/_/g, '].children[') + '].children' : ''), children, childs, true)
|
||||
this.i = arr.join('_') + '_' + (i + 1)
|
||||
}, 200)
|
||||
},
|
||||
/**
|
||||
* @description 移除第 i 个标签
|
||||
* @param {Number} i
|
||||
*/
|
||||
remove (i) {
|
||||
const arr = i.split('_')
|
||||
const j = arr.pop()
|
||||
let path = arr.join('_')
|
||||
const children = path ? this.getNode(path).children : this.properties.childs
|
||||
const childs = children.slice(0)
|
||||
const delEle = childs.splice(j, 1)[0]
|
||||
if (delEle.name === 'img' || delEle.name === 'video' || delEle.name === 'audio') {
|
||||
let src = delEle.attrs.src
|
||||
if (delEle.src) {
|
||||
src = delEle.src.length === 1 ? delEle.src[0] : delEle.src
|
||||
}
|
||||
this.root.triggerEvent('remove', {
|
||||
type: delEle.name,
|
||||
src
|
||||
})
|
||||
}
|
||||
this.root._edit = undefined
|
||||
this.root._maskTap()
|
||||
path = this.properties.opts[7] + path
|
||||
if (path[path.length - 1] === '_') {
|
||||
path = path.slice(0, -1)
|
||||
}
|
||||
this.root._editVal('nodes' + (path ? '[' + path.replace(/_/g, '].children[') + '].children' : ''), children, childs, true)
|
||||
},
|
||||
/**
|
||||
* @description 标签被点击
|
||||
* @param {Event} e
|
||||
*/
|
||||
nodeTap (e) {
|
||||
if (this.properties.opts[5]) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
if (this.root._table) {
|
||||
const node = this.getNode(i)
|
||||
if (node.name === 'table') {
|
||||
this.root._table = undefined
|
||||
this.root._remove_table = () => {
|
||||
this.remove(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.root._lock) return
|
||||
// 阻止上层出现点击态
|
||||
this.root._lock = true
|
||||
setTimeout(() => {
|
||||
this.root._lock = false
|
||||
}, 50)
|
||||
const node = this.getNode(i)
|
||||
if (node.name === 'td' || node.name === 'th') {
|
||||
this.root._table = true
|
||||
}
|
||||
if (this.data.ctrl['e' + this.i] === 3) return
|
||||
this.root._maskTap()
|
||||
this.root._edit = this
|
||||
if (this.properties.opts[5] === 'simple') return
|
||||
const arr = i.split('_')
|
||||
const j = parseInt(arr.pop())
|
||||
let path = arr.join('_')
|
||||
const siblings = path ? this.getNode(path).children : this.properties.childs
|
||||
// 显示实线框
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 1
|
||||
})
|
||||
this.root._mask.push(() => {
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 0
|
||||
})
|
||||
})
|
||||
if (node.children.length === 1 && node.children[0].type === 'text') {
|
||||
const ii = i + '_0'
|
||||
if (!this.data.ctrl['e' + ii]) {
|
||||
this.setData({
|
||||
['ctrl.e' + ii]: 1
|
||||
})
|
||||
this.root._mask.push(() => {
|
||||
this.setData({
|
||||
['ctrl.e' + ii]: 0
|
||||
})
|
||||
})
|
||||
this.cursor = node.children[0].text.length
|
||||
}
|
||||
this.i = ii
|
||||
} else if (!(this.i || '').includes(i)) {
|
||||
this.i = i + '_'
|
||||
}
|
||||
const items = this.root._getItem(node, j !== 0, j !== siblings.length - 1)
|
||||
this.root._tooltip({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
if (items[tapIndex] === '大小') {
|
||||
// 改变字体大小
|
||||
const style = node.attrs.style || ''
|
||||
let value = style.match(/;font-size:([0-9]+)px/)
|
||||
if (value) {
|
||||
value = parseInt(value[1])
|
||||
} else {
|
||||
value = 16
|
||||
}
|
||||
this.root._slider({
|
||||
min: 10,
|
||||
max: 30,
|
||||
value,
|
||||
top: getTop(e),
|
||||
changing: val => {
|
||||
if (Math.abs(val - value) > 2) {
|
||||
// 字号变换超过 2 时更新到视图
|
||||
this.changeStyle('font-size', i, val + 'px', value + 'px')
|
||||
value = e.detail.value
|
||||
}
|
||||
},
|
||||
change: val => {
|
||||
if (val !== value) {
|
||||
this.changeStyle('font-size', i, val + 'px', value + 'px')
|
||||
}
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.style', style, this.getNode(i).attrs.style)
|
||||
}
|
||||
})
|
||||
} else if (items[tapIndex] === '颜色') {
|
||||
// 改变文字颜色
|
||||
const items = this.root._getItem('color')
|
||||
this.root._color({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
const style = node.attrs.style || ''
|
||||
const value = style.match(/;color:([^;]+)/)
|
||||
this.changeStyle('color', i, items[tapIndex], value ? value[1] : undefined)
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.style', style, this.getNode(i).attrs.style)
|
||||
}
|
||||
})
|
||||
} else if (items[tapIndex] === '上移' || items[tapIndex] === '下移') {
|
||||
const arr = siblings.slice(0)
|
||||
const item = arr[j]
|
||||
if (items[tapIndex] === '上移') {
|
||||
arr[j] = arr[j - 1]
|
||||
arr[j - 1] = item
|
||||
} else {
|
||||
arr[j] = arr[j + 1]
|
||||
arr[j + 1] = item
|
||||
}
|
||||
path = this.properties.opts[7] + path
|
||||
if (path[path.length - 1] === '_') {
|
||||
path = path.slice(0, -1)
|
||||
}
|
||||
this.root._editVal('nodes' + (path ? '[' + path.replace(/_/g, '].children[') + '].children' : ''), siblings, arr, true)
|
||||
} else if (items[tapIndex] === '删除') {
|
||||
if ((node.name === 'td' || node.name === 'th') && this.root._remove_table) {
|
||||
this.root._remove_table()
|
||||
this.root._remove_table = undefined
|
||||
} else {
|
||||
this.remove(i)
|
||||
}
|
||||
} else {
|
||||
const style = node.attrs.style || ''
|
||||
let newStyle = ''
|
||||
const item = items[tapIndex]
|
||||
let name
|
||||
let value
|
||||
if (item === '斜体') {
|
||||
name = 'font-style'
|
||||
value = 'italic'
|
||||
} else if (item === '粗体') {
|
||||
name = 'font-weight'
|
||||
value = 'bold'
|
||||
} else if (item === '下划线') {
|
||||
name = 'text-decoration'
|
||||
value = 'underline'
|
||||
} else if (item === '居中') {
|
||||
name = 'text-align'
|
||||
value = 'center'
|
||||
} else if (item === '缩进') {
|
||||
name = 'text-indent'
|
||||
value = '2em'
|
||||
}
|
||||
if (style.includes(name + ':')) {
|
||||
// 已有则取消
|
||||
newStyle = style.replace(new RegExp(name + ':[^;]+'), '')
|
||||
} else {
|
||||
// 没有则添加
|
||||
newStyle = style + ';' + name + ':' + value
|
||||
}
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.style', style, newStyle, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 音视频被点击
|
||||
* @param {Event} e
|
||||
*/
|
||||
mediaTap (e) {
|
||||
if (this.properties.opts[5]) {
|
||||
const i = e.target.dataset.i
|
||||
const node = this.getNode(i)
|
||||
const items = this.root._getItem(node)
|
||||
this.root._maskTap()
|
||||
this.root._edit = this
|
||||
this.i = i
|
||||
this.root._tooltip({
|
||||
top: e.target.offsetTop - 30,
|
||||
items,
|
||||
success: tapIndex => {
|
||||
switch (items[tapIndex]) {
|
||||
case '封面':
|
||||
// 设置封面
|
||||
this.root.getSrc('img', node.attrs.poster || '').then(url => {
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.poster', node.attrs.poster, url instanceof Array ? url[0] : url, true)
|
||||
}).catch(() => { })
|
||||
break
|
||||
case '删除':
|
||||
this.remove(i)
|
||||
break
|
||||
case '循环':
|
||||
case '不循环':
|
||||
// 切换循环播放
|
||||
this.root.setData({
|
||||
['nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.loop']: !node.attrs.loop
|
||||
})
|
||||
wx.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
break
|
||||
case '自动播放':
|
||||
case '不自动播放':
|
||||
// 切换自动播放播放
|
||||
this.root.setData({
|
||||
['nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.autoplay']: !node.attrs.autoplay
|
||||
})
|
||||
wx.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
// 避免上层出现点击态
|
||||
this.root._lock = true
|
||||
setTimeout(() => {
|
||||
this.root._lock = false
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 改变样式
|
||||
* @param {String} name 属性名
|
||||
* @param {Number} i 第几个标签
|
||||
* @param {String} value 新值
|
||||
* @param {String} oldVal 旧值
|
||||
*/
|
||||
changeStyle (name, i, value, oldVal) {
|
||||
let style = this.getNode(i).attrs.style || ''
|
||||
if (style.includes(';' + name + ':' + oldVal)) {
|
||||
// style 中已经有
|
||||
style = style.replace(';' + name + ':' + oldVal, ';' + name + ':' + value)
|
||||
} else {
|
||||
// 没有则新增
|
||||
style += ';' + name + ':' + value
|
||||
}
|
||||
this.root.setData({
|
||||
['nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.style']: style
|
||||
})
|
||||
}
|
||||
},
|
||||
handler (file) {
|
||||
if (file.isBuffer()) {
|
||||
let content = file.contents.toString()
|
||||
if (file.path.includes('miniprogram' + path.sep + 'index.wxml')) {
|
||||
// 传递 editable 属性和路径
|
||||
content = content.replace(/opts\s*=\s*"{{\[([^\]]+)\]}}"/, 'opts="{{[$1,editable,placeholder,\'\']}}"')
|
||||
.replace(/<view(.*?)style\s*=\s*"{{containerStyle}}"/, '<view$1style="{{editable?\'min-height:200px;\':\'\'}}{{containerStyle}}" bindtap="_containTap"')
|
||||
// 工具弹窗
|
||||
.replace('</view>', ` <view wx:if="{{tooltip}}" class="_tooltip_contain" style="top:{{tooltip.top}}px">
|
||||
<view class="_tooltip">
|
||||
<view wx:for="{{tooltip.items}}" wx:key="index" class="_tooltip_item" data-i="{{index}}" bindtap="_tooltipTap">{{item}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{slider}}" class="_slider" style="top:{{slider.top}}px">
|
||||
<slider value="{{slider.value}}" min="{{slider.min}}" max="{{slider.max}}" block-size="14" show-value activeColor="white" mp-alipay:style="padding:10px" bindchanging="_sliderChanging" bindchange="_sliderChange" />
|
||||
</view>
|
||||
<view wx:if="{{color}}" class="_tooltip_contain" style="top:{{color.top}}px">
|
||||
<view class="_tooltip" style="overflow-y: hidden;">
|
||||
<view wx:for="{{color.items}}" wx:key="index" class="_color_item" style="background-color:{{item}}" data-i="{{index}}" bindtap="_colorTap"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>`)
|
||||
} else if (file.path.includes('miniprogram' + path.sep + 'index.js')) {
|
||||
// 添加 editable 属性,发生变化时重新解析
|
||||
content = content.replace(/properties\s*:\s*{/, `properties: {
|
||||
editable: {
|
||||
type: null,
|
||||
observer (val) {
|
||||
if (this.properties.content) {
|
||||
this.setContent(val ? this.properties.content : this.getContent())
|
||||
} else if (val) {
|
||||
this.setData({
|
||||
nodes: [{
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
}]
|
||||
})
|
||||
// #ifdef MP-TOUTIAO
|
||||
this.selectComponent('#_root', child => {
|
||||
child.root = this
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
if (!val) {
|
||||
this._maskTap()
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder: String,`)
|
||||
.replace(/didUpdate\s*\(e\)\s*{/, `didUpdate (e) {
|
||||
if (e.editable !== this.properties.editable) {
|
||||
const val = this.properties.editable
|
||||
if (this.properties.content) {
|
||||
this.setContent(val ? this.properties.content : this.getContent())
|
||||
} else if (val) {
|
||||
this.setData({
|
||||
nodes: [{
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
}]
|
||||
})
|
||||
}
|
||||
if (!val) {
|
||||
this._maskTap()
|
||||
}
|
||||
}`)
|
||||
// 处理各类弹窗的事件
|
||||
.replace(/methods\s*:\s*{/, `methods: {
|
||||
_containTap() {
|
||||
if (!this._lock && !this.data.slider && !this.data.color) {
|
||||
this._edit = undefined
|
||||
this._maskTap()
|
||||
}
|
||||
},
|
||||
_tooltipTap(e) {
|
||||
this._tooltipcb(e.currentTarget.dataset.i)
|
||||
this.setData({
|
||||
tooltip: null
|
||||
})
|
||||
},
|
||||
_sliderChanging(e) {
|
||||
this._slideringcb(e.detail.value)
|
||||
},
|
||||
_sliderChange(e) {
|
||||
this._slidercb(e.detail.value)
|
||||
},
|
||||
_colorTap(e) {
|
||||
this._colorcb(e.currentTarget.dataset.i)
|
||||
this.setData({
|
||||
color: null
|
||||
})
|
||||
},`)
|
||||
} else if (file.path.includes('miniprogram' + path.sep + 'index.wxss')) {
|
||||
// 工具弹窗的样式
|
||||
content += `/* 提示条 */
|
||||
._tooltip_contain {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
left: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
._tooltip {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
height: 30px;
|
||||
padding: 0 3px;
|
||||
overflow: scroll;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
._tooltip_item {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
padding: 0 2vw;
|
||||
line-height: 30px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
._color_item {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 5px 2vw;
|
||||
border:1px solid #dfe2e5;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 图片宽度滚动条 */
|
||||
._slider {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
._tooltip,
|
||||
._slider {
|
||||
background-color: black;
|
||||
border-radius: 3px;
|
||||
opacity: 0.75;
|
||||
}`
|
||||
} else if (file.path.includes('parser.js')) {
|
||||
content = content.replace(/popNode\s*=\s*function\s*\(\)\s*{/, 'popNode = function () {\n const editable = this.options.editable')
|
||||
// 不转换标签名
|
||||
.replace(/if\s*\(config.blockTags\[node.name\]\)\s*{[\s\S]+?}/, `if (config.blockTags[node.name]) {
|
||||
if (!editable) {
|
||||
node.name = 'div'
|
||||
}
|
||||
}`)
|
||||
// 转换表格和列表
|
||||
.replace(/node.c(\)|\s*&&|\s*\n)/g, '(node.c || editable)$1')
|
||||
.replace(/while\s*\(map\[row\s*\+\s*'.'\s*\+\s*col\]\)\s*{[\s\S]+?}/, `while (map[row + '.' + col]) {
|
||||
col++
|
||||
}
|
||||
if (editable) {
|
||||
td.r = row
|
||||
}`)
|
||||
// 不做 expose 处理
|
||||
.replace(/parser.prototype.expose\s*=\s*function\s*\(\)\s*{/, `parser.prototype.expose = function () {
|
||||
if (this.options.editable) return`)
|
||||
} else if (file.path.includes('node.wxml')) {
|
||||
content = content.replace(/opts\s*=\s*"{{opts}}"/, 'opts="{{[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+i+\'_\']}}"')
|
||||
.replace(/opts\s*=\s*"{{opts}}"/, 'opts="{{[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+i1+\'_\'+i2+\'_\'+i3+\'_\'+i4+\'_\'+i5+\'_\']}}"')
|
||||
.replace('!n.c', "opts[5]?(!n.children||n.name=='a'):!n.c")
|
||||
.replace(/!(n.?)\.c(?![a-z])/g, '(opts[5]?true:!$1.c)')
|
||||
.replace(/isInline\((.*?)\)/g, '(opts[5]?true:isInline($1))')
|
||||
// 修改普通标签
|
||||
.replace(/<view\s*wx:else\s*id(.+?)style="/, '<view wx:else data-i="{{path+i}}" bindtap="nodeTap" id$1style="{{ctrl[\'e\'+path+i]&&opts[5]!==\'simple\'?\'border:1px solid black;padding:5px;display:block;\':\'\'}}')
|
||||
.replace(/<view\s*wx:else\s*id(.+?)style="/, '<view wx:else data-i="{{\'\'+i1}}" bindtap="nodeTap" id$1style="{{ctrl[\'e\'+i1]&&opts[5]!==\'simple\'?\'border:1px solid black;padding:5px;display:block;\':\'\'}}')
|
||||
.replace(/<view\s*wx:else\s*id(.+?)style="/, '<view wx:else data-i="{{i1+\'_\'+i2}}" bindtap="nodeTap" id$1style="{{ctrl[\'e\'+i1+\'_\'+i2]&&opts[5]!==\'simple\'?\'border:1px solid black;padding:5px;display:block;\':\'\'}}')
|
||||
.replace(/<view\s*wx:else\s*id(.+?)style="/, '<view wx:else data-i="{{i1+\'_\'+i2+\'_\'+i3}}" bindtap="nodeTap" id$1style="{{ctrl[\'e\'+i1+\'_\'+i2+\'_\'+i3]&&opts[5]!==\'simple\'?\'border:1px solid black;padding:5px;display:block;\':\'\'}}')
|
||||
.replace(/<view\s*wx:else\s*id(.+?)style="/, '<view wx:else data-i="{{i1+\'_\'+i2+\'_\'+i3+\'_\'+i4}}" bindtap="nodeTap" id$1style="{{ctrl[\'e\'+i1+\'_\'+i2+\'_\'+i3+\'_\'+i4]&&opts[5]!==\'simple\'?\'border:1px solid black;padding:5px;display:block;\':\'\'}}')
|
||||
// 修改文本块
|
||||
.replace(/<!--\s*文本\s*-->[\s\S]+?<!--\s*链接\s*-->/,
|
||||
`<block wx:elif="{{n.type==='text'}}">
|
||||
<text wx:if="{{!ctrl['e'+i]}}" data-i="{{i}}" mp-weixin:user-select="{{opts[4]}}" decode="{{!opts[5]}}" bindtap="editStart">{{n.text}}
|
||||
<text wx:if="{{!n.text}}" style="color:gray">{{opts[6]||'请输入'}}</text>
|
||||
</text>
|
||||
<text wx:elif="{{ctrl['e'+i]===1}}" data-i="{{i}}" style="border:1px dashed black;min-width:50px;width:auto;padding:5px;display:block" catchtap="editStart">{{n.text}}
|
||||
<text wx:if="{{!n.text}}" style="color:gray">{{opts[6]||'请输入'}}</text>
|
||||
</text>
|
||||
<textarea wx:else style="{{opts[5]==='simple'?'':'border:1px dashed black;'}}min-width:50px;width:auto;padding:5px" auto-height maxlength="-1" focus="{{ctrl['e'+i]===3}}" value="{{n.text}}" data-i="{{i}}" bindinput="editInput" bindblur="editEnd" />
|
||||
</block>
|
||||
<text wx:elif="{{n.name==='br'}}">\\n</text>`)
|
||||
// 修改图片
|
||||
.replace(/<image(.+?)id="\{\{n.attrs.id/, '<image$1id="{{n.attrs.id||(\'n\'+i)')
|
||||
.replace('height:1px', "height:{{ctrl['h'+i]||1}}px")
|
||||
.replace('style="{{ctrl[i]', 'style="{{ctrl[\'e\'+i]&&opts[5]!==\'simple\'?\'border:1px dashed black;padding:3px;\':\'\'}}{{ctrl[i]')
|
||||
.replace(/weixin:show-menu-by-longpress\s*=\s*"{{(\S+?)}}"\s*baidu:image-menu-prevent\s*=\s*"{{(\S+?)}}"/, 'weixin:show-menu-by-longpress="{{!opts[5]&&$1}}" baidu:image-menu-prevent="{{opts[5]||$2}}"')
|
||||
// 修改音视频
|
||||
.replace('<video', '<video bindtap="mediaTap"')
|
||||
.replace('audio ', 'audio bindtap="mediaTap" ')
|
||||
.replace('card', 'card bindtap="mediaTap"')
|
||||
} else if (file.path.includes('node.js') && file.extname === '.js') {
|
||||
content = `
|
||||
const Parser = require('../parser')
|
||||
function getTop(e) {
|
||||
let top
|
||||
// #ifndef MP-ALIPAY
|
||||
top = e.detail.y
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
top = top = e.detail.pageY
|
||||
// #endif
|
||||
if (top - e.currentTarget.offsetTop < 150 || top < 600) {
|
||||
top = e.currentTarget.offsetTop
|
||||
}
|
||||
if (top < 30) {
|
||||
top += 70
|
||||
}
|
||||
return top - 30
|
||||
}` + content.replace('methods:', `detached () {
|
||||
if (this.root && this.root._edit === this) {
|
||||
this.root._edit = undefined
|
||||
}
|
||||
},
|
||||
methods:`)
|
||||
// 记录图片宽度
|
||||
.replace(/imgLoad\s*\(e\)\s*{/, `imgLoad (e) {
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
if (this.properties.opts[5]) {
|
||||
setTimeout(() => {
|
||||
const id = this.getNode(i).attrs.id || ('n' + i)
|
||||
wx.createSelectorQuery().in(this).select('#' + id).boundingClientRect().exec(res => {
|
||||
this.setData({
|
||||
['ctrl.h'+i]: res[0].height
|
||||
})
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
// #endif`)
|
||||
.replace(/if\s*\(!node.w\)\s*{[\s\S]+?}/,
|
||||
`if (!node.w) {
|
||||
val = e.detail.width
|
||||
if (this.properties.opts[5]) {
|
||||
const data = {}
|
||||
const path = 'nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.'
|
||||
if (val < 150) {
|
||||
data[path + 'ignore'] = 'T'
|
||||
}
|
||||
data[path + 'width'] = val.toString()
|
||||
this.root.setData(data)
|
||||
}
|
||||
}`)
|
||||
// 处理图片点击
|
||||
.replace(/imgTap\s*\(e\)\s*{([\s\S]+?)},\s*\/\*/,
|
||||
`imgTap (e) {
|
||||
if (!this.properties.opts[5]) {$1} else {
|
||||
const i = e.target.dataset.i
|
||||
const node = this.getNode(i)
|
||||
const items = this.root._getItem(node)
|
||||
this.root._edit = this
|
||||
const parser = new Parser(this.root)
|
||||
this.i = i
|
||||
this.root._maskTap()
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 1
|
||||
})
|
||||
this.root._mask.push(() => {
|
||||
this.setData({
|
||||
['ctrl.e' + i]: 0
|
||||
})
|
||||
})
|
||||
this.root._tooltip({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
if (items[tapIndex] === '换图') {
|
||||
// 换图
|
||||
this.root.getSrc('img', node.attrs.src || '').then(url => {
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.src', node.attrs.src, parser.getUrl(url instanceof Array ? url[0] : url), true)
|
||||
}).catch(() => { })
|
||||
} else if (items[tapIndex] === '宽度') {
|
||||
// 更改宽度
|
||||
const style = node.attrs.style || ''
|
||||
let value = style.match(/max-width:([0-9]+)%/)
|
||||
if (value) {
|
||||
value = parseInt(value[1])
|
||||
} else {
|
||||
value = 100
|
||||
}
|
||||
this.root._slider({
|
||||
min: 0,
|
||||
max: 100,
|
||||
value,
|
||||
top: getTop(e),
|
||||
changing: val => {
|
||||
// 变化超过 5% 更新时视图
|
||||
if (Math.abs(val - value) > 5) {
|
||||
this.changeStyle('max-width', i, val + '%', value + '%')
|
||||
value = val
|
||||
}
|
||||
},
|
||||
change: val => {
|
||||
if (val !== value) {
|
||||
this.changeStyle('max-width', i, val + '%', value + '%')
|
||||
value = val
|
||||
}
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.style', style, this.getNode(i).attrs.style)
|
||||
}
|
||||
})
|
||||
} else if (items[tapIndex] === '超链接') {
|
||||
// 将图片设置为链接
|
||||
this.root.getSrc('link', node.a ? node.a.href : '').then(url => {
|
||||
// 如果有 a 标签则替换 href
|
||||
if (node.a) {
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].a.href', node.a.href, parser.getUrl(url), true)
|
||||
} else {
|
||||
const link = {
|
||||
name: 'a',
|
||||
attrs: {
|
||||
href: parser.getUrl(url)
|
||||
},
|
||||
children: [node]
|
||||
}
|
||||
node.a = link.attrs
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + ']', node, link, true)
|
||||
}
|
||||
wx.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}).catch(() => { })
|
||||
} else if (items[tapIndex] === '预览图') {
|
||||
// 设置预览图链接
|
||||
this.root.getSrc('img', node.attrs['original-src'] || '').then(url => {
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.original-src', node.attrs['original-src'], parser.getUrl(url instanceof Array ? url[0] : url), true)
|
||||
wx.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}).catch(() => { })
|
||||
} else if (items[tapIndex] === '删除') {
|
||||
this.remove(i)
|
||||
} else {
|
||||
// 禁用 / 启用预览
|
||||
this.root.setData({
|
||||
['nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.ignore']: !node.attrs.ignore
|
||||
})
|
||||
wx.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
this.root._lock = true
|
||||
setTimeout(() => {
|
||||
this.root._lock = false
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
/*`)
|
||||
// 处理链接点击
|
||||
.replace(/linkTap\s*\(e\)\s*{([\s\S]+?)},\s*\/\*/,
|
||||
`linkTap (e) {
|
||||
if (!this.properties.opts[5]) {$1} else {
|
||||
const i = e.currentTarget.dataset.i
|
||||
const node = this.getNode(i)
|
||||
const items = this.root._getItem(node)
|
||||
this.root._tooltip({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
if (items[tapIndex] === '更换链接') {
|
||||
this.root.getSrc('link', node.attrs.href).then(url => {
|
||||
this.root._editVal('nodes[' + (this.properties.opts[7] + i).replace(/_/g, '].children[') + '].attrs.href', node.attrs.href, url, true)
|
||||
wx.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}).catch(() => { })
|
||||
} else {
|
||||
this.remove(i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
/*`)
|
||||
}
|
||||
file.contents = Buffer.from(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
+551
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* @fileoverview editable 插件
|
||||
*/
|
||||
const config = require('./config')
|
||||
const Parser = require('../parser')
|
||||
|
||||
function Editable (vm) {
|
||||
this.vm = vm
|
||||
this.editHistory = [] // 历史记录
|
||||
this.editI = -1 // 历史记录指针
|
||||
vm._mask = [] // 蒙版被点击时进行的操作
|
||||
|
||||
/**
|
||||
* @description 移动历史记录指针
|
||||
* @param {Number} num 移动距离
|
||||
*/
|
||||
const move = num => {
|
||||
const item = this.editHistory[this.editI + num]
|
||||
if (item) {
|
||||
this.editI += num
|
||||
vm.setData({
|
||||
[item.key]: item.value
|
||||
})
|
||||
}
|
||||
}
|
||||
vm.undo = () => move(-1) // 撤销
|
||||
vm.redo = () => move(1) // 重做
|
||||
|
||||
/**
|
||||
* @description 更新记录
|
||||
* @param {String} path 路径
|
||||
* @param {*} oldVal 旧值
|
||||
* @param {*} newVal 新值
|
||||
* @param {Boolean} set 是否更新到视图
|
||||
* @private
|
||||
*/
|
||||
vm._editVal = (path, oldVal, newVal, set) => {
|
||||
// 当前指针后的内容去除
|
||||
while (this.editI < this.editHistory.length - 1) {
|
||||
this.editHistory.pop()
|
||||
}
|
||||
|
||||
// 最多存储 30 条操作记录
|
||||
while (this.editHistory.length > 30) {
|
||||
this.editHistory.pop()
|
||||
this.editI--
|
||||
}
|
||||
|
||||
const last = this.editHistory[this.editHistory.length - 1]
|
||||
if (!last || last.key !== path) {
|
||||
if (last) {
|
||||
// 去掉上一次的新值
|
||||
this.editHistory.pop()
|
||||
this.editI--
|
||||
}
|
||||
// 存入这一次的旧值
|
||||
this.editHistory.push({
|
||||
key: path,
|
||||
value: oldVal
|
||||
})
|
||||
this.editI++
|
||||
}
|
||||
|
||||
// 存入本次的新值
|
||||
this.editHistory.push({
|
||||
key: path,
|
||||
value: newVal
|
||||
})
|
||||
this.editI++
|
||||
|
||||
// 更新到视图
|
||||
if (set) {
|
||||
vm.setData({
|
||||
[path]: newVal
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取菜单项
|
||||
* @private
|
||||
*/
|
||||
vm._getItem = function (node, up, down) {
|
||||
let items
|
||||
let i
|
||||
if (node === 'color') {
|
||||
return config.color
|
||||
}
|
||||
if (node.name === 'img') {
|
||||
items = config.img.slice(0)
|
||||
if (!vm.getSrc) {
|
||||
i = items.indexOf('换图')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
i = items.indexOf('超链接')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
i = items.indexOf('预览图')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
i = items.indexOf('禁用预览')
|
||||
if (i !== -1 && node.attrs.ignore) {
|
||||
items[i] = '启用预览'
|
||||
}
|
||||
} else if (node.name === 'a') {
|
||||
items = config.link.slice(0)
|
||||
if (!vm.getSrc) {
|
||||
i = items.indexOf('更换链接')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
} else if (node.name === 'video' || node.name === 'audio') {
|
||||
items = config.media.slice(0)
|
||||
i = items.indexOf('封面')
|
||||
if (!vm.getSrc && i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
i = items.indexOf('循环')
|
||||
if (node.attrs.loop && i !== -1) {
|
||||
items[i] = '不循环'
|
||||
}
|
||||
i = items.indexOf('自动播放')
|
||||
if (node.attrs.autoplay && i !== -1) {
|
||||
items[i] = '不自动播放'
|
||||
}
|
||||
} else if (node.name === 'card') {
|
||||
items = config.card.slice(0)
|
||||
} else {
|
||||
items = config.node.slice(0)
|
||||
}
|
||||
if (!up) {
|
||||
i = items.indexOf('上移')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
if (!down) {
|
||||
i = items.indexOf('下移')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 显示 tooltip
|
||||
* @param {object} obj
|
||||
* @private
|
||||
*/
|
||||
vm._tooltip = function (obj) {
|
||||
vm.setData({
|
||||
tooltip: {
|
||||
top: obj.top,
|
||||
items: obj.items
|
||||
}
|
||||
})
|
||||
vm._tooltipcb = obj.success
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 显示滚动条
|
||||
* @param {object} obj
|
||||
* @private
|
||||
*/
|
||||
vm._slider = function (obj) {
|
||||
vm.setData({
|
||||
slider: {
|
||||
min: obj.min,
|
||||
max: obj.max,
|
||||
value: obj.value,
|
||||
top: obj.top
|
||||
}
|
||||
})
|
||||
vm._slideringcb = obj.changing
|
||||
vm._slidercb = obj.change
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 显示颜色选择
|
||||
* @param {object} obj
|
||||
* @private
|
||||
*/
|
||||
vm._color = function (obj) {
|
||||
vm.setData({
|
||||
color: {
|
||||
items: obj.items,
|
||||
top: obj.top
|
||||
}
|
||||
})
|
||||
vm._colorcb = obj.success
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 点击蒙版
|
||||
* @private
|
||||
*/
|
||||
vm._maskTap = function () {
|
||||
// 隐藏所有悬浮窗
|
||||
while (this._mask.length) {
|
||||
(this._mask.pop())()
|
||||
}
|
||||
const data = {}
|
||||
if (this.data.tooltip) {
|
||||
data.tooltip = null
|
||||
}
|
||||
if (this.data.slider) {
|
||||
data.slider = null
|
||||
}
|
||||
if (this.data.color) {
|
||||
data.color = null
|
||||
}
|
||||
if (this.data.tooltip || this.data.slider || this.data.color) {
|
||||
this.setData(data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 插入节点
|
||||
* @param {Object} node
|
||||
*/
|
||||
function insert (node) {
|
||||
if (vm._edit) {
|
||||
vm._edit.insert(node)
|
||||
} else {
|
||||
const nodes = vm.data.nodes.slice(0)
|
||||
nodes.push(node)
|
||||
vm._editVal('nodes', vm.data.nodes, nodes, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入指定 html 内容
|
||||
* @param {String} html 内容
|
||||
*/
|
||||
vm.insertHtml = html => {
|
||||
this.inserting = true
|
||||
const arr = new Parser(vm).parse(html)
|
||||
this.inserting = undefined
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
insert(arr[i])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入图片
|
||||
*/
|
||||
vm.insertImg = function () {
|
||||
vm.getSrc && vm.getSrc('img').then(src => {
|
||||
if (typeof src === 'string') {
|
||||
src = [src]
|
||||
}
|
||||
const parser = new Parser(vm)
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
insert({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: parser.getUrl(src[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个链接
|
||||
*/
|
||||
vm.insertLink = function () {
|
||||
vm.getSrc && vm.getSrc('link').then(url => {
|
||||
insert({
|
||||
name: 'a',
|
||||
attrs: {
|
||||
href: url
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: url
|
||||
}]
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个表格
|
||||
* @param {Number} rows 行数
|
||||
* @param {Number} cols 列数
|
||||
*/
|
||||
vm.insertTable = function (rows, cols) {
|
||||
const table = {
|
||||
name: 'table',
|
||||
attrs: {
|
||||
style: 'display:table;width:100%;margin:10px 0;text-align:center;border-spacing:0;border-collapse:collapse;border:1px solid gray'
|
||||
},
|
||||
children: []
|
||||
}
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const tr = {
|
||||
name: 'tr',
|
||||
attrs: {},
|
||||
children: []
|
||||
}
|
||||
for (let j = 0; j < cols; j++) {
|
||||
tr.children.push({
|
||||
name: 'td',
|
||||
attrs: {
|
||||
style: 'padding:2px;border:1px solid gray'
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
})
|
||||
}
|
||||
table.children.push(tr)
|
||||
}
|
||||
insert(table)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 插入视频/音频
|
||||
* @param {Object} node
|
||||
*/
|
||||
function insertMedia (node) {
|
||||
if (typeof node.src === 'string') {
|
||||
node.src = [node.src]
|
||||
}
|
||||
const parser = new Parser(vm)
|
||||
// 拼接主域名
|
||||
for (let i = 0; i < node.src.length; i++) {
|
||||
node.src[i] = parser.getUrl(node.src[i])
|
||||
}
|
||||
insert({
|
||||
name: 'div',
|
||||
attrs: {
|
||||
style: 'text-align:center'
|
||||
},
|
||||
children: [node]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个视频
|
||||
*/
|
||||
vm.insertVideo = function () {
|
||||
vm.getSrc && vm.getSrc('video').then(src => {
|
||||
insertMedia({
|
||||
name: 'video',
|
||||
attrs: {
|
||||
controls: 'T'
|
||||
},
|
||||
src
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个音频
|
||||
*/
|
||||
vm.insertAudio = function () {
|
||||
vm.getSrc && vm.getSrc('audio').then(attrs => {
|
||||
let src
|
||||
if (attrs.src) {
|
||||
src = attrs.src
|
||||
attrs.src = undefined
|
||||
} else {
|
||||
src = attrs
|
||||
attrs = {}
|
||||
}
|
||||
attrs.controls = 'T'
|
||||
insertMedia({
|
||||
name: 'audio',
|
||||
attrs,
|
||||
src
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一段文本
|
||||
*/
|
||||
vm.insertText = function () {
|
||||
insert({
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 清空内容
|
||||
*/
|
||||
vm.clear = function () {
|
||||
vm._maskTap()
|
||||
vm._edit = undefined
|
||||
vm.setData({
|
||||
nodes: [{
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取编辑后的 html
|
||||
*/
|
||||
vm.getContent = function () {
|
||||
let html = '';
|
||||
// 递归遍历获取
|
||||
(function traversal (nodes, table) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let item = nodes[i]
|
||||
if (item.type === 'text') {
|
||||
// 编码实体
|
||||
html += item.text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>').replace(/\xa0/g, ' ')
|
||||
} else {
|
||||
// 还原被转换的 svg
|
||||
if (item.name === 'img' && (item.attrs.src || '').includes('data:image/svg+xml;utf8,')) {
|
||||
html += item.attrs.src.substr(24).replace(/%23/g, '#').replace('<svg', '<svg style="' + (item.attrs.style || '') + '"')
|
||||
continue
|
||||
} else if (item.name === 'video' || item.name === 'audio') {
|
||||
// 还原 video 和 audio 的 source
|
||||
if (item.src.length > 1) {
|
||||
item.children = []
|
||||
for (let j = 0; j < item.src.length; j++) {
|
||||
item.children.push({
|
||||
name: 'source',
|
||||
attrs: {
|
||||
src: item.src[j]
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
item.attrs.src = item.src[0]
|
||||
}
|
||||
} else if (item.name === 'div' && (item.attrs.style || '').includes('overflow:auto') && (item.children[0] || {}).name === 'table') {
|
||||
// 还原滚动层
|
||||
item = item.children[0]
|
||||
}
|
||||
// 还原 table
|
||||
if (item.name === 'table') {
|
||||
table = item.attrs
|
||||
if ((item.attrs.style || '').includes('display:grid')) {
|
||||
item.attrs.style = item.attrs.style.split('display:grid')[0]
|
||||
const children = [{
|
||||
name: 'tr',
|
||||
attrs: {},
|
||||
children: []
|
||||
}]
|
||||
for (let j = 0; j < item.children.length; j++) {
|
||||
item.children[j].attrs.style = item.children[j].attrs.style.replace(/grid-[^;]+;*/g, '')
|
||||
if (item.children[j].r !== children.length) {
|
||||
children.push({
|
||||
name: 'tr',
|
||||
attrs: {},
|
||||
children: [item.children[j]]
|
||||
})
|
||||
} else {
|
||||
children[children.length - 1].children.push(item.children[j])
|
||||
}
|
||||
}
|
||||
item.children = children
|
||||
}
|
||||
}
|
||||
html += '<' + item.name
|
||||
for (const attr in item.attrs) {
|
||||
let val = item.attrs[attr]
|
||||
if (!val) continue
|
||||
// bool 型省略值
|
||||
if (val === 'T' || val === true) {
|
||||
html += ' ' + attr
|
||||
continue
|
||||
} else if (item.name[0] === 't' && attr === 'style' && table) {
|
||||
// 取消为了显示 table 添加的 style
|
||||
val = val.replace(/;*display:table[^;]*/, '')
|
||||
if (table.border) {
|
||||
val = val.replace(/border[^;]+;*/g, $ => $.includes('collapse') ? $ : '')
|
||||
}
|
||||
if (table.cellpadding) {
|
||||
val = val.replace(/padding[^;]+;*/g, '')
|
||||
}
|
||||
if (!val) continue
|
||||
}
|
||||
html += ' ' + attr + '="' + val.replace(/"/g, '"') + '"'
|
||||
}
|
||||
html += '>'
|
||||
if (item.children) {
|
||||
traversal(item.children, table)
|
||||
html += '</' + item.name + '>'
|
||||
}
|
||||
}
|
||||
}
|
||||
})(vm.data.nodes)
|
||||
|
||||
// 其他插件处理
|
||||
for (let i = vm.plugins.length; i--;) {
|
||||
if (vm.plugins[i].onGetContent) {
|
||||
html = vm.plugins[i].onGetContent(html) || html
|
||||
}
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
}
|
||||
|
||||
Editable.prototype.onUpdate = function (content, config) {
|
||||
if (this.vm.properties.editable) {
|
||||
this.vm._maskTap()
|
||||
config.entities.amp = '&'
|
||||
if (!this.inserting) {
|
||||
this.vm._edit = undefined
|
||||
if (!content) {
|
||||
setTimeout(() => {
|
||||
this.vm.setData({
|
||||
nodes: [{
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
}]
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Editable.prototype.onParse = function (node) {
|
||||
// 空白单元格可编辑
|
||||
if (this.vm.properties.editable && (node.name === 'td' || node.name === 'th') && !this.vm.getText(node.children)) {
|
||||
node.children.push({
|
||||
type: 'text',
|
||||
text: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Editable
|
||||
+744
@@ -0,0 +1,744 @@
|
||||
/* global getTop */
|
||||
module.exports = {
|
||||
style: `/* #ifndef H5 || MP-ALIPAY || APP-PLUS */
|
||||
._address,
|
||||
._article,
|
||||
._aside,
|
||||
._body,
|
||||
._caption,
|
||||
._center,
|
||||
._cite,
|
||||
._footer,
|
||||
._header,
|
||||
._html,
|
||||
._nav,
|
||||
._pre,
|
||||
._section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
._video {
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
display: inline-block;
|
||||
background-color: black;
|
||||
}`,
|
||||
methods: {
|
||||
/**
|
||||
* @description 开始编辑文本
|
||||
* @param {Event} e
|
||||
*/
|
||||
editStart (e) {
|
||||
if (this.opts[5]) {
|
||||
const i = e.currentTarget.dataset.i
|
||||
if (!this.ctrl['e' + i] && this.opts[5] !== 'simple') {
|
||||
// 显示虚线框
|
||||
this.$set(this.ctrl, 'e' + i, 1)
|
||||
setTimeout(() => {
|
||||
this.root._mask.push(() => this.$set(this.ctrl, 'e' + i, 0))
|
||||
}, 50)
|
||||
this.root._edit = this
|
||||
this.i = i
|
||||
this.cursor = this.childs[i].text.length
|
||||
} else {
|
||||
if (this.opts[5] === 'simple') {
|
||||
this.root._edit = this
|
||||
this.i = i
|
||||
this.cursor = this.childs[i].text.length
|
||||
}
|
||||
this.root._mask.pop()
|
||||
this.root._maskTap()
|
||||
// 将 text 转为 textarea
|
||||
this.$set(this.ctrl, 'e' + i, 2)
|
||||
// 延时对焦,避免高度错误
|
||||
setTimeout(() => {
|
||||
this.$set(this.ctrl, 'e' + i, 3)
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 输入文本
|
||||
* @param {Event} e
|
||||
*/
|
||||
editInput (e) {
|
||||
const i = e.target.dataset.i
|
||||
// 替换连续空格
|
||||
const value = e.detail.value.replace(/ {2,}/, $ => {
|
||||
let res = '\xa0'
|
||||
for (let i = 1; i < $.length; i++) {
|
||||
res += '\xa0'
|
||||
}
|
||||
return res
|
||||
})
|
||||
this.root._editVal(`${this.opts[7]}.${i}.text`, this.childs[i].text, value) // 记录编辑历史
|
||||
this.cursor = e.detail.cursor
|
||||
},
|
||||
/**
|
||||
* @description 完成编辑文本
|
||||
* @param {Event} e
|
||||
*/
|
||||
editEnd (e) {
|
||||
const i = e.target.dataset.i
|
||||
this.$set(this.ctrl, 'e' + i, 0)
|
||||
// 更新到视图
|
||||
this.root._setData(`${this.opts[7]}.${i}.text`, e.detail.value.replace(/ {2}/g, '\xa0 '))
|
||||
if (e.detail.cursor !== undefined) {
|
||||
this.cursor = e.detail.cursor
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 插入一个标签
|
||||
* @param {Object} node 要插入的标签
|
||||
*/
|
||||
insert (node) {
|
||||
setTimeout(() => {
|
||||
const childs = this.childs.slice(0)
|
||||
if (!childs[this.i]) {
|
||||
childs.push(node)
|
||||
} else if (childs[this.i].text) {
|
||||
// 在文本中插入
|
||||
const text = childs[this.i].text
|
||||
if (node.type === 'text') {
|
||||
if (this.cursor) {
|
||||
childs[this.i].text = text.substring(0, this.cursor) + node.text + text.substring(this.cursor)
|
||||
} else {
|
||||
childs[this.i].text += node.text
|
||||
}
|
||||
} else {
|
||||
const list = []
|
||||
if (this.cursor) {
|
||||
list.push({
|
||||
type: 'text',
|
||||
text: text.substring(0, this.cursor)
|
||||
})
|
||||
}
|
||||
list.push(node)
|
||||
if (this.cursor < text.length) {
|
||||
list.push({
|
||||
type: 'text',
|
||||
text: text.substring(this.cursor)
|
||||
})
|
||||
}
|
||||
childs.splice(this.i, 1, ...list)
|
||||
}
|
||||
} else {
|
||||
childs.splice(parseInt(this.i) + 1, 0, node)
|
||||
}
|
||||
this.root._editVal(this.opts[7], this.childs, childs, true)
|
||||
this.i = parseInt(this.i) + 1
|
||||
}, 200)
|
||||
},
|
||||
/**
|
||||
* @description 移除第 i 个标签
|
||||
* @param {Number} i
|
||||
*/
|
||||
remove (i) {
|
||||
const arr = this.childs.slice(0)
|
||||
const delEle = arr.splice(i, 1)[0]
|
||||
if (delEle.name === 'img' || delEle.name === 'video' || delEle.name === 'audio') {
|
||||
let src = delEle.attrs.src
|
||||
if (delEle.src) {
|
||||
src = delEle.src.length === 1 ? delEle.src[0] : delEle.src
|
||||
}
|
||||
this.root.$emit('remove', {
|
||||
type: delEle.name,
|
||||
src
|
||||
})
|
||||
}
|
||||
this.root._edit = undefined
|
||||
this.root._maskTap()
|
||||
this.root._editVal(this.opts[7], this.childs, arr, true)
|
||||
},
|
||||
/**
|
||||
* @description 标签被点击
|
||||
* @param {Event} e
|
||||
*/
|
||||
nodeTap (e) {
|
||||
if (this.opts[5]) {
|
||||
if (this.root._lock) return
|
||||
this.root._lock = true
|
||||
setTimeout(() => {
|
||||
this.root._lock = false
|
||||
}, 50)
|
||||
if (this.ctrl['e' + this.i] === 3) return
|
||||
this.root._maskTap()
|
||||
this.root._edit = this
|
||||
if (this.opts[5] === 'simple') return
|
||||
let start = this.opts[7].lastIndexOf('children.')
|
||||
if (start !== -1) {
|
||||
start += 9
|
||||
} else {
|
||||
start = 6
|
||||
}
|
||||
const i = parseInt(this.opts[7].substring(start, this.opts[7].lastIndexOf('.children')))
|
||||
let parent = this.$parent
|
||||
while (parent && parent.$options.name !== 'node') {
|
||||
parent = parent.$parent
|
||||
}
|
||||
let remove = () => {
|
||||
parent.remove(i)
|
||||
}
|
||||
if (this.opts[7].length - parent.opts[7].length > 15) {
|
||||
const parts = this.opts[7].split('.')
|
||||
let childs = parent.childs
|
||||
const i = parseInt(parts[parent.opts[7].split('.').length])
|
||||
const oldParent = parent
|
||||
// 删除整个表格
|
||||
remove = () => {
|
||||
oldParent.remove(i)
|
||||
}
|
||||
for (let i = parent.opts[7].split('.').length; i < parts.length - 2; i++) {
|
||||
childs = childs[parts[i]]
|
||||
}
|
||||
const that = this
|
||||
parent = {
|
||||
childs,
|
||||
opts: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, parts.slice(0, parts.length - 2).join('.')],
|
||||
changeStyle (name, i, value, oldVal) {
|
||||
let style = this.childs[i].attrs.style || ''
|
||||
if (style.includes(';' + name + ':' + oldVal)) {
|
||||
style = style.replace(';' + name + ':' + oldVal, ';' + name + ':' + value)
|
||||
} else {
|
||||
style += ';' + name + ':' + value
|
||||
}
|
||||
that.root._setData(`${this.opts[7]}.${i}.attrs.style`, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!parent) return
|
||||
// 显示实线框
|
||||
this.$set(this.ctrl, 'root', 1)
|
||||
this.root._mask.push(() => this.$set(this.ctrl, 'root', 0))
|
||||
if (this.childs.length === 1 && this.childs[0].type === 'text' && !this.ctrl.e0) {
|
||||
this.$set(this.ctrl, 'e0', 1)
|
||||
this.root._mask.push(() => this.$set(this.ctrl, 'e0', 0))
|
||||
this.i = 0
|
||||
this.cursor = this.childs[0].text.length
|
||||
}
|
||||
const items = this.root._getItem(parent.childs[i], i !== 0, i !== parent.childs.length - 1)
|
||||
this.root._tooltip({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
if (items[tapIndex] === '大小') {
|
||||
// 改变字体大小
|
||||
const style = parent.childs[i].attrs.style || ''
|
||||
let value = style.match(/;font-size:([0-9]+)px/)
|
||||
if (value) {
|
||||
value = parseInt(value[1])
|
||||
} else {
|
||||
value = 16
|
||||
}
|
||||
this.root._slider({
|
||||
min: 10,
|
||||
max: 30,
|
||||
value,
|
||||
top: getTop(e),
|
||||
changing: val => {
|
||||
if (Math.abs(val - value) > 2) {
|
||||
// 字号变换超过 2 时更新到视图
|
||||
parent.changeStyle('font-size', i, val + 'px', value + 'px')
|
||||
value = e.detail.value
|
||||
}
|
||||
},
|
||||
change: val => {
|
||||
if (val !== value) {
|
||||
parent.changeStyle('font-size', i, val + 'px', value + 'px')
|
||||
}
|
||||
this.root._editVal(`${parent.opts[7]}.${i}.attrs.style`, style, parent.childs[i].attrs.style)
|
||||
}
|
||||
})
|
||||
} else if (items[tapIndex] === '颜色') {
|
||||
// 改变文字颜色
|
||||
const items = this.root._getItem('color')
|
||||
this.root._color({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
const style = parent.childs[i].attrs.style || ''
|
||||
const value = style.match(/;color:([^;]+)/)
|
||||
parent.changeStyle('color', i, items[tapIndex], value ? value[1] : undefined)
|
||||
this.root._editVal(`${parent.opts[7]}.${i}.attrs.style`, style, parent.childs[i].attrs.style)
|
||||
}
|
||||
})
|
||||
} else if (items[tapIndex] === '上移' || items[tapIndex] === '下移') {
|
||||
const arr = parent.childs.slice(0)
|
||||
const item = arr[i]
|
||||
if (items[tapIndex] === '上移') {
|
||||
arr[i] = arr[i - 1]
|
||||
arr[i - 1] = item
|
||||
} else {
|
||||
arr[i] = arr[i + 1]
|
||||
arr[i + 1] = item
|
||||
}
|
||||
this.root._editVal(parent.opts[7], parent.childs, arr, true)
|
||||
} else if (items[tapIndex] === '删除') {
|
||||
remove()
|
||||
} else {
|
||||
const style = parent.childs[i].attrs.style || ''
|
||||
let newStyle = ''
|
||||
const item = items[tapIndex]
|
||||
let name
|
||||
let value
|
||||
if (item === '斜体') {
|
||||
name = 'font-style'
|
||||
value = 'italic'
|
||||
} else if (item === '粗体') {
|
||||
name = 'font-weight'
|
||||
value = 'bold'
|
||||
} else if (item === '下划线') {
|
||||
name = 'text-decoration'
|
||||
value = 'underline'
|
||||
} else if (item === '居中') {
|
||||
name = 'text-align'
|
||||
value = 'center'
|
||||
} else if (item === '缩进') {
|
||||
name = 'text-indent'
|
||||
value = '2em'
|
||||
}
|
||||
if (style.includes(name + ':')) {
|
||||
// 已有则取消
|
||||
newStyle = style.replace(new RegExp(name + ':[^;]+'), '')
|
||||
} else {
|
||||
// 没有则添加
|
||||
newStyle = style + ';' + name + ':' + value
|
||||
}
|
||||
this.root._editVal(`${parent.opts[7]}.${i}.attrs.style`, style, newStyle, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 音视频被点击
|
||||
* @param {Event} e
|
||||
*/
|
||||
mediaTap (e, index) {
|
||||
if (this.opts[5]) {
|
||||
const i = e.target.dataset.i || index
|
||||
const node = this.childs[i]
|
||||
const items = this.root._getItem(node)
|
||||
this.root._maskTap()
|
||||
this.root._edit = this
|
||||
this.i = i
|
||||
this.root._tooltip({
|
||||
top: e.currentTarget.offsetTop - 30,
|
||||
items,
|
||||
success: tapIndex => {
|
||||
switch (items[tapIndex]) {
|
||||
case '封面':
|
||||
// 设置封面
|
||||
this.root.getSrc('img', node.attrs.poster || '').then(url => {
|
||||
this.root._editVal(`${this.opts[7]}.${i}.attrs.poster`, node.attrs.poster, url instanceof Array ? url[0] : url, true)
|
||||
}).catch(() => { })
|
||||
break
|
||||
case '删除':
|
||||
this.remove(i)
|
||||
break
|
||||
case '循环':
|
||||
case '不循环':
|
||||
// 切换循环播放
|
||||
this.root._setData(`${this.opts[7]}.${i}.attrs.loop`, !node.attrs.loop)
|
||||
uni.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
break
|
||||
case '自动播放':
|
||||
case '不自动播放':
|
||||
// 切换自动播放播放
|
||||
this.root._setData(`${this.opts[7]}.${i}.attrs.autoplay`, !node.attrs.autoplay)
|
||||
uni.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
// 避免上层出现点击态
|
||||
this.root._lock = true
|
||||
setTimeout(() => {
|
||||
this.root._lock = false
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 改变样式
|
||||
* @param {String} name 属性名
|
||||
* @param {Number} i 第几个标签
|
||||
* @param {String} value 新值
|
||||
* @param {String} oldVal 旧值
|
||||
*/
|
||||
changeStyle (name, i, value, oldVal) {
|
||||
let style = this.childs[i].attrs.style || ''
|
||||
if (style.includes(';' + name + ':' + oldVal)) {
|
||||
// style 中已经有
|
||||
style = style.replace(';' + name + ':' + oldVal, ';' + name + ':' + value)
|
||||
} else {
|
||||
// 没有则新增
|
||||
style += ';' + name + ':' + value
|
||||
}
|
||||
this.root._setData(`${this.opts[7]}.${i}.attrs.style`, style)
|
||||
}
|
||||
},
|
||||
handler (file) {
|
||||
if (file.isBuffer()) {
|
||||
let content = file.contents.toString()
|
||||
if (file.path.includes('mp-html.vue')) {
|
||||
// 传递 editable 属性和路径
|
||||
content = content.replace(/opts\s*=\s*"\[([^\]]+)\]"/, 'opts="[$1,editable,placeholder,\'nodes\']"')
|
||||
.replace(/<view(.*?):style\s*=\s*"containerStyle"/, '<view$1:style="(editable?\'min-height:200px;\':\'\')+containerStyle" @tap="_containTap"')
|
||||
// 工具弹窗
|
||||
.replace(/<\/view>\s*<\/template>/, ` <view v-if="tooltip" class="_tooltip_contain" :style="'top:'+tooltip.top+'px'">
|
||||
<view class="_tooltip">
|
||||
<view v-for="(item, index) in tooltip.items" v-bind:key="index" class="_tooltip_item" :data-i="index" @tap="_tooltipTap">{{item}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="slider" class="_slider" :style="'top:'+slider.top+'px'">
|
||||
<slider :value="slider.value" :min="slider.min" :max="slider.max" handle-size="14" block-size="14" show-value activeColor="white" style="padding:3px" @changing="_sliderChanging" @change="_sliderChange" />
|
||||
</view>
|
||||
<view v-if="color" class="_tooltip_contain" :style="'top:'+color.top+'px'">
|
||||
<view class="_tooltip" style="overflow-y: hidden;">
|
||||
<view v-for="(item, index) in color.items" v-bind:key="index" class="_color_item" :style="'background-color:'+item" :data-i="index" @tap="_colorTap"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>`)
|
||||
// 添加 data
|
||||
.replace(/data\s*\(\)\s*{\s*return\s*{/, `data() {
|
||||
return {
|
||||
tooltip: null,
|
||||
slider: null,
|
||||
color: null,`)
|
||||
// 添加 editable 属性
|
||||
.replace(/props\s*:\s*{/, `props: {
|
||||
editable: [Boolean, String],
|
||||
placeholder: String,`)
|
||||
// 添加 watch
|
||||
.replace(/watch\s*:\s*{/, `watch: {
|
||||
editable(val) {
|
||||
this.setContent(val ? this.content : this.getContent())
|
||||
if (!val)
|
||||
this._maskTap()
|
||||
},`)
|
||||
.replace(/if\s*\(this.content/, 'if ((this.content || this.editable)')
|
||||
// 处理各类弹窗的事件
|
||||
.replace(/methods\s*:\s*{/, `methods: {
|
||||
_containTap() {
|
||||
if (!this._lock && !this.slider && !this.color) {
|
||||
this._edit = undefined
|
||||
this._maskTap()
|
||||
}
|
||||
},
|
||||
_tooltipTap(e) {
|
||||
this._tooltipcb(e.currentTarget.dataset.i)
|
||||
this.$set(this, 'tooltip', null)
|
||||
},
|
||||
_sliderChanging(e) {
|
||||
this._slideringcb(e.detail.value)
|
||||
},
|
||||
_sliderChange(e) {
|
||||
this._slidercb(e.detail.value)
|
||||
},
|
||||
_colorTap(e) {
|
||||
this._colorcb(e.currentTarget.dataset.i)
|
||||
this.$set(this, 'color', null)
|
||||
},`)
|
||||
// 工具弹窗的样式
|
||||
.replace('</style>', `
|
||||
/* 提示条 */
|
||||
._tooltip_contain {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
left: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
._tooltip {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
height: 30px;
|
||||
padding: 0 3px;
|
||||
overflow: scroll;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
._tooltip_item {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
padding: 0 2vw;
|
||||
line-height: 30px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
._color_item {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 5px 2vw;
|
||||
border:1px solid #dfe2e5;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 图片宽度滚动条 */
|
||||
._slider {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
._tooltip,
|
||||
._slider {
|
||||
background-color: black;
|
||||
border-radius: 3px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
</style>`)
|
||||
} else if (file.path.includes('parser.js')) {
|
||||
// 不做 expose 处理
|
||||
content = content.replace(/parser.prototype.expose\s*=\s*function\s*\(\)\s*{/, `parser.prototype.expose = function () {
|
||||
if (this.options.editable) return`)
|
||||
.replace(/popNode\s*=\s*function\s*\(\)\s*{/, 'popNode = function () {\n const editable = this.options.editable')
|
||||
// 不转换标签名
|
||||
.replace(/if\s*\(config.blockTags\[node.name\]\)\s*{[\s\S]+?}/, `if (config.blockTags[node.name]) {
|
||||
if (!editable) {
|
||||
node.name = 'div'
|
||||
}
|
||||
}`)
|
||||
// 转换表格和列表
|
||||
.replace(/else\s*if\s*\(node.c\)/, 'else if (!editable && node.c )')
|
||||
.replace(/node.c(\)|\s*&&|\s*\n)/g, '(node.c || editable)$1')
|
||||
.replace(/while\s*\(map\[row\s*\+\s*'.'\s*\+\s*col\]\)\s*{[\s\S]+?}/, `while (map[row + '.' + col]) {
|
||||
col++
|
||||
}
|
||||
if (editable) {
|
||||
td.r = row
|
||||
}`)
|
||||
.replace(/let\s+str\s*=\s*'<video style="width:100%;height:100%"'/, `let str = '<video style="width:100%;height:100%"'
|
||||
if (editable) {
|
||||
attrs.controls = ''
|
||||
}`)
|
||||
} else if (file.path.includes('node.vue')) {
|
||||
content =
|
||||
// 传递 opts
|
||||
content.replace(/:childs\s*=\s*"tbody.children"\s*:opts="opts"/, ':childs="tbody.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+\'.\'+i+\'.children.\'+x+\'.children\']"')
|
||||
.replace(/:childs\s*=\s*"n2.children"\s*:opts="opts"/, ':childs="n2.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+\'.\'+i+\'.children.\'+j+\'.children\']"')
|
||||
.replace(/:childs\s*=\s*"tr.children"\s*:opts="opts"/, ':childs="tr.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+\'.\'+i+\'.children.\'+x+\'.children.\'+y+\'.children\']"')
|
||||
.replace(/:childs\s*=\s*"td.children"\s*:opts="opts"/, ':childs="td.children" :opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+\'.\'+i+\'.children.\'+x+\'.children.\'+y+\'.children.\'+z+\'.children\']"')
|
||||
.replace(/opts\s*=\s*"opts"/g, 'opts="[opts[0],opts[1],opts[2],opts[3],opts[4],opts[5],opts[6],opts[7]+\'.\'+i+\'.children\']"')
|
||||
// 不使用 rich-text
|
||||
.replace(/!n.c/g, '!opts[5]&&!n.c').replace('&&n.c', '&&(n.c||opts[5])')
|
||||
// 修改普通标签
|
||||
.replace(/<view\s+:id(.+?)style="/, '<view @tap="nodeTap" :id$1style="(ctrl.root&&opts[5]!==\'simple\'?\'border:1px solid black;padding:5px;display:block;\':\'\')+')
|
||||
// 修改文本块
|
||||
.replace(/<!--\s*文本\s*-->[\s\S]+?<!--\s*链接\s*-->/,
|
||||
`<!-- 文本 -->
|
||||
<text v-else-if="n.type==='text'&&!ctrl['e'+i]" :data-i="i" :user-select="opts[4]" :decode="!opts[5]" @tap="editStart">{{n.text}}
|
||||
<text v-if="!n.text" style="color:gray">{{opts[6]||'请输入'}}</text>
|
||||
</text>
|
||||
<text v-else-if="n.type==='text'&&ctrl['e'+i]===1" :data-i="i" style="border:1px dashed black;min-width:50px;width:auto;padding:5px;display:block" @tap.stop="editStart">{{n.text}}
|
||||
<text v-if="!n.text" style="color:gray">{{opts[6]||'请输入'}}</text>
|
||||
</text>
|
||||
<textarea v-else-if="n.type==='text'" :style="opts[5]==='simple'?'':'border:1px dashed black;'+'min-width:50px;width:auto;padding:5px'" auto-height maxlength="-1" :focus="ctrl['e'+i]===3" :value="n.text" :data-i="i" @input="editInput" @blur="editEnd" />
|
||||
<text v-else-if="n.name==='br'">\\n</text>
|
||||
<!-- 链接 -->`)
|
||||
// 修改图片
|
||||
.replace(/<image(.+?)id="n.attrs.id/, '<image$1id="n.attrs.id||(\'n\'+i)')
|
||||
.replace('height:1px', "height:'+(ctrl['h'+i]||1)+'px")
|
||||
.replace(/:style\s*=\s*"\(ctrl\[i\]/g, ':style="(ctrl[\'e\'+i]&&opts[5]!==\'simple\'?\'border:1px dashed black;padding:3px;\':\'\')+(ctrl[i]')
|
||||
.replace(/show-menu-by-longpress\s*=\s*"(\S+?)"\s*:image-menu-prevent\s*=\s*"(\S+?)"/, 'show-menu-by-longpress="!opts[5]&&$1" :image-menu-prevent="opts[5]||$2"')
|
||||
// 修改音视频
|
||||
.replace('v-else-if="n.html"', 'v-else-if="n.html" :data-i="i" @tap="mediaTap"')
|
||||
.replace('<video', '<video :show-center-play-btn="!opts[5]" @tap="mediaTap"')
|
||||
.replace('<audio ', '<audio @tap="mediaTap" ')
|
||||
.replace('<my-audio ', '<my-audio @onClick="mediaTap($event, i)" ')
|
||||
.replace('card ', 'card @click="mediaTap($event, i)" ')
|
||||
.replace('<script>',
|
||||
`<script>
|
||||
import Parser from '../parser'
|
||||
function getTop(e) {
|
||||
let top
|
||||
// #ifdef H5 && VUE3
|
||||
top = e.pageY
|
||||
// #endif
|
||||
// #ifdef (H5 && VUE2) || APP-PLUS
|
||||
top = e.touches[0].pageY
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
top = e.detail.pageY
|
||||
// #endif
|
||||
// #ifndef H5 || MP-ALIPAY || APP-PLUS
|
||||
top = e.detail.y
|
||||
// #endif
|
||||
if (top - e.currentTarget.offsetTop < 150 || top < 600) {
|
||||
top = e.currentTarget.offsetTop
|
||||
}
|
||||
if (top < 30) {
|
||||
top += 70
|
||||
}
|
||||
return top - 30
|
||||
}`)
|
||||
// 周期处理
|
||||
.replace(/beforeDestroy\s*\(\)\s*{/, `beforeDestroy () {
|
||||
if (this.root && this.root._edit === this) {
|
||||
this.root._edit = undefined
|
||||
}`)
|
||||
// 记录图片宽度
|
||||
.replace(/imgLoad\s*\(e\)\s*{/, `imgLoad(e) {
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
if (this.opts[5])
|
||||
this.$nextTick(() => {
|
||||
const id = this.childs[i].attrs.id || ('n' + i)
|
||||
uni.createSelectorQuery().in(this).select('#' + id).boundingClientRect().exec(res => {
|
||||
this.$set(this.ctrl, 'h'+i, res[0].height)
|
||||
})
|
||||
})
|
||||
// #endif`)
|
||||
.replace(/if\s*\(!this.childs\[i\].w\)\s*{[\s\S]+?}/,
|
||||
`if (!this.childs[i].w) {
|
||||
this.$set(this.ctrl, i, e.detail.width)
|
||||
if (this.opts[5]) {
|
||||
const path = this.opts[7] + '.' + i + '.attrs.'
|
||||
if (e.detail.width < 150)
|
||||
this.root._setData(path + 'ignore', 'T')
|
||||
this.root._setData(path + 'width', e.detail.width.toString())
|
||||
}
|
||||
}`)
|
||||
// 处理图片长按
|
||||
.replace(/imgLongTap\s*\(\)\s*{/, `imgLongTap() {
|
||||
if (this.opts[5]) return`)
|
||||
// 处理图片点击
|
||||
.replace(/imgTap\s*\(e\)\s*{([\s\S]+?)},\s*\/\*/,
|
||||
`imgTap (e) {
|
||||
if (!this.opts[5]) {$1} else {
|
||||
const i = e.currentTarget.dataset.i
|
||||
const node = this.childs[i]
|
||||
const items = this.root._getItem(node)
|
||||
const parser = new Parser(this.root)
|
||||
this.root._edit = this
|
||||
this.i = i
|
||||
this.root._maskTap()
|
||||
this.$set(this.ctrl, 'e' + i, 1)
|
||||
this.root._mask.push(() => this.$set(this.ctrl, 'e' + i, 0))
|
||||
this.root._tooltip({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
if (items[tapIndex] === '换图') {
|
||||
// 换图
|
||||
this.root.getSrc('img', node.attrs.src || '').then(url => {
|
||||
this.root._editVal(this.opts[7] + '.' + i + '.attrs.src', node.attrs.src, parser.getUrl(url instanceof Array ? url[0] : url), true)
|
||||
}).catch(() => { })
|
||||
} else if (items[tapIndex] === '宽度') {
|
||||
// 更改宽度
|
||||
const style = node.attrs.style || ''
|
||||
let value = style.match(/max-width:([0-9]+)%/)
|
||||
if (value) {
|
||||
value = parseInt(value[1])
|
||||
} else {
|
||||
value = 100
|
||||
}
|
||||
this.root._slider({
|
||||
min: 0,
|
||||
max: 100,
|
||||
value,
|
||||
top: getTop(e),
|
||||
changing: val => {
|
||||
// 变化超过 5% 更新时视图
|
||||
if (Math.abs(val - value) > 5) {
|
||||
this.changeStyle('max-width', i, val + '%', value + '%')
|
||||
value = val
|
||||
}
|
||||
},
|
||||
change: val => {
|
||||
if (val !== value) {
|
||||
this.changeStyle('max-width', i, val + '%', value + '%')
|
||||
value = val
|
||||
}
|
||||
this.root._editVal(this.opts[7] + '.' + i + '.attrs.style', style, this.childs[i].attrs.style)
|
||||
}
|
||||
})
|
||||
} else if (items[tapIndex] === '超链接') {
|
||||
// 将图片设置为链接
|
||||
this.root.getSrc('link', node.a ? node.a.href : '').then(url => {
|
||||
// 如果有 a 标签则替换 href
|
||||
if (node.a) {
|
||||
this.root._editVal(this.opts[7] + '.' + i + '.a.href', node.a.href, parser.getUrl(url), true)
|
||||
} else {
|
||||
const link = {
|
||||
name: 'a',
|
||||
attrs: {
|
||||
href: parser.getUrl(url)
|
||||
},
|
||||
children: [node]
|
||||
}
|
||||
node.a = link.attrs
|
||||
this.root._editVal(this.opts[7] + '.' + i, node, link, true)
|
||||
}
|
||||
wx.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}).catch(() => { })
|
||||
} else if (items[tapIndex] === '预览图') {
|
||||
// 设置预览图链接
|
||||
this.root.getSrc('img', node.attrs['original-src'] || '').then(url => {
|
||||
this.root._editVal(this.opts[7] + '.' + i + '.attrs.original-src', node.attrs['original-src'], parser.getUrl(url instanceof Array ? url[0] : url), true)
|
||||
uni.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}).catch(() => { })
|
||||
} else if (items[tapIndex] === '删除') {
|
||||
this.remove(i)
|
||||
} else {
|
||||
// 禁用 / 启用预览
|
||||
this.root._setData(this.opts[7] + '.' + i + '.attrs.ignore', !node.attrs.ignore)
|
||||
uni.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
this.root._lock = true
|
||||
setTimeout(() => {
|
||||
this.root._lock = false
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
/*`)
|
||||
// 处理链接点击
|
||||
.replace(/linkTap\s*\(e\)\s*{([\s\S]+?)},\s*\/\*/,
|
||||
`linkTap (e) {
|
||||
if (!this.opts[5]) {$1} else {
|
||||
const i = e.currentTarget.dataset.i
|
||||
const node = this.childs[i]
|
||||
const items = this.root._getItem(node)
|
||||
this.root._tooltip({
|
||||
top: getTop(e),
|
||||
items,
|
||||
success: tapIndex => {
|
||||
if (items[tapIndex] === '更换链接') {
|
||||
this.root.getSrc('link', node.attrs.href).then(url => {
|
||||
this.root._editVal(this.opts[7] + '.' + i + '.attrs.href', node.attrs.href, url, true)
|
||||
uni.showToast({
|
||||
title: '成功'
|
||||
})
|
||||
}).catch(() => { })
|
||||
} else {
|
||||
this.remove(i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
/*`)
|
||||
}
|
||||
file.contents = Buffer.from(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
+553
@@ -0,0 +1,553 @@
|
||||
/**
|
||||
* @fileoverview editable 插件
|
||||
*/
|
||||
const config = require('./config')
|
||||
const Parser = require('../parser')
|
||||
|
||||
function Editable (vm) {
|
||||
this.vm = vm
|
||||
this.editHistory = [] // 历史记录
|
||||
this.editI = -1 // 历史记录指针
|
||||
vm._mask = [] // 蒙版被点击时进行的操作
|
||||
|
||||
vm._setData = function (path, val) {
|
||||
const paths = path.split('.')
|
||||
let target = vm
|
||||
for (let i = 0; i < paths.length - 1; i++) {
|
||||
target = target[paths[i]]
|
||||
}
|
||||
vm.$set(target, paths.pop(), val)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 移动历史记录指针
|
||||
* @param {Number} num 移动距离
|
||||
*/
|
||||
const move = num => {
|
||||
setTimeout(() => {
|
||||
const item = this.editHistory[this.editI + num]
|
||||
if (item) {
|
||||
this.editI += num
|
||||
vm._setData(item.key, item.value)
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
vm.undo = () => move(-1) // 撤销
|
||||
vm.redo = () => move(1) // 重做
|
||||
|
||||
/**
|
||||
* @description 更新记录
|
||||
* @param {String} path 更新内容路径
|
||||
* @param {*} oldVal 旧值
|
||||
* @param {*} newVal 新值
|
||||
* @param {Boolean} set 是否更新到视图
|
||||
* @private
|
||||
*/
|
||||
vm._editVal = (path, oldVal, newVal, set) => {
|
||||
// 当前指针后的内容去除
|
||||
while (this.editI < this.editHistory.length - 1) {
|
||||
this.editHistory.pop()
|
||||
}
|
||||
|
||||
// 最多存储 30 条操作记录
|
||||
while (this.editHistory.length > 30) {
|
||||
this.editHistory.pop()
|
||||
this.editI--
|
||||
}
|
||||
|
||||
const last = this.editHistory[this.editHistory.length - 1]
|
||||
if (!last || last.key !== path) {
|
||||
if (last) {
|
||||
// 去掉上一次的新值
|
||||
this.editHistory.pop()
|
||||
this.editI--
|
||||
}
|
||||
// 存入这一次的旧值
|
||||
this.editHistory.push({
|
||||
key: path,
|
||||
value: oldVal
|
||||
})
|
||||
this.editI++
|
||||
}
|
||||
|
||||
// 存入本次的新值
|
||||
this.editHistory.push({
|
||||
key: path,
|
||||
value: newVal
|
||||
})
|
||||
this.editI++
|
||||
|
||||
// 更新到视图
|
||||
if (set) {
|
||||
vm._setData(path, newVal)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取菜单项
|
||||
* @private
|
||||
*/
|
||||
vm._getItem = function (node, up, down) {
|
||||
let items
|
||||
let i
|
||||
if (node === 'color') {
|
||||
return config.color
|
||||
}
|
||||
if (node.name === 'img') {
|
||||
items = config.img.slice(0)
|
||||
if (!vm.getSrc) {
|
||||
i = items.indexOf('换图')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
i = items.indexOf('超链接')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
i = items.indexOf('预览图')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
i = items.indexOf('禁用预览')
|
||||
if (i !== -1 && node.attrs.ignore) {
|
||||
items[i] = '启用预览'
|
||||
}
|
||||
} else if (node.name === 'a') {
|
||||
items = config.link.slice(0)
|
||||
if (!vm.getSrc) {
|
||||
i = items.indexOf('更换链接')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
} else if (node.name === 'video' || node.name === 'audio') {
|
||||
items = config.media.slice(0)
|
||||
i = items.indexOf('封面')
|
||||
if (!vm.getSrc && i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
i = items.indexOf('循环')
|
||||
if (node.attrs.loop && i !== -1) {
|
||||
items[i] = '不循环'
|
||||
}
|
||||
i = items.indexOf('自动播放')
|
||||
if (node.attrs.autoplay && i !== -1) {
|
||||
items[i] = '不自动播放'
|
||||
}
|
||||
} else if (node.name === 'card') {
|
||||
items = config.card.slice(0)
|
||||
} else {
|
||||
items = config.node.slice(0)
|
||||
}
|
||||
if (!up) {
|
||||
i = items.indexOf('上移')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
if (!down) {
|
||||
i = items.indexOf('下移')
|
||||
if (i !== -1) {
|
||||
items.splice(i, 1)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 显示 tooltip
|
||||
* @param {object} obj
|
||||
* @private
|
||||
*/
|
||||
vm._tooltip = function (obj) {
|
||||
vm.$set(vm, 'tooltip', {
|
||||
top: obj.top,
|
||||
items: obj.items
|
||||
})
|
||||
vm._tooltipcb = obj.success
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 显示滚动条
|
||||
* @param {object} obj
|
||||
* @private
|
||||
*/
|
||||
vm._slider = function (obj) {
|
||||
vm.$set(vm, 'slider', {
|
||||
min: obj.min,
|
||||
max: obj.max,
|
||||
value: obj.value,
|
||||
top: obj.top
|
||||
})
|
||||
vm._slideringcb = obj.changing
|
||||
vm._slidercb = obj.change
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 显示颜色选择
|
||||
* @param {object} obj
|
||||
* @private
|
||||
*/
|
||||
vm._color = function (obj) {
|
||||
vm.$set(vm, 'color', {
|
||||
items: obj.items,
|
||||
top: obj.top
|
||||
})
|
||||
vm._colorcb = obj.success
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 点击蒙版
|
||||
* @private
|
||||
*/
|
||||
vm._maskTap = function () {
|
||||
// 隐藏所有悬浮窗
|
||||
while (vm._mask.length) {
|
||||
(vm._mask.pop())()
|
||||
}
|
||||
if (vm.tooltip) {
|
||||
vm.$set(vm, 'tooltip', null)
|
||||
}
|
||||
if (vm.slider) {
|
||||
vm.$set(vm, 'slider', null)
|
||||
}
|
||||
if (vm.color) {
|
||||
vm.$set(vm, 'color', null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 插入节点
|
||||
* @param {Object} node
|
||||
*/
|
||||
function insert (node) {
|
||||
if (vm._edit) {
|
||||
vm._edit.insert(node)
|
||||
} else {
|
||||
const nodes = vm.nodes.slice(0)
|
||||
nodes.push(node)
|
||||
vm._editVal('nodes', vm.nodes, nodes, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入指定 html 内容
|
||||
* @param {String} html 内容
|
||||
*/
|
||||
vm.insertHtml = html => {
|
||||
this.inserting = true
|
||||
const arr = new Parser(vm).parse(html)
|
||||
this.inserting = undefined
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
insert(arr[i])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入图片
|
||||
*/
|
||||
vm.insertImg = function () {
|
||||
vm.getSrc && vm.getSrc('img').then(src => {
|
||||
if (typeof src === 'string') {
|
||||
src = [src]
|
||||
}
|
||||
const parser = new Parser(vm)
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
insert({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: parser.getUrl(src[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个链接
|
||||
*/
|
||||
vm.insertLink = function () {
|
||||
vm.getSrc && vm.getSrc('link').then(url => {
|
||||
insert({
|
||||
name: 'a',
|
||||
attrs: {
|
||||
href: url
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: url
|
||||
}]
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个表格
|
||||
* @param {Number} rows 行数
|
||||
* @param {Number} cols 列数
|
||||
*/
|
||||
vm.insertTable = function (rows, cols) {
|
||||
const table = {
|
||||
name: 'table',
|
||||
attrs: {
|
||||
style: 'display:table;width:100%;margin:10px 0;text-align:center;border-spacing:0;border-collapse:collapse;border:1px solid gray'
|
||||
},
|
||||
children: []
|
||||
}
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const tr = {
|
||||
name: 'tr',
|
||||
attrs: {},
|
||||
children: []
|
||||
}
|
||||
for (let j = 0; j < cols; j++) {
|
||||
tr.children.push({
|
||||
name: 'td',
|
||||
attrs: {
|
||||
style: 'padding:2px;border:1px solid gray'
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
})
|
||||
}
|
||||
table.children.push(tr)
|
||||
}
|
||||
insert(table)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 插入视频/音频
|
||||
* @param {Object} node
|
||||
*/
|
||||
function insertMedia (node) {
|
||||
if (typeof node.src === 'string') {
|
||||
node.src = [node.src]
|
||||
}
|
||||
const parser = new Parser(vm)
|
||||
// 拼接主域名
|
||||
for (let i = 0; i < node.src.length; i++) {
|
||||
node.src[i] = parser.getUrl(node.src[i])
|
||||
}
|
||||
insert({
|
||||
name: 'div',
|
||||
attrs: {
|
||||
style: 'text-align:center'
|
||||
},
|
||||
children: [node]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个视频
|
||||
*/
|
||||
vm.insertVideo = function () {
|
||||
vm.getSrc && vm.getSrc('video').then(src => {
|
||||
insertMedia({
|
||||
name: 'video',
|
||||
attrs: {
|
||||
controls: 'T'
|
||||
},
|
||||
children: [],
|
||||
src,
|
||||
// #ifdef APP-PLUS
|
||||
html: `<video src="${src}" style="width:100%;height:100%"></video>`
|
||||
// #endif
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一个音频
|
||||
*/
|
||||
vm.insertAudio = function () {
|
||||
vm.getSrc && vm.getSrc('audio').then(attrs => {
|
||||
let src
|
||||
if (attrs.src) {
|
||||
src = attrs.src
|
||||
attrs.src = undefined
|
||||
} else {
|
||||
src = attrs
|
||||
attrs = {}
|
||||
}
|
||||
attrs.controls = 'T'
|
||||
insertMedia({
|
||||
name: 'audio',
|
||||
attrs,
|
||||
children: [],
|
||||
src
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在光标处插入一段文本
|
||||
*/
|
||||
vm.insertText = function () {
|
||||
insert({
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 清空内容
|
||||
*/
|
||||
vm.clear = function () {
|
||||
vm._maskTap()
|
||||
vm._edit = undefined
|
||||
vm.$set(vm, 'nodes', [{
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
}])
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取编辑后的 html
|
||||
*/
|
||||
vm.getContent = function () {
|
||||
let html = '';
|
||||
// 递归遍历获取
|
||||
(function traversal (nodes, table) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let item = nodes[i]
|
||||
if (item.type === 'text') {
|
||||
html += item.text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>').replace(/\xa0/g, ' ') // 编码实体
|
||||
} else {
|
||||
if (item.name === 'img') {
|
||||
item.attrs.i = ''
|
||||
// 还原被转换的 svg
|
||||
if ((item.attrs.src || '').includes('data:image/svg+xml;utf8,')) {
|
||||
html += item.attrs.src.substr(24).replace(/%23/g, '#').replace('<svg', '<svg style="' + (item.attrs.style || '') + '"')
|
||||
continue
|
||||
}
|
||||
} else if (item.name === 'video' || item.name === 'audio') {
|
||||
// 还原 video 和 audio 的 source
|
||||
item = JSON.parse(JSON.stringify(item))
|
||||
if (item.src.length > 1) {
|
||||
item.children = []
|
||||
for (let j = 0; j < item.src.length; j++) {
|
||||
item.children.push({
|
||||
name: 'source',
|
||||
attrs: {
|
||||
src: item.src[j]
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
item.attrs.src = item.src[0]
|
||||
}
|
||||
} else if (item.name === 'div' && (item.attrs.style || '').includes('overflow:auto') && (item.children[0] || {}).name === 'table') {
|
||||
// 还原滚动层
|
||||
item = item.children[0]
|
||||
}
|
||||
// 还原 table
|
||||
if (item.name === 'table') {
|
||||
item = JSON.parse(JSON.stringify(item))
|
||||
table = item.attrs
|
||||
if ((item.attrs.style || '').includes('display:grid')) {
|
||||
item.attrs.style = item.attrs.style.split('display:grid')[0]
|
||||
const children = [{
|
||||
name: 'tr',
|
||||
attrs: {},
|
||||
children: []
|
||||
}]
|
||||
for (let j = 0; j < item.children.length; j++) {
|
||||
item.children[j].attrs.style = item.children[j].attrs.style.replace(/grid-[^;]+;*/g, '')
|
||||
if (item.children[j].r !== children.length) {
|
||||
children.push({
|
||||
name: 'tr',
|
||||
attrs: {},
|
||||
children: [item.children[j]]
|
||||
})
|
||||
} else {
|
||||
children[children.length - 1].children.push(item.children[j])
|
||||
}
|
||||
}
|
||||
item.children = children
|
||||
}
|
||||
}
|
||||
html += '<' + item.name
|
||||
for (const attr in item.attrs) {
|
||||
let val = item.attrs[attr]
|
||||
if (!val) continue
|
||||
if (val === 'T' || val === true) {
|
||||
// bool 型省略值
|
||||
html += ' ' + attr
|
||||
continue
|
||||
} else if (item.name[0] === 't' && attr === 'style' && table) {
|
||||
// 取消为了显示 table 添加的 style
|
||||
val = val.replace(/;*display:table[^;]*/, '')
|
||||
if (table.border) {
|
||||
val = val.replace(/border[^;]+;*/g, $ => $.includes('collapse') ? $ : '')
|
||||
}
|
||||
if (table.cellpadding) {
|
||||
val = val.replace(/padding[^;]+;*/g, '')
|
||||
}
|
||||
if (!val) continue
|
||||
}
|
||||
html += ' ' + attr + '="' + val.replace(/"/g, '"') + '"'
|
||||
}
|
||||
html += '>'
|
||||
if (item.children) {
|
||||
traversal(item.children, table)
|
||||
html += '</' + item.name + '>'
|
||||
}
|
||||
}
|
||||
}
|
||||
})(vm.nodes)
|
||||
|
||||
// 其他插件处理
|
||||
for (let i = vm.plugins.length; i--;) {
|
||||
if (vm.plugins[i].onGetContent) {
|
||||
html = vm.plugins[i].onGetContent(html) || html
|
||||
}
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
}
|
||||
|
||||
Editable.prototype.onUpdate = function (content, config) {
|
||||
if (this.vm.editable) {
|
||||
this.vm._maskTap()
|
||||
config.entities.amp = '&'
|
||||
if (!this.inserting) {
|
||||
this.vm._edit = undefined
|
||||
if (!content) {
|
||||
setTimeout(() => {
|
||||
this.vm.$set(this.vm, 'nodes', [{
|
||||
name: 'p',
|
||||
attrs: {},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: ''
|
||||
}]
|
||||
}])
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Editable.prototype.onParse = function (node) {
|
||||
// 空白单元格可编辑
|
||||
if (this.vm.editable && (node.name === 'td' || node.name === 'th') && !this.vm.getText(node.children)) {
|
||||
node.children.push({
|
||||
type: 'text',
|
||||
text: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Editable
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# emoji
|
||||
功能:解析 *emoji*
|
||||
大小:*≈3KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √ |
|
||||
|
||||
说明:
|
||||
将形如 *[笑脸]* 的文本替换为 *emoji* 字符 😄
|
||||
匹配模式可以通过修改 *reg* 变量实现
|
||||
默认配置了 *177* 个常用的 *emoji* 小表情,可以自行按照需要修改 *data* 变量
|
||||
|
||||
?> 与 *editable* 插件共用时,导出编辑好的 *html* 内容,会将 *emoji* 字符编码为文本形式,便于存储
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @fileoverview emoji 插件
|
||||
*/
|
||||
const reg = /\[(\S+?)\]/g
|
||||
const data = {
|
||||
笑脸: '😄',
|
||||
生病: '😷',
|
||||
破涕为笑: '😂',
|
||||
吐舌: '😝',
|
||||
脸红: '😳',
|
||||
恐惧: '😱',
|
||||
失望: '😔',
|
||||
无语: '😒',
|
||||
眨眼: '😉',
|
||||
酷: '😎',
|
||||
哭: '😭',
|
||||
痴迷: '😍',
|
||||
吻: '😘',
|
||||
思考: '🤔',
|
||||
困惑: '😕',
|
||||
颠倒: '🙃',
|
||||
钱: '🤑',
|
||||
惊讶: '😲',
|
||||
白眼: '🙄',
|
||||
叹气: '😤',
|
||||
睡觉: '😴',
|
||||
书呆子: '🤓',
|
||||
愤怒: '😡',
|
||||
面无表情: '😑',
|
||||
张嘴: '😮',
|
||||
量体温: '🤒',
|
||||
呕吐: '🤮',
|
||||
光环: '😇',
|
||||
幽灵: '👻',
|
||||
外星人: '👽',
|
||||
机器人: '🤖',
|
||||
捂眼镜: '🙈',
|
||||
捂耳朵: '🙉',
|
||||
捂嘴: '🙊',
|
||||
婴儿: '👶',
|
||||
男孩: '👦',
|
||||
女孩: '👧',
|
||||
男人: '👨',
|
||||
女人: '👩',
|
||||
老人: '👴',
|
||||
老妇人: '👵',
|
||||
警察: '👮',
|
||||
王子: '🤴',
|
||||
公主: '🤴',
|
||||
举手: '🙋',
|
||||
跑步: '🏃',
|
||||
家庭: '👪',
|
||||
眼睛: '👀',
|
||||
鼻子: '👃',
|
||||
耳朵: '👂',
|
||||
舌头: '👅',
|
||||
嘴: '👄',
|
||||
心: '❤️',
|
||||
心碎: '💔',
|
||||
雪人: '☃️',
|
||||
情书: '💌',
|
||||
大便: '💩',
|
||||
闹钟: '⏰',
|
||||
眼镜: '👓',
|
||||
雨伞: '☂️',
|
||||
音乐: '🎵',
|
||||
话筒: '🎤',
|
||||
游戏机: '🎮',
|
||||
喇叭: '📢',
|
||||
耳机: '🎧',
|
||||
礼物: '🎁',
|
||||
电话: '📞',
|
||||
电脑: '💻',
|
||||
打印机: '🖨️',
|
||||
手电筒: '🔦',
|
||||
灯泡: '💡',
|
||||
书本: '📖',
|
||||
信封: '✉️',
|
||||
药丸: '💊',
|
||||
口红: '💄',
|
||||
手机: '📱',
|
||||
相机: '📷',
|
||||
电视: '📺',
|
||||
中: '🀄',
|
||||
垃圾桶: '🚮',
|
||||
厕所: '🚾',
|
||||
感叹号: '❗',
|
||||
禁: '🈲',
|
||||
可: '🉑',
|
||||
彩虹: '🌈',
|
||||
旋风: '🌀',
|
||||
雷电: '⚡',
|
||||
雪花: '❄️',
|
||||
星星: '⭐',
|
||||
水滴: '💧',
|
||||
玫瑰: '🌹',
|
||||
加油: '💪',
|
||||
左: '👈',
|
||||
右: '👉',
|
||||
上: '👆',
|
||||
下: '👇',
|
||||
手掌: '🖐️',
|
||||
好的: '👌',
|
||||
好: '👍',
|
||||
差: '👎',
|
||||
胜利: '✌',
|
||||
拳头: '👊',
|
||||
挥手: '👋',
|
||||
鼓掌: '👏',
|
||||
猴子: '🐒',
|
||||
狗: '🐶',
|
||||
狼: '🐺',
|
||||
猫: '🐱',
|
||||
老虎: '🐯',
|
||||
马: '🐎',
|
||||
独角兽: '🦄',
|
||||
斑马: '🦓',
|
||||
鹿: '🦌',
|
||||
牛: '🐮',
|
||||
猪: '🐷',
|
||||
羊: '🐏',
|
||||
长颈鹿: '🦒',
|
||||
大象: '🐘',
|
||||
老鼠: '🐭',
|
||||
蝙蝠: '🦇',
|
||||
刺猬: '🦔',
|
||||
熊猫: '🐼',
|
||||
鸽子: '🕊️',
|
||||
鸭子: '🦆',
|
||||
兔子: '🐇',
|
||||
老鹰: '🦅',
|
||||
青蛙: '🐸',
|
||||
蛇: '🐍',
|
||||
龙: '🐉',
|
||||
鲸鱼: '🐳',
|
||||
海豚: '🐬',
|
||||
足球: '⚽',
|
||||
棒球: '⚾',
|
||||
篮球: '🏀',
|
||||
排球: '🏐',
|
||||
橄榄球: '🏉',
|
||||
网球: '🎾',
|
||||
骰子: '🎲',
|
||||
鸡腿: '🍗',
|
||||
蛋糕: '🎂',
|
||||
啤酒: '🍺',
|
||||
饺子: '🥟',
|
||||
汉堡: '🍔',
|
||||
薯条: '🍟',
|
||||
意大利面: '🍝',
|
||||
干杯: '🥂',
|
||||
筷子: '🥢',
|
||||
糖果: '🍬',
|
||||
奶瓶: '🍼',
|
||||
爆米花: '🍿',
|
||||
邮局: '🏤',
|
||||
医院: '🏥',
|
||||
银行: '🏦',
|
||||
酒店: '🏨',
|
||||
学校: '🏫',
|
||||
城堡: '🏰',
|
||||
火车: '🚂',
|
||||
高铁: '🚄',
|
||||
地铁: '🚇',
|
||||
公交: '🚌',
|
||||
救护车: '🚑',
|
||||
消防车: '🚒',
|
||||
警车: '🚓',
|
||||
出租车: '🚕',
|
||||
汽车: '🚗',
|
||||
货车: '🚛',
|
||||
自行车: '🚲',
|
||||
摩托: '🛵',
|
||||
红绿灯: '🚥',
|
||||
帆船: '⛵',
|
||||
游轮: '🛳️',
|
||||
轮船: '⛴️',
|
||||
飞机: '✈️',
|
||||
直升机: '🚁',
|
||||
缆车: '🚠',
|
||||
警告: '⚠️',
|
||||
禁止: '⛔'
|
||||
}
|
||||
|
||||
function Emoji () {
|
||||
|
||||
}
|
||||
|
||||
Emoji.prototype.onUpdate = function (content) {
|
||||
return content.replace(reg, ($, $1) => {
|
||||
if (data[$1]) return data[$1]
|
||||
return $
|
||||
})
|
||||
}
|
||||
|
||||
Emoji.prototype.onGetContent = function (content) {
|
||||
for (const item in data) {
|
||||
content = content.replace(new RegExp(data[item], 'g'), '[' + item + ']')
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
module.exports = Emoji
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# highlight
|
||||
功能:代码块高亮显示
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √ |
|
||||
|
||||
说明:
|
||||
大小:*≈16KB*
|
||||
编辑 *plugins/highlight/config.js* 顶部的选项,可以选择是否需要以下功能:
|
||||
- *copyByLongPress* 是否需要长按代码块时显示复制代码内容菜单(*uni-app nvue* 暂不支持)
|
||||
- *showLanguageName* 是否在代码块右上角显示语言的名称
|
||||
- *showLineNumber* 是否在左侧显示行号
|
||||
|
||||
> 修改该配置后需要重新生成组件包,在构建后的组件包中修改配置无法生效
|
||||
|
||||
引入本插件后,*html* 中符合以下格式的 *pre* 将被高亮处理:
|
||||
```html
|
||||
<!-- pre 中内含一个 code,并在 pre 或 code 的 class 中设置 language- -->
|
||||
<pre><code class="language-css">p { color: red }</code></pre>
|
||||
```
|
||||
|
||||
> 与 *editable* 插件共用时,编辑状态下,不会进行高亮,可以直接修改代码文本
|
||||
|
||||
> 本插件的高亮功能依赖于 [prismjs](https://prismjs.com/),默认配置中仅支持 *html*、*css*、*c-like*、*javascript* 语言和 *Tomorrow Night* 主题,如果需要更多语言或更换主题请前往 [官网](https://prismjs.com/download.html) 下载对应的 *prism.min.js* 和 *prism.css* 并替换 *plugins/highlight/* 目录下的文件
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
copyByLongPress: false, // 是否需要长按代码块时显示复制代码内容菜单
|
||||
showLanguageName: false, // 是否在代码块右上角显示语言的名称
|
||||
showLineNumber: false // 是否显示行号
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @fileoverview highlight 插件
|
||||
* Include prismjs (https://prismjs.com)
|
||||
*/
|
||||
const prism = require('./prism.min')
|
||||
const config = require('./config')
|
||||
const Parser = require('../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'
|
||||
if (config.showLanguageName) {
|
||||
node.children.push({
|
||||
name: 'div',
|
||||
attrs: {
|
||||
class: 'hl-language',
|
||||
style: 'user-select:none'
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: lang
|
||||
}]
|
||||
})
|
||||
}
|
||||
if (config.copyByLongPress) {
|
||||
node.attrs.style += (node.attrs.style || '') + ';user-select:none'
|
||||
node.attrs['data-content'] = 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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Highlight
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
const config = require('../config')
|
||||
|
||||
const build = {
|
||||
import: 'prism.css',
|
||||
handler (file) {
|
||||
if (file.path.includes('prism.css')) {
|
||||
// 将标签名选择器和属性选择器转为 class 选择器(组件内仅支持 class 选择器)
|
||||
file.contents = Buffer.from(file.contents.toString().replace(/pre([[)])/g, '.hl-pre$1').replace(/code/g, '.hl-code').replace(/\[class\*="?language-"?\]/g, '').replace(/:not[^,}]+[,}]*/g, '').replace(/\.token\./g, '.hl-'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.showLanguageName || config.showLineNumber) {
|
||||
// pre 内部的 code 进行滚动,避免行号和语言名称跟随滚动
|
||||
build.style = `.hl-pre {
|
||||
position: relative;
|
||||
}
|
||||
.hl-code {
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}`
|
||||
}
|
||||
|
||||
if (config.copyByLongPress) {
|
||||
build.template = '<rich-text wx:if="{{n.attrs[\'data-content\']}}" nodes="{{[n]}}" data-content="{{n.attrs[\'data-content\']}}" data-lang="{{n.attrs[\'data-lang\']}}" bindlongpress="copyCode" />'
|
||||
build.methods = {
|
||||
copyCode (e) {
|
||||
wx.showActionSheet({
|
||||
itemList: ['复制代码'],
|
||||
success: () =>
|
||||
wx.setClipboardData({
|
||||
data: e.currentTarget.dataset.content
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.showLanguageName) {
|
||||
build.style = (build.style || '') +
|
||||
`.hl-language {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
text-align: right;
|
||||
top: 3px;
|
||||
}
|
||||
.hl-pre {
|
||||
padding-top: 1.5em;
|
||||
}`
|
||||
}
|
||||
|
||||
if (config.showLineNumber) {
|
||||
build.style = (build.style || '') +
|
||||
`.hl-pre {
|
||||
font-size: 14px;
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
}
|
||||
.line-numbers-rows {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: ${config.showLanguageName ? 1.5 : 1}em;
|
||||
font-size: 100%;
|
||||
left: 0;
|
||||
width: 3em; /* works for line-numbers below 1000 lines */
|
||||
letter-spacing: -1px;
|
||||
border-right: 1px solid #999;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.line-numbers-rows .span {
|
||||
display: block;
|
||||
counter-increment: linenumber;
|
||||
}
|
||||
.line-numbers-rows .span:before {
|
||||
content: counter(linenumber);
|
||||
color: #999;
|
||||
display: block;
|
||||
padding-right: 0.8em;
|
||||
text-align: right;
|
||||
}`
|
||||
}
|
||||
|
||||
module.exports = build
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/* PrismJS 1.22.0
|
||||
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
|
||||
* Based on https://github.com/chriskempson/tomorrow-theme
|
||||
* @author Rose Pritchard
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #ccc;
|
||||
background: none;
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.block-comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.token.tag,
|
||||
.token.attr-name,
|
||||
.token.namespace,
|
||||
.token.deleted {
|
||||
color: #e2777a;
|
||||
}
|
||||
|
||||
.token.function-name {
|
||||
color: #6196cc;
|
||||
}
|
||||
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.function {
|
||||
color: #f08d49;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.class-name,
|
||||
.token.constant,
|
||||
.token.symbol {
|
||||
color: #f8c555;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.important,
|
||||
.token.atrule,
|
||||
.token.keyword,
|
||||
.token.builtin {
|
||||
color: #cc99cd;
|
||||
}
|
||||
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.attr-value,
|
||||
.token.regex,
|
||||
.token.variable {
|
||||
color: #7ec699;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url {
|
||||
color: #67cdcc;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: green;
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*! PrismJS 1.22.0
|
||||
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript */
|
||||
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,r){var a,n;switch(r=r||{},M.util.type(e)){case"Object":if(n=M.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case"Array":return n=M.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e&&!c.test(e.className);)e=e.parentElement;return e?(e.className.match(c)||[,"none"])[1].toLowerCase():"none"},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName("script");for(var r in t)if(t[r].src==n)return t[r]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{extend:function(e,n){var t=M.util.clone(M.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||M.languages)[t],i={};for(var l in a)if(a.hasOwnProperty(l)){if(l==e)for(var o in n)n.hasOwnProperty(o)&&(i[o]=n[o]);n.hasOwnProperty(l)||(i[l]=a[l])}var s=r[t];return r[t]=i,M.languages.DFS(M.languages,function(e,n){n===s&&e!=t&&(this[e]=i)}),i},DFS:function e(n,t,r,a){a=a||{};var i=M.util.objId;for(var l in n)if(n.hasOwnProperty(l)){t.call(n,l,n[l],r||l);var o=n[l],s=M.util.type(o);"Object"!==s||a[i(o)]?"Array"!==s||a[i(o)]||(a[i(o)]=!0,e(o,t,l,a)):(a[i(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){M.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};M.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),M.hooks.run("before-all-elements-highlight",r);for(var a,i=0;a=r.elements[i++];)M.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=M.util.getLanguage(e),a=M.languages[r];e.className=e.className.replace(c,"").replace(/\s+/g," ")+" language-"+r;var i=e.parentElement;i&&"pre"===i.nodeName.toLowerCase()&&(i.className=i.className.replace(c,"").replace(/\s+/g," ")+" language-"+r);var l={element:e,language:r,grammar:a,code:e.textContent};function o(e){l.highlightedCode=e,M.hooks.run("before-insert",l),l.element.innerHTML=l.highlightedCode,M.hooks.run("after-highlight",l),M.hooks.run("complete",l),t&&t.call(l.element)}if(M.hooks.run("before-sanity-check",l),!l.code)return M.hooks.run("complete",l),void(t&&t.call(l.element));if(M.hooks.run("before-highlight",l),l.grammar)if(n&&u.Worker){var s=new Worker(M.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:l.language,code:l.code,immediateClose:!0}))}else o(M.highlight(l.code,l.grammar,l.language));else o(M.util.encode(l.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};return M.hooks.run("before-tokenize",r),r.tokens=M.tokenize(r.code,r.grammar),M.hooks.run("after-tokenize",r),W.stringify(M.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new i;return I(a,a.head,e),function e(n,t,r,a,i,l){for(var o in r)if(r.hasOwnProperty(o)&&r[o]){var s=r[o];s=Array.isArray(s)?s:[s];for(var u=0;u<s.length;++u){if(l&&l.cause==o+","+u)return;var c=s[u],g=c.inside,f=!!c.lookbehind,h=!!c.greedy,d=0,v=c.alias;if(h&&!c.pattern.global){var p=c.pattern.toString().match(/[imsuy]*$/)[0];c.pattern=RegExp(c.pattern.source,p+"g")}for(var m=c.pattern||c,y=a.next,k=i;y!==t.tail&&!(l&&k>=l.reach);k+=y.value.length,y=y.next){var b=y.value;if(t.length>n.length)return;if(!(b instanceof W)){var x=1;if(h&&y!=t.tail.prev){m.lastIndex=k;var w=m.exec(n);if(!w)break;var A=w.index+(f&&w[1]?w[1].length:0),P=w.index+w[0].length,S=k;for(S+=y.value.length;S<=A;)y=y.next,S+=y.value.length;if(S-=y.value.length,k=S,y.value instanceof W)continue;for(var E=y;E!==t.tail&&(S<P||"string"==typeof E.value);E=E.next)x++,S+=E.value.length;x--,b=n.slice(k,S),w.index-=k}else{m.lastIndex=0;var w=m.exec(b)}if(w){f&&(d=w[1]?w[1].length:0);var A=w.index+d,O=w[0].slice(d),P=A+O.length,L=b.slice(0,A),N=b.slice(P),j=k+b.length;l&&j>l.reach&&(l.reach=j);var C=y.prev;L&&(C=I(t,C,L),k+=L.lengthLine truncated
|
||||
Prism.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var n={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[^]*?>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
|
||||
!function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+t.source+")*?(?=\\s*\\{)"),string:{pattern:t,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var s=e.languages.markup;s&&(s.tag.addInlined("style","css"),e.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/(^|["'\s])style\s*=\s*(?:"[^"]*"|'[^']*')/i,lookbehind:!0,inside:{"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{style:{pattern:/(["'])[\s\S]+(?=["']$)/,lookbehind:!0,alias:"language-css",inside:e.languages.css},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},"attr-name":/^style/i}}},s.tag))}(Prism);
|
||||
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
|
||||
Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-flags":/[a-z]+$/,"regex-delimiter":/^\/|\/$/}},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript;
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
const config = require('../config')
|
||||
|
||||
const build = {
|
||||
import: 'prism.css',
|
||||
handler (file) {
|
||||
if (file.path.includes('prism.css')) {
|
||||
// 将标签名选择器和属性选择器转为 class 选择器(组件内仅支持 class 选择器)
|
||||
file.contents = Buffer.from(file.contents.toString().replace(/pre([[)])/g, '.hl-pre$1').replace(/code/g, '.hl-code').replace(/\[class\*="?language-"?\]/g, '').replace(/:not[^,}]+[,}]*/g, '').replace(/\.token\./g, '.hl-'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.showLanguageName || config.showLineNumber) {
|
||||
// pre 内部的 code 进行滚动,避免行号和语言名称跟随滚动
|
||||
build.style = `.hl-pre {
|
||||
position: relative;
|
||||
}
|
||||
.hl-code {
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}`
|
||||
}
|
||||
|
||||
if (config.copyByLongPress) {
|
||||
build.template = '<rich-text v-if="n.attrs&&n.attrs[\'data-content\']" :nodes="[n]" :data-content="n.attrs[\'data-content\']" :data-lang="n.attrs[\'data-lang\']" @longpress="copyCode" />'
|
||||
build.methods = {
|
||||
copyCode (e) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['复制代码'],
|
||||
success: () =>
|
||||
uni.setClipboardData({
|
||||
data: e.currentTarget.dataset.content
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.showLanguageName) {
|
||||
build.style = (build.style || '') +
|
||||
`.hl-language {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
text-align: right;
|
||||
top: 3px;
|
||||
}
|
||||
.hl-pre {
|
||||
padding-top: 1.5em;
|
||||
}`
|
||||
}
|
||||
|
||||
if (config.showLineNumber) {
|
||||
build.style = (build.style || '') +
|
||||
`.hl-pre {
|
||||
font-size: 14px;
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
}
|
||||
.line-numbers-rows {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: ${config.showLanguageName ? 1.5 : 1}em;
|
||||
font-size: 100%;
|
||||
left: 0;
|
||||
width: 3em; /* works for line-numbers below 1000 lines */
|
||||
letter-spacing: -1px;
|
||||
border-right: 1px solid #999;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.line-numbers-rows .span {
|
||||
display: block;
|
||||
counter-increment: linenumber;
|
||||
}
|
||||
.line-numbers-rows .span:before {
|
||||
content: counter(linenumber);
|
||||
color: #999;
|
||||
display: block;
|
||||
padding-right: 0.8em;
|
||||
text-align: right;
|
||||
}`
|
||||
}
|
||||
|
||||
module.exports = build
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
功能:图片本地缓存
|
||||
大小:*≈4KB*
|
||||
作者:[@PentaTea](https://github.com/PentaTea)
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| | | | | | √(仅支持 app 的 vue 页面) |
|
||||
|
||||
说明:
|
||||
引入本插件后,会给组件添加一个 *img-cache* 属性,将该属性设置为 *true* 后,将自动下载引用的图片并将 *src* 属性更换为本地地址
|
||||
同时在组件实例上挂载了 *imgCache* 对象,扩充缓存控制能力
|
||||
|
||||
*imgCache* 对象属性和方法:
|
||||
|
||||
| 属性 | 功能 |
|
||||
|:---:|:---:|
|
||||
| list | 当前缓存的 url 列表 |
|
||||
| get(url) | 传入 url 获得本地地址 |
|
||||
| delete(url) | 传入 url 删除缓存记录 |
|
||||
| add(url) | 传入 url 并下载目标为缓存 |
|
||||
| clear() | 清空所有缓存 |
|
||||
|
||||
!> 请尽量确保 *src* 中含有文件后缀名,不以后缀结尾也没关系,插件会从路径中推测合理的图片后缀,如果完全不包含后缀信息可能会无法保存到相册
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
main: 'index.js',
|
||||
platform: ['uni-app'],
|
||||
handler (file, platform) {
|
||||
if (platform === 'uni-app') {
|
||||
// 添加 img-cache 属性
|
||||
if (file.path.includes('mp-html.vue')) {
|
||||
file.contents = Buffer.from(
|
||||
file.contents
|
||||
.toString()
|
||||
.replace(/props\s*:\s*{/, 'props: {\n ImgCache: Boolean,')
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
const data = {
|
||||
name: 'imgcache',
|
||||
prefix: 'imgcache_'
|
||||
}
|
||||
function ImgCache (vm) {
|
||||
this.vm = vm // 保存实例在其他周期使用
|
||||
this.i = 0 // 用于标记第几张图
|
||||
vm.imgCache = {
|
||||
get list () {
|
||||
return uni
|
||||
.getStorageInfoSync()
|
||||
.keys.filter((key) => key.startsWith(data.prefix))
|
||||
.map((key) => key.split(data.prefix)[1])
|
||||
},
|
||||
get (url) {
|
||||
return uni.getStorageSync(data.prefix + url)
|
||||
},
|
||||
delete (url) {
|
||||
const path = uni.getStorageSync(data.prefix + url)
|
||||
if (!path) return false
|
||||
plus.io.resolveLocalFileSystemURL(path, (entry) => {
|
||||
entry.remove()
|
||||
})
|
||||
uni.removeStorageSync(data.prefix + url)
|
||||
return true
|
||||
},
|
||||
async add (url) {
|
||||
const filename = await download(url)
|
||||
if (filename) {
|
||||
uni.setStorageSync(data.prefix + url, filename)
|
||||
return 'file://' + plus.io.convertLocalFileSystemURL(filename)
|
||||
}
|
||||
return null
|
||||
},
|
||||
clear () {
|
||||
uni
|
||||
.getStorageInfoSync()
|
||||
.keys.filter((key) => key.startsWith(data.prefix))
|
||||
.forEach((key) => {
|
||||
uni.removeStorageSync(key)
|
||||
})
|
||||
|
||||
plus.io.resolveLocalFileSystemURL(`_doc/${data.name}/`, (entry) => {
|
||||
entry.removeRecursively(
|
||||
(entry) => {
|
||||
console.log(`${data.name}缓存删除成功`, entry)
|
||||
},
|
||||
(e) => {
|
||||
console.log(`${data.name}缓存删除失败`, e)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
ImgCache.prototype.onParse = function (node, parser) {
|
||||
// 启用本插件 && 解析图片标签 && 拥有src属性 && 是网络图片
|
||||
if (
|
||||
this.vm.ImgCache &&
|
||||
node.name === 'img' &&
|
||||
node.attrs.src &&
|
||||
/^https?:\/\//.test(node.attrs.src)
|
||||
) {
|
||||
const src = node.attrs.src
|
||||
node.attrs.src = ''
|
||||
node.attrs.i = this.vm.imgList.length + this.i++
|
||||
parser.expose()
|
||||
|
||||
async function getUrl (path) {
|
||||
if (await resolveFile(path)) return path
|
||||
const filename = await download(src)
|
||||
filename && uni.setStorageSync(data.prefix + src, filename)
|
||||
return filename
|
||||
}
|
||||
|
||||
uni.getStorage({
|
||||
key: data.prefix + src,
|
||||
success: async (res) => {
|
||||
const path = await getUrl(res.data)
|
||||
const url = path
|
||||
? 'file://' + plus.io.convertLocalFileSystemURL(path)
|
||||
: src
|
||||
node.attrs.src = url
|
||||
this.vm.imgList[node.attrs.i] = path || src
|
||||
},
|
||||
fail: async () => {
|
||||
const path = await getUrl()
|
||||
const url = path
|
||||
? 'file://' + plus.io.convertLocalFileSystemURL(path)
|
||||
: src
|
||||
node.attrs.src = url
|
||||
this.vm.imgList[node.attrs.i] = path || src
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const taskQueue = new Set()
|
||||
|
||||
function download (url) {
|
||||
return new Promise((resolve) => {
|
||||
if (taskQueue.has(url)) return
|
||||
taskQueue.add(url)
|
||||
const suffix = /.+\.(jpg|jpeg|png|bmp|gif|webp)/.exec(url)
|
||||
const name = `${makeid(8)}_${Date.now()}${suffix ? '.' + suffix[1] : ''}`
|
||||
const task = plus.downloader.createDownload(
|
||||
url,
|
||||
{ filename: `_doc/${data.name}/${name}` },
|
||||
(download, status) => {
|
||||
taskQueue.delete(url)
|
||||
resolve(status === 200 ? download.filename : null)
|
||||
}
|
||||
)
|
||||
task.start()
|
||||
})
|
||||
}
|
||||
|
||||
// 判断文件存在
|
||||
function resolveFile (url) {
|
||||
return new Promise((resolve) => {
|
||||
plus.io.resolveLocalFileSystemURL(url, resolve, () => resolve(null))
|
||||
})
|
||||
}
|
||||
|
||||
// 生成uuid
|
||||
function makeid (length) {
|
||||
let result = ''
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
// #endif
|
||||
|
||||
module.exports = ImgCache
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# latex
|
||||
功能:渲染 *latex* 公式
|
||||
大小:**≈300KB**
|
||||
作者:[@Zeng-J](https://github.com/Zeng-J)
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √ |
|
||||
|
||||
说明:
|
||||
引入本插件后,会将 *$xxx$* 的文本内容按照 *latex* 规则进行解析和渲染
|
||||
|
||||
> 与 *editable* 插件共用时,编辑状态下,公式不会渲染,可以直接修改公式文本
|
||||
|
||||
> 本插件通过 [katex-mini](https://github.com/rojer95/katex-mini) 解析 *latex* 文本,[字体文件](https://github.com/KaTeX/KaTeX/tree/main/fonts) 建议自行转存
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
import: 'katex.css',
|
||||
handler (file) {
|
||||
if (file.isBuffer()) {
|
||||
let content = file.contents.toString()
|
||||
if (file.basename === 'node.wxml') {
|
||||
content = content.replace(/(n.?)\.name==='a'\|\|/g, "$1.name==='a'||$1.l||")
|
||||
} else if (file.basename === 'node.vue') {
|
||||
content = content.replace(/!handler.isInline\((.*?)\)/, '(n.l||!handler.isInline($1))')
|
||||
}
|
||||
file.contents = Buffer.from(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @fileoverview latex 插件
|
||||
* katex.min.js来源 https://github.com/rojer95/katex-mini
|
||||
*/
|
||||
const parse = require('./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
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Latex
|
||||
+1070
File diff suppressed because it is too large.
Load diff
+1
@@ -0,0 +1 @@
|
||||
function t(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(n,a,function(t){return e[t]}.bind(null,a));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";var n=this&&this.__assign||function(){return(n=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var a in t=arguments[r])Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e}).apply(this,arguments)},a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.createClass=void 0;var i=a(r(1));r(2);var o=/([A-Z])/g,s={"&":"&",">":">","<":"<",'"':""","'":"'"},l=/[&><"']/g;function h(e){return String(e).replace(l,(function(e){return s[e]}))}var c=function(e){return"data:image/svg+xml,"+encodeURIComponent(e.replace(/\s+/g," "))};t.createClass=function(e){var t;return null!==(t=null==e?void 0:e.filter((function(e){return e})).join(" "))&&void 0!==t?t:""};var m=function(e,r){return e.map((function(e){var n,a=r;(null===(n=null==e?void 0:e.style)||void 0===n?void 0:n.color)&&(a=e.style.color);var s=void 0;e instanceof i.default.__domTree.Span&&(s="span"),e instanceof i.default.__domTree.Anchor&&(s="anchor"),e instanceof i.default.__domTree.LineNode&&(s="line"),e instanceof i.default.__domTree.PathNode&&(s="path"),e instanceof i.default.__domTree.SvgNode&&(s="svg",a&&(e.attributes.fill=a)),e instanceof i.default.__domTree.SymbolNode&&(s="text");var l=e.children&&e.children.length>0?m(e.children,a):[];return s?function(e,r,n){var a=!1;r.classes&&r.classes.length>0&&(a=!0);var i,s=h((0,t.createClass)(r.classes)),l="";for(var m in"text"===e&&r.italic>0&&(l+="margin-right:"+r.italic+"em;"),r.style)r.style.hasOwnProperty(m)&&(l+="".concat((i=m,i.replace(o,"-$1").toLowerCase()),":").concat(r.style[m],";"));l&&(a=!0);var u={};for(var p in r.attributes)r.attributes.hasOwnProperty(p)&&(u[p]=h(r.attributes[p]));if("span"===e)return{name:"span",attrs:{class:s+" katex-span",style:l},children:n};if("text"===e){var d=h(r.text);return a?{name:"span",attrs:{class:s,style:l},children:[{type:"text",text:d}]}:{type:"text",text:d}}if("svg"===e){var f=r.toMarkup();return{name:"img",attrs:{src:c(f),class:"katex-svg"}}}return null}(s,e,l):l})).reduce((function(e,t){return Array.isArray(t)?e.push.apply(e,t):e.push(t),e}),[]).filter((function(e){return!!e}))};t.default=function(e,t){void 0===t&&(t={});try{var r=i.default.__renderToDomTree(e,n(n({},t),{output:"html"}));return m([r])}catch(e){return[{name:"span",attrs:{style:"color:red;"},children:[{type:"text",text:e.message}]}]}}},function(e,t,r){var n;"undefined"!=typeof self&&self,n=function(){return function(){"use strict";var e={d:function(t,r){for(var n in r)e.o(r,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:r[n]})},o:function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}},t={};e.d(t,{default:function(){return Fn}});var r=function e(t,r){this.position=void 0;var n,a="KaTeX parse error: "+t,i=r&&r.loc;if(i&&i.start<=i.end){var o=i.lexer.input;n=i.start;var s=i.end;n===o.length?a+=" at end of input: ":a+=" at position "+(n+1)+": ";var l=o.slice(n,s).replace(/[^]/g,"$&̲");a+=(n>15?"…"+o.slice(n-15,n):o.slice(0,n))+l+(s+15<o.length?o.slice(s,s+15)+"…":o.slice(s))}var h=new Error(a);return h.name="ParseError",h.__proto__=e.prototype,h.position=n,h};r.prototype.__proto__=Error.prototype;var n=r,a=/([A-Z])/g,i={"&":"&",">":">","<":"<",'"':""","'":"'"},o=/[&><"']/g,s=function e(t){return"ordgroup"===t.type||"color"===t.type?1===t.body.length?e(t.body[0]):t:"font"===t.type?e(t.body):t},l={contains:function(e,t){return-1!==e.indexOf(t)},deflt:function(e,t){return void 0===e?t:e},escape:function(e){return String(e).replace(o,(function(e){return i[e]}))},hyphenate:function(e){return e.replace(a,"-$1").toLowerCase()},getBaseElem:s,isCharacterBox:function(e){var t=s(e);return"mathord"===t.type||"textord"===t.type||"atom"===t.type},protocolFromUrl:function(e){var t=/^\s*([^\\/#]*?)(?::|�*58|�*3a)/i.exec(e);return null!=t?t[1]:"_relative"}},h={displayMode:{type:"boolean",description:"Render math in display mode, which puts the math in display style (so \\int and \\sum are large,
Line truncated
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# markdown
|
||||
功能:渲染 *markdown*
|
||||
大小:*≈37KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √ |
|
||||
|
||||
说明:
|
||||
引入本插件后,会给组件添加一个 *markdown* 属性,将该属性设置为 *true* 后,即可通过 *content* 属性或 *setContent* 方法设置 *markdown* 内容即可
|
||||
|
||||
若开启 *use-anchor* 属性,所有标题 `*# xxx*` 都会被设置为锚点,通过链接 `[xxx](#xxx)` 可以直接跳转
|
||||
|
||||
> 本插件通过 [marked](https://github.com/markedjs/marked) 解析 *markdown* 文本,部分 *css* 摘选自 [github-markdown-css](https://github.com/sindresorhus/github-markdown-css)
|
||||
|
||||
> 本插件可以和 *highlight* 插件共用,实现 *markdown* 中代码块的高亮效果
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @fileoverview markdown 插件
|
||||
* Include marked (https://github.com/markedjs/marked)
|
||||
* Include github-markdown-css (https://github.com/sindresorhus/github-markdown-css)
|
||||
*/
|
||||
const { marked } = require('./marked.min')
|
||||
let index = 0
|
||||
|
||||
function Markdown (vm) {
|
||||
this.vm = vm
|
||||
vm._ids = {}
|
||||
}
|
||||
|
||||
Markdown.prototype.onUpdate = function (content) {
|
||||
if (this.vm.properties.markdown) {
|
||||
// 解决中文标点符号后粗体失效的问题,增加零宽空格
|
||||
content = content.replace(/\*\*([^*]+)\*\*([,。!?;:])/g, '**$1**​$2')
|
||||
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 || ''}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Markdown
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* marked v16.3.0 - a markdown parser
|
||||
* Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/markedjs/marked
|
||||
*/
|
||||
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE
|
||||
* The code in this file is generated from files in ./src/
|
||||
*/
|
||||
|
||||
function L(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var O=L();function G(l){O=l}var E={exec:()=>null};function h(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(r,i)=>{let s=typeof i=="string"?i:i.source;return s=s.replace(m.caret,"$1"),t=t.replace(r,s),n},getRegex:()=>new RegExp(t,e)};return n}var m={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i")},xe=/^(?:[ \t]*(?:\n|$))+/,be=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Re=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,C=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,Oe=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,j=/(?:[*+-]|\d{1,9}[.)])/,se=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,ie=h(se).replace(/bull/g,j).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Te=h(se).replace(/bull/g,j).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),F=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,we=/^[^\n]+/,Q=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,ye=h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",Q).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Pe=h(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,j).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",U=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,Se=h("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",U).replace("tag",v).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),oe=h(F).replace("hr",C).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replLine truncated
|
||||
]`).replace("lheading",ie).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},Me=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ze=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,ae=/^( {2,}|\\)\n(?!\s*$)/,Ae=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,D=/[\p{P}\p{S}]/u,W=/[\s\p{P}\p{S}]/u,le=/[^\s\p{P}\p{S}]/u,Ee=h(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,W).getRegex(),ue=/(?!~)[\p{P}\p{S}]/u,Ce=/(?!~)[\s\p{P}\p{S}]/u,Ie=/(?:[^\s\p{P}\p{S}]|~)/u,Be=/\[[^\[\]]*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)|`[^`]*?`|<(?! )[^<>]*?>/g,pe=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,qe=h(pe,"u").replace(/punct/g,D).getRegex(),ve=h(pe,"u").replace(/punct/g,ue).getRegex(),ce="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",De=h(ce,"gu").replace(/notPunctSpace/g,le).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),He=h(ce,"gu").replace(/notPunctSpace/g,Ie).replace(/punctSpace/g,Ce).replace(/punct/g,ue).getRegex(),Ze=h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,le).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Ge=h(/\\(punct)/,"gu").replace(/punct/g,D).getRegex(),Ne=h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),je=h(U).replace("(?:-->|$)","-->").getRegex(),Fe=h("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",je).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`[^`]*`|[^\[\]\\`])*?/,Qe=h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),he=h(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",Q).getRegex(),de=h(/^!?\[(ref)\](?:\[\])?/).replace("ref",Q).getRegex(),Ue=h("reflink|nolink(?!\\()","g").replace("reflink",he).replace("nolink",de).getRegex(),X={_backpedal:E,anyPunctuation:Ge,autolink:Ne,blockSkip:Be,br:ae,code:ze,del:E,emStrongLDelim:qe,emStrongRDelimAst:De,emStrongRDelimUnd:Ze,escape:Me,link:Qe,nolink:de,punctuation:Ee,reflink:he,reflinkSearch:Ue,tag:Fe,text:Ae,url:E},Ke={...X,link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},N={...X,emStrongRDelimAst:He,emStrongLDelim:ve,url:h(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},We={...N,br:h(ae).replace("{2,}","*").getRegex(),text:h(N.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},I={normal:K,gfm:_e,pedantic:Le},M={normal:X,gfm:N,breaks:We,pedantic:Ke};var Xe={"&":"&","<":"<",">":">",'"':""","'":"'"},ke=l=>Xe[l];function w(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,ke)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,ke);return l}function J(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function V(l,e){let t=l.replace(m.findPipe,(i,s,o)=>{let a=!1,u=s;for(;--u>=0&&o[u]==="\\";)a=!a;return a?"|":" |"}),n=t.split(m.splitPipe),r=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;r<n.length;r++)n[r]=n[r].trim().replace(m.slashPipe,"|");return n}function z(l,e,t){let n=l.length;if(n===0)return"";let r=0;for(;r<n;){let i=l.charAt(n-r-1);if(i===e&&!t)r++;else if(i!==e&&t)r++;else break}return l.slice(0,n-r)}function ge(l,e){if(l.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n<l.length;n++)if(l[n]==="\\")n++;else if(l[n]===e[0])t++;else if(l[n]===e[1]&&(Line truncated
|
||||
`).map(i=>{let s=i.match(t.other.beginningSpace);if(s===null)return i;let[o]=s;return o.length>=r.length?i.slice(r.length):i}).join(`
|
||||
`)}var y=class{options;rules;lexer;constructor(e){this.options=e||O}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:z(n,`
|
||||
`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],r=Je(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:r}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let r=z(n,"#");(this.options.pedantic||!r||this.rules.other.endingSpaceChar.test(r))&&(n=r.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:z(t[0],`
|
||||
`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=z(t[0],`
|
||||
`).split(`
|
||||
`),r="",i="",s=[];for(;n.length>0;){let o=!1,a=[],u;for(u=0;u<n.length;u++)if(this.rules.other.blockquoteStart.test(n[u]))a.push(n[u]),o=!0;else if(!o)a.push(n[u]);else break;n=n.slice(u);let p=a.join(`
|
||||
`),c=p.replace(this.rules.other.blockquoteSetextReplace,`
|
||||
$1`).replace(this.rules.other.blockquoteSetextReplace2,"");r=r?`${r}
|
||||
${p}`:p,i=i?`${i}
|
||||
${c}`:c;let f=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(c,s,!0),this.lexer.state.top=f,n.length===0)break;let k=s.at(-1);if(k?.type==="code")break;if(k?.type==="blockquote"){let x=k,g=x.raw+`
|
||||
`+n.join(`
|
||||
`),T=this.blockquote(g);s[s.length-1]=T,r=r.substring(0,r.length-x.raw.length)+T.raw,i=i.substring(0,i.length-x.text.length)+T.text;break}else if(k?.type==="list"){let x=k,g=x.raw+`
|
||||
`+n.join(`
|
||||
`),T=this.list(g);s[s.length-1]=T,r=r.substring(0,r.length-k.raw.length)+T.raw,i=i.substring(0,i.length-x.raw.length)+T.raw,n=g.substring(s.at(-1).raw.length).split(`
|
||||
`);continue}}return{type:"blockquote",raw:r,tokens:s,text:i}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim(),r=n.length>1,i={type:"list",raw:"",ordered:r,start:r?+n.slice(0,-1):"",loose:!1,items:[]};n=r?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=r?n:"[*+-]");let s=this.rules.other.listItemRegex(n),o=!1;for(;e;){let u=!1,p="",c="";if(!(t=s.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let f=t[2].split(`
|
||||
`,1)[0].replace(this.rules.other.listReplaceTabs,H=>" ".repeat(3*H.length)),k=e.split(`
|
||||
`,1)[0],x=!f.trim(),g=0;if(this.options.pedantic?(g=2,c=f.trimStart()):x?g=t[1].length+1:(g=t[2].search(this.rules.other.nonSpaceChar),g=g>4?1:g,c=f.slice(g),g+=t[1].length),x&&this.rules.other.blankLine.test(k)&&(p+=k+`
|
||||
`,e=e.substring(k.length+1),u=!0),!u){let H=this.rules.other.nextBulletRegex(g),ee=this.rules.other.hrRegex(g),te=this.rules.other.fencesBeginRegex(g),ne=this.rules.other.headingBeginRegex(g),me=this.rules.other.htmlBeginRegex(g);for(;e;){let Z=e.split(`
|
||||
`,1)[0],A;if(k=Z,this.options.pedantic?(k=k.replace(this.rules.other.listReplaceNesting," "),A=k):A=k.replace(this.rules.other.tabCharGlobal," "),te.test(k)||ne.test(k)||me.test(k)||H.test(k)||ee.test(k))break;if(A.search(this.rules.other.nonSpaceChar)>=g||!k.trim())c+=`
|
||||
`+A.slice(g);else{if(x||f.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||te.test(f)||ne.test(f)||ee.test(f))break;c+=`
|
||||
`+k}!x&&!k.trim()&&(x=!0),p+=Z+`
|
||||
`,e=e.substring(Z.length+1),f=A.slice(g)}}i.loose||(o?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(o=!0));let T=null,Y;this.options.gfm&&(T=this.rules.other.listIsTask.exec(c),T&&(Y=T[0]!=="[ ] ",c=c.replace(this.rules.other.listReplaceTask,""))),i.items.push({type:"list_item",raw:p,task:!!T,checked:Y,loose:!1,text:c,tokens:[]}),i.raw+=p}let a=i.items.at(-1);if(a)a.raw=a.raw.trimEnd(),a.text=a.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let u=0;u<i.items.length;u++)if(this.lexer.state.top=!1,i.items[u].tokens=this.lexer.blockTokens(i.items[u].text,[]),!i.loose){let p=i.items[u].tokens.filter(f=>f.type==="space"),c=p.length>0&&p.some(f=>this.rules.other.anyLine.test(f.raw));i.loose=c}if(i.loose)for(let u=0;u<i.items.length;u++)i.items[u].loose=!0;return i}}html(e){let t=this.rules.block.html.exec(e);if(t)return{type:"html",block:!0,raw:t[0],pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:t[0]}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),r=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",i=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:n,raw:t[0],href:r,title:i}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=V(t[1]),r=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),i=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(`
|
||||
`):[],s={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===r.length){for(let o of r)this.rules.other.tableAlignRight.test(o)?s.align.push("right"):this.rules.other.tableAlignCenter.test(o)?s.align.push("center"):this.rules.other.tableAlignLeft.test(o)?s.align.push("left"):s.align.push(null);for(let o=0;o<n.length;o++)s.header.push({text:n[o],tokens:this.lexer.inline(n[o]),header:!0,align:s.align[o]});for(let o of i)s.rows.push(V(o,s.header.length).map((a,u)=>({text:a,tokens:this.lexer.inline(a),header:!1,align:s.align[u]})));return s}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===`
|
||||
`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let s=z(n.slice(0,-1),"\\");if((n.length-s.length)%2===0)return}else{let s=ge(t[2],"()");if(s===-2)return;if(s>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+s;t[2]=t[2].substring(0,s),t[0]=t[0].substring(0,a).trim(),t[3]=""}}let r=t[2],i="";if(this.options.pedantic){let s=this.rules.other.pedanticHrefTitle.exec(r);s&&(r=s[1],i=s[3])}else i=t[3]?t[3].slice(1,-1):"";return r=r.trim(),this.rules.other.startAngleBracket.test(r)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?r=r.slice(1):r=r.slice(1,-1)),fe(t,{href:r&&r.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let r=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[r.toLowerCase()];if(!i){let s=n[0].charAt(0);return{type:"text",raw:s,text:s}}return fe(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let r=this.rules.inline.emStrongLDelim.exec(e);if(!r||r[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(r[1]||r[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let s=[...r[0]].length-1,o,a,u=s,p=0,c=r[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(c.lastIndex=0,t=t.slice(-1*e.length+s);(r=c.exec(t))!=null;){if(o=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!o)continue;if(a=[...o].length,r[3]||r[4]){u+=a;continue}else if((r[5]||r[6])&&s%3&&!((s+a)%3)){p+=a;continue}if(u-=a,u>0)continue;a=Math.min(a,a+u+p);let f=[...r[0]][0].length,k=e.slice(0,s+r.index+f+a);if(Math.min(s,a)%2){let g=k.slice(1,-1);return{type:"em",raw:k,text:g,tokens:this.lexer.inlineTokens(g)}}let x=k.slice(2,-2);return{type:"strong",raw:k,text:x,tokens:this.lexer.inlineTokens(x)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),r=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return r&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,r;return t[2]==="@"?(n=t[1],r="mailto:"+n):(n=t[1],r=n),{type:"link",raw:t[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,r;if(t[2]==="@")n=t[0],r="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?r="http://"+t[0]:r=t[0]}return{type:"link",raw:t[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var b=class l{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||O,this.options.tokenizer=this.options.tokenizer||new y,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:I.normal,inline:M.normal};this.options.pedantic?(t.block=I.pedantic,t.inline=M.pedantic):this.options.gfm&&(t.block=I.gfm,this.options.breaks?t.inline=M.breaks:t.inline=M.gfm),this.tokenizer.rules=t}static get rules(){return{block:I,inline:M}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,`
|
||||
`),this.blockTokens(e,this.tokens);for(let t=0;t<this.inlineQueue.length;t++){let n=this.inlineQueue[t];this.inlineTokens(n.src,n.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=!1){for(this.options.pedantic&&(e=e.replace(m.tabCharGlobal," ").replace(m.spaceLine,""));e;){let r;if(this.options.extensions?.block?.some(s=>(r=s.call({lexer:this},e,t))?(e=e.substring(r.raw.length),t.push(r),!0):!1))continue;if(r=this.tokenizer.space(e)){e=e.substring(r.raw.length);let s=t.at(-1);r.raw.length===1&&s!==void 0?s.raw+=`
|
||||
`:t.push(r);continue}if(r=this.tokenizer.code(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="paragraph"||s?.type==="text"?(s.raw+=(s.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+r.raw,s.text+=`
|
||||
`+r.text,this.inlineQueue.at(-1).src=s.text):t.push(r);continue}if(r=this.tokenizer.fences(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.heading(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.hr(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.blockquote(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.list(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.html(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.def(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="paragraph"||s?.type==="text"?(s.raw+=(s.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+r.raw,s.text+=`
|
||||
`+r.raw,this.inlineQueue.at(-1).src=s.text):this.tokens.links[r.tag]||(this.tokens.links[r.tag]={href:r.href,title:r.title},t.push(r));continue}if(r=this.tokenizer.table(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.lheading(e)){e=e.substring(r.raw.length),t.push(r);continue}let i=e;if(this.options.extensions?.startBlock){let s=1/0,o=e.slice(1),a;this.options.extensions.startBlock.forEach(u=>{a=u.call({lexer:this},o),typeof a=="number"&&a>=0&&(s=Math.min(s,a))}),s<1/0&&s>=0&&(i=e.substring(0,s+1))}if(this.state.top&&(r=this.tokenizer.paragraph(i))){let s=t.at(-1);n&&s?.type==="paragraph"?(s.raw+=(s.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+r.raw,s.text+=`
|
||||
`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=s.text):t.push(r),n=i.length!==e.length,e=e.substring(r.raw.length);continue}if(r=this.tokenizer.text(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="text"?(s.raw+=(s.raw.endsWith(`
|
||||
`)?"":`
|
||||
`)+r.raw,s.text+=`
|
||||
`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=s.text):t.push(r);continue}if(e){let s="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(s);break}else throw new Error(s)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,r=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(r=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(r[0].slice(r[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,r.index)+"["+"a".repeat(r[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(r=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,r.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;(r=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)n=n.slice(0,r.index)+"["+"a".repeat(r[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);n=this.options.hooks?.emStrongMask?.call({lexer:this},n)??n;let i=!1,s="";for(;e;){i||(s=""),i=!1;let o;if(this.options.extensions?.inline?.some(u=>(o=u.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),!0):!1))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let u=t.at(-1);o.type==="text"&&u?.type==="text"?(u.raw+=o.raw,u.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,s)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let a=e;if(this.options.extensions?.startInline){let u=1/0,p=e.slice(1),c;this.options.extensions.startInline.forEach(f=>{c=f.call({lexer:this},p),typeof c=="number"&&c>=0&&(u=Math.min(u,c))}),u<1/0&&u>=0&&(a=e.substring(0,u+1))}if(o=this.tokenizer.inlineText(a)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(s=o.raw.slice(-1)),i=!0;let u=t.at(-1);u?.type==="text"?(u.raw+=o.raw,u.text+=o.text):t.push(o);continue}if(e){let u="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(u);break}else throw new Error(u)}}return t}};var P=class{options;parser;constructor(e){this.options=e||O}space(e){return""}code({text:e,lang:t,escaped:n}){let r=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+`
|
||||
`;return r?'<pre><code class="language-'+w(r)+'">'+(n?i:w(i,!0))+`</code></pre>
|
||||
`:"<pre><code>"+(n?i:w(i,!0))+`</code></pre>
|
||||
`}blockquote({tokens:e}){return`<blockquote>
|
||||
${this.parser.parse(e)}</blockquote>
|
||||
`}html({text:e}){return e}def(e){return""}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}>
|
||||
`}hr(e){return`<hr>
|
||||
`}list(e){let t=e.ordered,n=e.start,r="";for(let o=0;o<e.items.length;o++){let a=e.items[o];r+=this.listitem(a)}let i=t?"ol":"ul",s=t&&n!==1?' start="'+n+'"':"";return"<"+i+s+`>
|
||||
`+r+"</"+i+`>
|
||||
`}listitem(e){let t="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens[0]?.type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+w(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li>
|
||||
`}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p>
|
||||
`}table(e){let t="",n="";for(let i=0;i<e.header.length;i++)n+=this.tablecell(e.header[i]);t+=this.tablerow({text:n});let r="";for(let i=0;i<e.rows.length;i++){let s=e.rows[i];n="";for(let o=0;o<s.length;o++)n+=this.tablecell(s[o]);r+=this.tablerow({text:n})}return r&&(r=`<tbody>${r}</tbody>`),`<table>
|
||||
<thead>
|
||||
`+t+`</thead>
|
||||
`+r+`</table>
|
||||
`}tablerow({text:e}){return`<tr>
|
||||
${e}</tr>
|
||||
`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}>
|
||||
`}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${w(e,!0)}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){let r=this.parser.parseInline(n),i=J(e);if(i===null)return r;e=i;let s='<a href="'+e+'"';return t&&(s+=' title="'+w(t)+'"'),s+=">"+r+"</a>",s}image({href:e,title:t,text:n,tokens:r}){r&&(n=this.parser.parseInline(r,this.parser.textRenderer));let i=J(e);if(i===null)return w(n);e=i;let s=`<img src="${e}" alt="${n}"`;return t&&(s+=` title="${w(t)}"`),s+=">",s}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:w(e.text)}};var $=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}};var R=class l{options;renderer;textRenderer;constructor(e){this.options=e||O,this.options.renderer=this.options.renderer||new P,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new $}static parse(e,t){return new l(t).parse(e)}static parseInline(e,t){return new l(t).parseInline(e)}parse(e,t=!0){let n="";for(let r=0;r<e.length;r++){let i=e[r];if(this.options.extensions?.renderers?.[i.type]){let o=i,a=this.options.extensions.renderers[o.type].call({parser:this},o);if(a!==!1||!["space","hr","heading","code","table","blockquote","list","html","def","paragraph","text"].includes(o.type)){n+=a||"";continue}}let s=i;switch(s.type){case"space":{n+=this.renderer.space(s);continue}case"hr":{n+=this.renderer.hr(s);continue}case"heading":{n+=this.renderer.heading(s);continue}case"code":{n+=this.renderer.code(s);continue}case"table":{n+=this.renderer.table(s);continue}case"blockquote":{n+=this.renderer.blockquote(s);continue}case"list":{n+=this.renderer.list(s);continue}case"html":{n+=this.renderer.html(s);continue}case"def":{n+=this.renderer.def(s);continue}case"paragraph":{n+=this.renderer.paragraph(s);continue}case"text":{let o=s,a=this.renderer.text(o);for(;r+1<e.length&&e[r+1].type==="text";)o=e[++r],a+=`
|
||||
`+this.renderer.text(o);t?n+=this.renderer.paragraph({type:"paragraph",raw:a,text:a,tokens:[{type:"text",raw:a,text:a,escaped:!0}]}):n+=a;continue}default:{let o='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}parseInline(e,t=this.renderer){let n="";for(let r=0;r<e.length;r++){let i=e[r];if(this.options.extensions?.renderers?.[i.type]){let o=this.options.extensions.renderers[i.type].call({parser:this},i);if(o!==!1||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(i.type)){n+=o||"";continue}}let s=i;switch(s.type){case"escape":{n+=t.text(s);break}case"html":{n+=t.html(s);break}case"link":{n+=t.link(s);break}case"image":{n+=t.image(s);break}case"strong":{n+=t.strong(s);break}case"em":{n+=t.em(s);break}case"codespan":{n+=t.codespan(s);break}case"br":{n+=t.br(s);break}case"del":{n+=t.del(s);break}case"text":{n+=t.text(s);break}default:{let o='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}};var S=class{options;block;constructor(e){this.options=e||O}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens","emStrongMask"]);static passThroughHooksRespectAsync=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}emStrongMask(e){return e}provideLexer(){return this.block?b.lex:b.lexInline}provideParser(){return this.block?R.parse:R.parseInline}};var B=class{defaults=L();options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=R;Renderer=P;TextRenderer=$;Lexer=b;Tokenizer=y;Hooks=S;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(let r of e)switch(n=n.concat(t.call(this,r)),r.type){case"table":{let i=r;for(let s of i.header)n=n.concat(this.walkTokens(s.tokens,t));for(let s of i.rows)for(let o of s)n=n.concat(this.walkTokens(o.tokens,t));break}case"list":{let i=r;n=n.concat(this.walkTokens(i.items,t));break}default:{let i=r;this.defaults.extensions?.childTokens?.[i.type]?this.defaults.extensions.childTokens[i.type].forEach(s=>{let o=i[s].flat(1/0);n=n.concat(this.walkTokens(o,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let r={...n};if(r.async=this.defaults.async||r.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let s=t.renderers[i.name];s?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===!1&&(a=s.apply(this,o)),a}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let s=t[i.level];s?s.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),r.extensions=t),n.renderer){let i=this.defaults.renderer||new P(this.defaults);for(let s in n.renderer){if(!(s in i))throw new Error(`renderer '${s}' does not exist`);if(["options","parser"].includes(s))continue;let o=s,a=n.renderer[o],u=i[o];i[o]=(...p)=>{let c=a.apply(i,p);return c===!1&&(c=u.apply(i,p)),c||""}}r.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new y(this.defaults);for(let s in n.tokenizer){if(!(s in i))throw new Error(`tokenizer '${s}' does not exist`);if(["options","rules","lexer"].includes(s))continue;let o=s,a=n.tokenizer[o],u=i[o];i[o]=(...p)=>{let c=a.apply(i,p);return c===!1&&(c=u.apply(i,p)),c}}r.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new S;for(let s in n.hooks){if(!(s in i))throw new Error(`hook '${s}' does not exist`);if(["options","block"].includes(s))continue;let o=s,a=n.hooks[o],u=i[o];S.passThroughHooks.has(s)?i[o]=p=>{if(this.defaults.async&&S.passThroughHooksRespectAsync.has(s))return Promise.resolve(a.call(i,p)).then(f=>u.call(i,f));let c=a.call(i,p);return u.call(i,c)}:i[o]=(...p)=>{let c=a.apply(i,p);return c===!1&&(c=u.apply(i,p)),c}}r.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,s=n.walkTokens;r.walkTokens=function(o){let a=[];return a.push(s.call(this,o)),i&&(a=a.concat(i.call(this,o))),a}}this.defaults={...this.defaults,...r}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return b.lex(e,t??this.defaults)}parser(e,t){return R.parse(e,t??this.defaults)}parseMarkdown(e){return(n,r)=>{let i={...r},s={...this.defaults,...i},o=this.onError(!!s.silent,!!s.async);if(this.defaults.async===!0&&i.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===n
Line truncated
|
||||
Please report this to https://github.com/markedjs/marked.`,e){let r="<p>An error occurred:</p><pre>"+w(n.message+"",!0)+"</pre>";return t?Promise.resolve(r):r}if(t)return Promise.reject(n);throw n}}};var _=new B;function d(l,e){return _.parse(l,e)}d.options=d.setOptions=function(l){return _.setOptions(l),d.defaults=_.defaults,G(d.defaults),d};d.getDefaults=L;d.defaults=O;d.use=function(...l){return _.use(...l),d.defaults=_.defaults,G(d.defaults),d};d.walkTokens=function(l,e){return _.walkTokens(l,e)};d.parseInline=_.parseInline;d.Parser=R;d.parser=R.parse;d.Renderer=P;d.TextRenderer=$;d.Lexer=b;d.lexer=b.lex;d.Tokenizer=y;d.Hooks=S;d.parse=d;var Dt=d.options,Ht=d.setOptions,Zt=d.use,Gt=d.walkTokens,Nt=d.parseInline,jt=d,Ft=R.parse,Qt=b.lex;export{S as Hooks,b as Lexer,B as Marked,R as Parser,P as Renderer,$ as TextRenderer,y as Tokenizer,O as defaults,L as getDefaults,Qt as lexer,d as marked,Dt as options,jt as parse,Nt as parseInline,Ft as parser,Ht as setOptions,Zt as use,Gt as walkTokens};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
style:
|
||||
`.md-p {
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
}
|
||||
|
||||
.md-table,
|
||||
.md-blockquote {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.md-table {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.md-tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #c6cbd1;
|
||||
}
|
||||
|
||||
.md-table .md-tr:nth-child(2n) {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.md-th,
|
||||
.md-td {
|
||||
padding: 6px 13px !important;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
.md-th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.md-blockquote {
|
||||
padding: 0 1em;
|
||||
color: #6a737d;
|
||||
border-left: 0.25em solid #dfe2e5;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.md-pre .md-code {
|
||||
padding: 0;
|
||||
font-size: 100%;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}`,
|
||||
handler (file) {
|
||||
// 添加 markdown 属性
|
||||
if (file.path.includes('miniprogram' + path.sep + 'index.js')) {
|
||||
file.contents = Buffer.from(file.contents.toString().replace(/properties\s*:\s*{/, 'properties: {\n markdown: Boolean,')
|
||||
// 处理中文 id
|
||||
.replace(/navigateTo\s*\(id,\s*offset\)\s*{/, 'navigateTo (id, offset) {\n id = this._ids[decodeURI(id)] || id'))
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
module.exports = {
|
||||
style:
|
||||
`.md-p {
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
}
|
||||
|
||||
.md-table,
|
||||
.md-blockquote {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.md-table {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.md-tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #c6cbd1;
|
||||
}
|
||||
|
||||
.md-table .md-tr:nth-child(2n) {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.md-th,
|
||||
.md-td {
|
||||
padding: 6px 13px !important;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
.md-th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.md-blockquote {
|
||||
padding: 0 1em;
|
||||
color: #6a737d;
|
||||
border-left: 0.25em solid #dfe2e5;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.md-pre .md-code {
|
||||
padding: 0;
|
||||
font-size: 100%;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}`,
|
||||
handler (file) {
|
||||
// 添加 markdown 属性
|
||||
if (file.path.includes('mp-html.vue')) {
|
||||
file.contents = Buffer.from(file.contents.toString().replace(/props\s*:\s*{/, 'props: {\n markdown: Boolean,')
|
||||
// 处理中文 id
|
||||
.replace(/navigateTo\s*\(id,\s*offset\)\s*{/, 'navigateTo (id, offset) {\n id = this._ids[decodeURI(id)] || id'))
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# search
|
||||
功能:关键词搜索
|
||||
大小:*≈1.5KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √(nvue 不支持) |
|
||||
|
||||
说明:
|
||||
引入后会在组件实例上挂载一个 *search* 方法,用于关键词搜索
|
||||
|
||||
输入值
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
|:---:|:---:|:---:|---|
|
||||
| key | String 或 RegExp | - | 要搜索的关键词,支持字符串和正则 |
|
||||
| anchor | Boolean | false | 是否将搜索结果设置为锚点 |
|
||||
| style | String | background-color:yellow | 标记搜索结果的样式 |
|
||||
|
||||
返回值:*Promise*
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|:---:|:---:|---|
|
||||
| num | Number | 搜索结果数量 |
|
||||
| highlight | Function(i, style='background-color:#FF9632') | 高亮第 i(1 ~ num)个结果,将其样式设置为 style |
|
||||
| jump | Function(i, offset) | 跳转到第 i(1 ~ num)个结果,偏移量为 offset,anchor 为 true 才可用 |
|
||||
|
||||
示例:
|
||||
```javascript
|
||||
function search (key) {
|
||||
// ctx 为组件实例
|
||||
ctx.search(key, true).then(res => {
|
||||
res.highlight(1)
|
||||
res.jump(1, -50) // 高亮第 1 个结果并跳转到该位置,偏移量 -50
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
附加说明:
|
||||
1. 不传入 *key*(或为空)时即可取消搜索,取消所有的高亮,还原到原来的效果
|
||||
2. 进行新的搜索时旧的搜索结果将被还原,旧的结果中的 *highlight* 等方法不再可用
|
||||
3. 调用 *highlight* 方法高亮一个结果时,之前被高亮的结果会被还原,即始终只有一个结果被高亮
|
||||
4. *key* 传入字符串时大小写敏感,如果要忽略大小写可以用正则的 *i*(字符串搜索效率高于正则)
|
||||
5. 设置 *anchor* 为 *true* 会一定程度上降低效率,非必要不要开启
|
||||
6. 暂不支持跨标签搜索,即只有一个文本节点内包含整个关键词才能被搜索到
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* @fileoverview search 插件
|
||||
*/
|
||||
|
||||
function Search (vm) {
|
||||
/**
|
||||
* @description 关键词搜索
|
||||
* @param {regexp|string} key 要搜索的关键词
|
||||
* @param {boolean} anchor 是否将搜索结果设置为锚点
|
||||
* @param {string} style 搜索结果的样式
|
||||
*/
|
||||
vm.search = function (key, anchor, style = 'background-color:yellow') {
|
||||
const obj = {}
|
||||
const stack = []
|
||||
const res = [];
|
||||
|
||||
// 遍历搜索
|
||||
(function traversal (nodes, path) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
if (node.type === 'text' && key) {
|
||||
const arr = node.text.split(key)
|
||||
const children = []
|
||||
if (arr.length > 1) {
|
||||
// 找到关键词
|
||||
for (let j = 0; j < arr.length; j++) {
|
||||
if (arr[j]) {
|
||||
children.push({
|
||||
type: 'text',
|
||||
text: arr[j]
|
||||
})
|
||||
}
|
||||
if (j !== arr.length - 1) {
|
||||
// 关键词转为一个 span
|
||||
res.push(`${path}[${i}].children[${children.length}].attrs.style`)
|
||||
children.push({
|
||||
name: 'span',
|
||||
attrs: {
|
||||
id: anchor ? 'search' + res.length : undefined, // 用于锚点的 id
|
||||
style
|
||||
},
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: key instanceof RegExp ? key.exec(node.text)[0] : key
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
if (key instanceof RegExp) {
|
||||
key.exec(node.text)
|
||||
}
|
||||
if (anchor) {
|
||||
for (let l = stack.length; l--;) {
|
||||
// 给父组件做标记,将该标签暴露出来
|
||||
if (stack[l].c) {
|
||||
break
|
||||
} else {
|
||||
obj[stack[l].path] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
obj[`${path}[${i}]`] = {
|
||||
name: 'span',
|
||||
c: anchor ? 1 : undefined,
|
||||
s: 1,
|
||||
children
|
||||
}
|
||||
}
|
||||
} else if (node.s) {
|
||||
let text = ''
|
||||
// 复原上一次的结果
|
||||
for (let k = 0; k < node.children.length; k++) {
|
||||
const child = node.children[k]
|
||||
if (child.text) {
|
||||
text += child.text
|
||||
} else {
|
||||
text += child.children[0].text
|
||||
}
|
||||
}
|
||||
nodes[i] = {
|
||||
type: 'text',
|
||||
text
|
||||
}
|
||||
if (key && (key instanceof RegExp ? key.test(text) : text.includes(key))) {
|
||||
i--
|
||||
} else {
|
||||
obj[`${path}[${i}]`] = nodes[i]
|
||||
}
|
||||
} else if (node.children) {
|
||||
stack.push({
|
||||
path: `${path}[${i}].c`,
|
||||
c: node.c || node.name === 'table'
|
||||
})
|
||||
traversal(node.children, `${path}[${i}].children`)
|
||||
stack.pop()
|
||||
}
|
||||
}
|
||||
})(vm.data.nodes, 'nodes')
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
vm.setData(obj, () => {
|
||||
resolve({
|
||||
num: res.length, // 结果数量
|
||||
/**
|
||||
* @description 高亮某一个结果
|
||||
* @param {number} i 第几个
|
||||
* @param {string} hlstyle 高亮的样式
|
||||
*/
|
||||
highlight (i, hlstyle = 'background-color:#FF9632') {
|
||||
if (i < 1 || i > res.length) return
|
||||
const obj = {}
|
||||
if (this.last) {
|
||||
obj[res[this.last - 1]] = style
|
||||
}
|
||||
this.last = i
|
||||
obj[res[i - 1]] = hlstyle
|
||||
vm.setData(obj)
|
||||
},
|
||||
/**
|
||||
* @description 跳转到搜索结果
|
||||
* @param {number} i 第几个
|
||||
* @param {number} offset 偏移量
|
||||
*/
|
||||
jump: anchor
|
||||
? (i, offset) => {
|
||||
if (i > 0 && i <= res.length) {
|
||||
vm.navigateTo('search' + i, offset)
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Search
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @fileoverview search 插件
|
||||
*/
|
||||
function Search (vm) {
|
||||
/**
|
||||
* @description 关键词搜索
|
||||
* @param {regexp|string} key 要搜索的关键词
|
||||
* @param {boolean} anchor 是否将搜索结果设置为锚点
|
||||
* @param {string} style 搜索结果的样式
|
||||
*/
|
||||
vm.search = function (key, anchor, style = 'background-color:yellow') {
|
||||
const res = []
|
||||
const stack = [];
|
||||
|
||||
// 遍历搜索
|
||||
(function traversal (nodes) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i]
|
||||
if (node.type === 'text' && key) {
|
||||
const text = node.text
|
||||
const arr = text.split(key)
|
||||
if (arr.length > 1) {
|
||||
node = {
|
||||
name: 'span',
|
||||
attrs: {},
|
||||
type: 'node',
|
||||
c: 1,
|
||||
s: 1,
|
||||
children: []
|
||||
}
|
||||
vm.$set(nodes, i, node)
|
||||
for (let j = 0; j < arr.length; j++) {
|
||||
if (arr[j]) {
|
||||
node.children.push({
|
||||
type: 'text',
|
||||
text: arr[j]
|
||||
})
|
||||
}
|
||||
if (j !== arr.length - 1) {
|
||||
// 关键词转为一个 span
|
||||
node.children.push({
|
||||
name: 'span',
|
||||
attrs: {
|
||||
id: anchor ? 'search' + (res.length + 1) : undefined, // 用于锚点的 id
|
||||
style: style
|
||||
},
|
||||
// #ifdef VUE3
|
||||
c: 1,
|
||||
// #endif
|
||||
children: [{
|
||||
type: 'text',
|
||||
text: key instanceof RegExp ? key.exec(text)[0] : key
|
||||
}]
|
||||
})
|
||||
res.push(node.children[node.children.length - 1].attrs)
|
||||
}
|
||||
}
|
||||
if (key instanceof RegExp) {
|
||||
key.exec(text)
|
||||
}
|
||||
if (anchor) {
|
||||
for (let l = stack.length; l--;) {
|
||||
if (stack[l].c) {
|
||||
break
|
||||
} else {
|
||||
vm.$set(stack[l], 'c', 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.s) {
|
||||
let text = ''
|
||||
// 复原上一次的结果
|
||||
for (let k = 0; k < node.children.length; k++) {
|
||||
const child = node.children[k]
|
||||
if (child.text) {
|
||||
text += child.text
|
||||
} else {
|
||||
text += child.children[0].text
|
||||
}
|
||||
}
|
||||
vm.$set(nodes, i, {
|
||||
type: 'text',
|
||||
text
|
||||
})
|
||||
if (key && (key instanceof RegExp ? key.test(text) : text.includes(key))) {
|
||||
i--
|
||||
}
|
||||
} else if (node.children) {
|
||||
stack.push(node)
|
||||
traversal(node.children)
|
||||
stack.pop()
|
||||
}
|
||||
}
|
||||
})(vm.nodes)
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
num: res.length, // 结果数量
|
||||
/**
|
||||
* @description 高亮某一个结果
|
||||
* @param {number} i 第几个
|
||||
* @param {string} hlstyle 高亮的样式
|
||||
*/
|
||||
highlight (i, hlstyle = 'background-color:#FF9632') {
|
||||
if (i < 1 || i > res.length) return
|
||||
if (this.last) {
|
||||
res[this.last - 1].style = style
|
||||
}
|
||||
this.last = i
|
||||
res[i - 1].style = hlstyle
|
||||
},
|
||||
/**
|
||||
* @description 跳转到搜索结果
|
||||
* @param {number} i 第几个
|
||||
* @param {number} offset 偏移量
|
||||
*/
|
||||
jump: anchor
|
||||
? (i, offset) => {
|
||||
if (i > 0 && i <= res.length) {
|
||||
vm.navigateTo('search' + i, offset)
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Search
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# style
|
||||
功能:解析和匹配 *style* 标签中的样式
|
||||
|
||||
> 这里的 *style* 标签指的是传入 *content* 属性中的 *html* 里包含的 *style* 标签,且 *style* 标签要放在其他标签前面才能生效
|
||||
|
||||
大小:*≈3.5KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | √ | √ | √ | √ (nvue 直接支持) |
|
||||
|
||||
说明:
|
||||
支持以下选择器:
|
||||
|
||||
| 名称 | 示例 |
|
||||
|:---:|---|
|
||||
| 标签名选择器 | p {} |
|
||||
| class 选择器 | .class {} |
|
||||
| id 选择器 | #id {} |
|
||||
| 多选择器交集 | p.class {} |
|
||||
| 多选择器并集 | p, .class {} |
|
||||
| 后代选择器 | .class1 .class2 {} |
|
||||
| 子选择器 | .class1 > .class2 {} |
|
||||
| 伪类 | .class::before {} |
|
||||
|
||||
伪类仅支持 *before* 和 *after*,支持 *attr* 方法
|
||||
不支持的选择器(属性选择器等)将被忽略
|
||||
|
||||
> 由于小程序中无法动态写入 *css*,本插件的实现原理是通过解析,将匹配的样式添加到各标签的行内 *style* 中去,请慎用宽泛的选择器,以免大大增加解析结果大小,减慢渲染速度
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* @fileoverview style 插件
|
||||
*/
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
const Parser = require('./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
|
||||
|
||||
module.exports = Style
|
||||
+175
@@ -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
|
||||
}
|
||||
|
||||
module.exports = Parser
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
# 模板
|
||||
这是一个插件模板,需要开发插件时,可以从这个模板开始
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @description 插件构建文件模板
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @description 入口文件
|
||||
* @type {String}
|
||||
* @default 'index.js'
|
||||
*/
|
||||
main: 'index.js',
|
||||
/**
|
||||
* @description 支持的平台
|
||||
* @type {String[]}
|
||||
* @default ['mp-weixin','mp-qq','mp-baidu','mp-alipay','mp-toutiao','uni-app']
|
||||
*/
|
||||
platform: ['mp-weixin', 'mp-qq', 'mp-baidu', 'mp-alipay', 'mp-toutiao', 'uni-app'],
|
||||
/**
|
||||
* @description 要被添加到模板文件中的标签(将被添加到 src/node/node.wxml)
|
||||
* 必须要有 wx:if 表明什么情况下使用该标签
|
||||
* n 表示标签结构体,<node> 标签用于递归显示子节点(可参考源文件中的写法)
|
||||
* @type {String}
|
||||
*/
|
||||
template: '',
|
||||
/**
|
||||
* @description 用于处理模板中事件的方法(将被添加到 src/node/node.js)
|
||||
* 需要触发顶层组件的事件请使用 this.root.triggerEvent
|
||||
* @type {Object}
|
||||
*/
|
||||
methods: {
|
||||
|
||||
},
|
||||
/**
|
||||
* @description 用于模板文件的 css 样式(将被添加到 src/node/node.wxss)
|
||||
* @type {String}
|
||||
*/
|
||||
style: '',
|
||||
/**
|
||||
* @description 要被引入到模板文件的 css 文件路径(将被添加到 src/node/node.wxss)
|
||||
* @type {String|String[]}
|
||||
*/
|
||||
import: [],
|
||||
/**
|
||||
* @description 在模板中需要使用的组件或插件列表(将被添加到 src/node/node.json)
|
||||
* @type {Object}
|
||||
*/
|
||||
usingComponents: {
|
||||
|
||||
},
|
||||
/**
|
||||
* @description 自定义文件处理器
|
||||
* 如果上述处理还无法满足要求,可以在此方法中进行处理
|
||||
* 所有 src 目录下的文件和本插件目录下的文件都会经过此方法的处理
|
||||
* @param {Vinyl} file 关于该文件对象的格式可参考 https://github.com/gulpjs/vinyl#instance-methods
|
||||
* @param {String} platform 平台
|
||||
*/
|
||||
handler (file, platform) {
|
||||
let content = file.contents.toString()
|
||||
// 进行处理
|
||||
if (platform === 'xxx') {
|
||||
content = content.replace('aaa', 'bbb')
|
||||
}
|
||||
file.contents = Buffer.from(content)
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @fileoverview 插件入口文件模板
|
||||
*/
|
||||
|
||||
const data = {} // 全局数据
|
||||
|
||||
/**
|
||||
* @description 组件被创建时将实例化插件
|
||||
* @param {Component} vm 组件实例
|
||||
*/
|
||||
function Plugin (vm) {
|
||||
this.vm = vm // 保存实例在其他周期使用
|
||||
this.compData = {} // 仅在单个组件中使用的数据
|
||||
data.xxx = 'xxx' // 记录全局数据
|
||||
}
|
||||
|
||||
/**
|
||||
* @description html 数据更新时触发
|
||||
* @param {string} content 要更新的 html 字符串
|
||||
* @param {object} config 解析配置
|
||||
* @returns {string|void} 如果要对 html 字符串进行一些预处理,则返回处理后的字符串
|
||||
*/
|
||||
Plugin.prototype.onUpdate = function (content, config) {
|
||||
config.ignoreTags.xxx = true // 移除 xxx 标签
|
||||
// 对 html 内容进行预处理并返回修改,没有修改则不需要返回
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析到一个标签时触发
|
||||
* @param {object} node 标签
|
||||
* @param {object} parser 解析器实例
|
||||
* @returns {boolean|void} 如果返回 false 将移除该标签
|
||||
*/
|
||||
Plugin.prototype.onParse = function (node, parser) {
|
||||
// 处理文本标签
|
||||
if (node.type === 'text') {
|
||||
// node.text 文本内容
|
||||
} else {
|
||||
// 处理元素标签
|
||||
// node.name 标签名
|
||||
// node.attrs 属性列表
|
||||
// node.children 子节点(非自闭合标签有)
|
||||
|
||||
if (node.name === 'xxx') {
|
||||
parser.expose() // 如果该标签不能被 rich-text 包含,需要调用此方法暴露出来
|
||||
// parser.options 组件传入的一些解析属性
|
||||
// parser.stack 可以从栈中获取祖先节点
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description dom 树加载完毕时触发(load 事件)
|
||||
*/
|
||||
Plugin.prototype.onLoad = function () {
|
||||
// 可以获取媒体 context 对象等
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 组件被移除时触发
|
||||
*/
|
||||
Plugin.prototype.onDetached = function () {
|
||||
// 可以释放一些必要的资源(计时器等)
|
||||
}
|
||||
|
||||
module.exports = Plugin
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# txv-video
|
||||
功能:使用腾讯视频
|
||||
大小:*≈1KB*
|
||||
支持平台:
|
||||
|
||||
| 微信小程序 | QQ 小程序 | 百度小程序 | 支付宝小程序 | 头条小程序 | uni-app |
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| √ | √ | | | | √ (h5 和 app 直接支持) |
|
||||
|
||||
说明:
|
||||
引入本插件后,*html* 中符合下方格式的 *iframe* 标签(*src* 中含有 *vid*)将被转为通过腾讯视频播放:
|
||||
```html
|
||||
<iframe src="https://v.qq.com/txp/iframe/player.html?vid=xxxxxx" allowFullScreen="true"></iframe>
|
||||
```
|
||||
|
||||
同时,其可以被 *pause-video* 属性控制
|
||||
|
||||
!> 本插件仅用于将官方 [腾讯视频插件](https://github.com/tvfe/txv-miniprogram-plugin) 应用于本组件,仅在微信和 *qq* 平台有效,使用前请确认已经成功申请使用该插件并按要求在小程序 *app.json* 中配置完成,否则可能报错 **This application has not registered any plugins yet** 且无法生效
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
platform: ['mp-weixin', 'mp-qq', 'uni-app']
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @fileoverview txv-video 插件
|
||||
* Include txv-video (https://github.com/tvfe/txv-miniprogram-plugin)
|
||||
*/
|
||||
const TxvVideo = function (vm) {
|
||||
this.vm = vm
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
try {
|
||||
const TxvContext = requirePlugin('tencentvideo')
|
||||
|
||||
TxvVideo.prototype.onLoad = function () {
|
||||
setTimeout(() => {
|
||||
for (let i = 0; i < this.videos.length; i++) {
|
||||
const ctx = TxvContext.getTxvContext(this.videos[i])
|
||||
ctx.id = this.videos[i]
|
||||
this.vm._videos.push(ctx)
|
||||
}
|
||||
}, 50)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('使用txv-video扩展需注册腾讯视频插件')
|
||||
}
|
||||
|
||||
TxvVideo.prototype.onUpdate = function (_, config) {
|
||||
config.trustTags['txv-video'] = true
|
||||
this.videos = []
|
||||
}
|
||||
|
||||
TxvVideo.prototype.onParse = function (node, parser) {
|
||||
if (node.name === 'iframe' && (node.attrs.src || '').includes('vid')) {
|
||||
const vid = node.attrs.src.match(/vid=([^&\s]+)/)
|
||||
if (vid) {
|
||||
node.name = 'txv-video'
|
||||
node.attrs.vid = vid[1]
|
||||
this.videos.push(vid[1])
|
||||
node.attrs.src = undefined
|
||||
parser.expose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
module.exports = TxvVideo
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
template: '<txv-video wx:if="{{n.name==\'txv-video\'}}" vid="{{n.attrs.vid}}" playerid="{{n.attrs.vid}}" id="{{n.attrs.vid}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" controls data-i="{{i}}" bindplay="play" binderror="mediaError" />',
|
||||
usingComponents: {
|
||||
'txv-video': 'plugin://tencentvideo/video'
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
template: '<txv-video v-if="n.name==\'txv-video\'" :vid="n.attrs.vid" :playerid="n.attrs.vid" :id="n.attrs.vid" :class="n.attrs.class" :style="n.attrs.style" controls :data-i="i" @play="play" @error="mediaError" />'
|
||||
}
|
||||
Reference in new issue
Block a user