/*! * mp-html v2.5.1 * https://github.com/jin-yufeng/mp-html * * Released under the MIT license * Author: Jin Yufeng */ const Parser = require('./parser') const plugins = [] Component({ data: { nodes: [] }, properties: { /** * @description 容器的样式 * @type {String} */ containerStyle: String, /** * @description 用于渲染的 html 字符串 * @type {String} */ content: { type: String, value: '', observer (content) { this.setContent(content) } }, /** * @description 是否允许外部链接被点击时自动复制 * @type {Boolean} * @default true */ copyLink: { type: Boolean, value: true }, /** * @description 主域名,用于拼接链接 * @type {String} */ domain: String, /** * @description 图片出错时的占位图链接 * @type {String} */ errorImg: String, /** * @description 是否开启图片懒加载 * @type {Boolean} * @default false */ lazyLoad: Boolean, /** * @description 图片加载过程中的占位图链接 * @type {String} */ loadingImg: String, /** * @description 是否在播放一个视频时自动暂停其他视频 * @type {Boolean} * @default true */ pauseVideo: { type: Boolean, value: true }, /** * @description 是否允许图片被点击时自动预览 * @type {Boolean | String} * @default true */ previewImg: { type: null, value: true }, /** * @description 是否给每个表格添加一个滚动层使其能单独横向滚动 * @type {Boolean} * @default false */ scrollTable: Boolean, /** * @description 是否开启长按复制 * @type {Boolean | String} * @default false */ selectable: null, /** * @description 是否将 title 标签的内容设置到页面标题 * @type {Boolean} * @default true */ setTitle: { type: Boolean, value: true }, /** * @description 是否允许图片被长按时显示菜单 * @type {Boolean} * @default true */ showImgMenu: { type: Boolean, value: true }, /** * @description 标签的默认样式 * @type {Object} */ tagStyle: Object, /** * @description 是否使用锚点链接 * @type {Boolean | Number} * @default false */ useAnchor: null }, created () { this.plugins = [] for (let i = plugins.length; i--;) { this.plugins.push(new plugins[i](this)) } // #ifdef MP-ALIPAY if (this.properties.content) { this.setContent(this.properties.content) } // #endif }, // #ifdef MP-ALIPAY didUpdate (e) { if (e.content !== this.properties.content) { this.setContent(this.properties.content) } }, // #endif detached () { // 注销插件 this._hook('onDetached') }, methods: { /** * @description 将锚点跳转的范围限定在一个 scroll-view 内 * @param {Object} page scroll-view 所在页面的示例 * @param {String} selector scroll-view 的选择器 * @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名 */ in (page, selector, scrollTop) { if (page && selector && scrollTop) { this._in = { page, selector, scrollTop } } }, /** * @description 锚点跳转 * @param {String} id 要跳转的锚点 id * @param {Number} offset 跳转位置的偏移量 * @returns {Promise} */ navigateTo (id, offset) { return new Promise((resolve, reject) => { if (!this.properties.useAnchor) { reject(Error('Anchor is disabled')) return } // 跨组件选择器 const deep = // #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO '>>>' // #endif // #ifdef MP-BAIDU || MP-ALIPAY ' ' // eslint-disable-line // #endif const selector = wx.createSelectorQuery() // #ifndef MP-ALIPAY .in(this._in ? this._in.page : this) // #endif .select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect() if (this._in) { selector.select(this._in.selector).scrollOffset() .select(this._in.selector).boundingClientRect() } else { // 获取 scroll-view 的位置和滚动距离 selector.selectViewport().scrollOffset() // 获取窗口的滚动距离 } selector.exec(res => { if (!res[0]) { reject(Error('Label not found')) return } const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + (offset || parseInt(this.properties.useAnchor) || 0) if (this._in) { // scroll-view 跳转 this._in.page.setData({ [this._in.scrollTop]: scrollTop }) } else { // 页面跳转 wx.pageScrollTo({ scrollTop, duration: 300 }) } resolve() }) }) }, /** * @description 获取文本内容 * @returns {String} */ getText (nodes) { let text = ''; (function traversal (nodes) { for (let i = 0; i < nodes.length; i++) { const node = nodes[i] if (node.type === 'text') { text += node.text.replace(/&/g, '&') } else if (node.name === 'br') { text += '\n' } else { // 块级标签前后加换行 const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7') if (isBlock && text && text[text.length - 1] !== '\n') { text += '\n' } // 递归获取子节点的文本 if (node.children) { traversal(node.children) } if (isBlock && text[text.length - 1] !== '\n') { text += '\n' } else if (node.name === 'td' || node.name === 'th') { text += '\t' } } } })(nodes || this.data.nodes) return text }, /** * @description 获取内容大小 * @returns {Promise} */ getRect () { return new Promise((resolve, reject) => { wx.createSelectorQuery() // #ifndef MP-ALIPAY .in(this) // #endif .select('._root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found'))) }) }, /** * @description 暂停播放媒体 */ pauseMedia () { for (let i = (this._videos || []).length; i--;) { this._videos[i].pause() } }, /** * @description 设置媒体播放速率 * @param {Number} rate 播放速率 */ setPlaybackRate (rate) { this.playbackRate = rate for (let i = (this._videos || []).length; i--;) { this._videos[i].playbackRate(rate) } }, /** * @description 设置富文本内容 * @param {string} content 要渲染的 html 字符串 * @param {boolean} append 是否在尾部追加 */ setContent (content, append) { if (!this.imgList || !append) { this.imgList = [] } this._videos = [] const data = {} const nodes = new Parser(this).parse(content) // 尾部追加内容 if (append) { for (let i = this.data.nodes.length, j = nodes.length; j--;) { data[`nodes[${i + j}]`] = nodes[j] } } else { data.nodes = nodes } this.setData(data, // #ifndef MP-TOUTIAO () => { this._hook('onLoad') this.triggerEvent('load') } // #endif ) // #ifdef MP-TOUTIAO this.selectComponent('#_root', child => { child.root = this this._hook('onLoad') this.triggerEvent('load') }) // #endif if (this.properties.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) { // 设置懒加载,每 350ms 获取高度,不变则认为加载完毕 let height = 0 const callback = rect => { if (!rect || !rect.height) rect = {} // 350ms 总高度无变化就触发 ready 事件 if (rect.height === height) { this.triggerEvent('ready', rect) } else { height = rect.height setTimeout(() => { this.getRect().then(callback).catch(callback) }, 350) } } this.getRect().then(callback).catch(callback) } else { // 未设置懒加载,等待所有图片加载完毕 if (!this.imgList._unloadimgs) { this.getRect().then(rect => { this.triggerEvent('ready', rect) }).catch(() => { this.triggerEvent('ready', {}) }) } } }, /** * @description 调用插件的钩子函数 * @private */ _hook (name) { for (let i = plugins.length; i--;) { if (this.plugins[i][name]) { this.plugins[i][name]() } } }, // #ifndef MP-TOUTIAO /** * @description 添加子组件 * @private */ _add (e) { e // #ifndef MP-ALIPAY .detail // #endif .root = this } // #endif } })