tribute 实现@(艾特)功能
Opened this issue · 0 comments
xiaotiandada commented
利用 https://github.com/zurb/tribute 实现一个前端@的功能 大概长这样。
因为前端是Vue(Nuxt)所以用了一个别人封装好的库 https://github.com/syropian/vue-tribute
在Nuxt里面用?
如果你不需要在Nuxt里面用就可以跳过
// import Tribute from 'tributejs'
let Tribute
if (process.client) {
Tribute = require('tributejs')
}
const VueTribute = {
name: 'vue-tribute',
props: {
options: {
type: Object,
required: true
}
},
watch: {
options: {
immediate: false,
deep: true,
handler() {
if (this.tribute) {
setTimeout(() => {
var $el = this.$slots.default[0].elm
this.tribute.detach($el)
setTimeout(() => {
$el = this.$slots.default[0].elm
this.tribute = new Tribute(this.options)
this.tribute.attach($el)
$el.tributeInstance = this.tribute
}, 0)
}, 0)
}
}
}
},
mounted() {
if (process.client) {
if (typeof Tribute === 'undefined') {
throw new Error('[vue-tribute] cannot locate tributejs!')
}
const $el = this.$slots.default[0].elm
this.tribute = new Tribute(this.options)
this.tribute.attach($el)
$el.tributeInstance = this.tribute
$el.addEventListener('tribute-replaced', e => {
e.target.dispatchEvent(new Event('input', { bubbles: true }))
})
}
},
beforeDestroy() {
const $el = this.$slots.default[0].elm
if (this.tribute) {
this.tribute.detach($el)
}
},
render(h) {
return h(
'div',
{
staticClass: 'v-tribute'
},
this.$slots.default
)
}
}
if (process.client) {
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.component(VueTribute.name, VueTribute)
}
}
export default VueTribute
因为Nuxt的关系 我基于源码又做了一层封装(加了一些判断而已)。
使用
<client-only>
<vue-tribute
:options="tributeOptions"
@tribute-no-match="noMatchFound"
@tribute-replaced="tributeReplaced"
>
<div
id="tributeShare"
class="content-editable"
contenteditable="true"
placeholder="谈谈感想"
/>
</vue-tribute>
</client-only>
搜索用户
最主要的是这个options配置,然后 tribute 有几种输入框,具体看Demo就知道了 这里说一下怎么搜索
tributeOptions: {
collection: [
{
trigger: '@',
values: (query, cb) => {
console.log('query', query)
if (!query) {
return cb([])
}
return this.searchUser(query, cb)
},
loadingItemTemplate: '<div style="padding: 16px">Loading</div>',
lookup: 'key',
fillAttr: 'key',
selectTemplate: function (item) {
console.log('item', item)
return `<a
class="tribute-mention"
contenteditable="false"
href="javascript:;"
title="${item.original.value}"
data-user="${item.original.id}">@${item.original.value}</a>`
},
},
],
},
// ...
searchUser: debounce(async function (val, cb) {
const list = await search(params)
return cb(list)
} else {
return cb([])
}
}, 300),
搜索通过 values 定义的Func来执行 query 是输入的内容 利用cb设置数据
设置用户显示模版
可以自定义Temp,我这里返回了一个 a Tag,默认是 span 我为了方便改成了 a。
并且设置了 data-user 自定义数据 方便后续做渲染操作。
return `<a
class="tribute-mention"
contenteditable="false"
href="javascript:;"
title="${item.original.value}"
data-user="${item.original.id}">@${item.original.value}</a>`
Xss过滤
为了避免用户插入HTML等提交,这里用 https://github.com/leizongmin/js-xss 过滤,显示的时候也需要过滤一次 避免用户通过接口提交(也可以用其他的办法)
这里只通过a标签 因为a标签需要做用户跳转的
// 过滤分享内容
const whiteListShare = {
a: [ 'class', 'contenteditable', 'href', 'title', 'data-user', 'target' ]
}
export const Fn = (html, whiteList = whiteListShare) => {
return xss(html, {
whiteList: whiteList,
stripIgnoreTag: true,
stripIgnoreTagBody: ['script']
})
}
渲染
因为搜索用户的时候已经做了a标签处理只需要设置属性就可以
ele.setAttribute('href', 'xxx')
Emoji
https://github.com/jm-david/emoji-mart-vue 可以考虑用这个 选择很多