wangEditor
wangEditor copied to clipboard
V5 版本自定义表情扩展参考示例
功能示例参考
V5 版本自定义表情扩展参考示例,供大家借鉴学习
项目框架
Nuxt3 最新版
wangEditor 版本
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
最终效果图
大致思路
1 参考文档自定义拓展功能,自定义表情菜单。 2 将表情放在项目public/emoji目录下,通过node加载public/emoji目录下面的所有图片文件,获取到文件名名称,拼装表情地址 3 支持emoji表情,例如:😃 ,自定义图片。
获取public/emoji目录下的node代码
import fs from 'fs'
import { fileURLToPath } from 'url'
import { dirname, resolve } from 'path'
const __filename = fileURLToPath(import.meta.url)
// 递归函数,用于获取目录中的所有文件
const getAllFiles = (dir: string) => {
// 存放所有文件的数组
const files: string[] = []
try {
// 同步地读取目录内容
const dirents = fs.readdirSync(dir, { withFileTypes: true })
for (let i = 0; i < dirents.length; i++) {
if (dirents[i].isDirectory()) {
// 如果当前项为子目录,则递归调用getAllFiles()函数
const subDir = `${dir}/${dirents[i].name}`
files.push(...getAllFiles(subDir))
} else {
// 否则将该文件添加到files数组中
files.push(`${dir}/${dirents[i].name}`)
}
}
return files
} catch (err) {
console.error(err)
throw err
}
}
/**
* 获取表情文件夹下面所有的表情图片名称
*/
export default defineEventHandler(() => {
try {
// 表情存放路径
const emojiPath = process.env.NODE_ENV === 'development' ? resolve(dirname(__filename), '../../public/emoji') : resolve(dirname(__filename), '../public/emoji')
//递归获取所有图片地址
const allFiles = getAllFiles(emojiPath)
// 默认静态图标,unicode编码
const defaultEmoji: emojiList = {
groupName: '默认',
groupType: 'emoji',
groupArray: '😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😛 😝 😜 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😢 😭 😤 😠 😡 😳 😱 😨 🤗 🤔 😶 😑 😬 🙄 😯 😴 😷 🤑 😈 🤡 👻 💀 👀 👣 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🖕 ✍️ 🙏'.split(
' '
)
}
//QQ经典动态表情
const defaults: emojiList = { groupName: 'QQ经典', groupType: 'image', groupArray: [] }
//气泡熊动态表情
const ppx: emojiList = { groupName: '气泡熊', groupType: 'image', groupArray: [] }
//蓝色QQ动态表情
const grapeman: emojiList = { groupName: '蓝色QQ表情', groupType: 'image', groupArray: [] }
//嘻哈猴动态表情
const coolmonkey: emojiList = { groupName: '嘻哈猴', groupType: 'image', groupArray: [] }
//QQ动态表情
const comcom: emojiList = { groupName: 'QQ动态', groupType: 'image', groupArray: [] }
// 图片处理
allFiles.forEach((element) => {
const file = element.split('emoji')[1]
const fileImgSrc = process.env.NODE_ENV === 'development' ? `/_nuxt/emoji${file}` : `/emoji${file}`
// 因为有分组,图片存在不同的目录下
if (fileImgSrc.includes('default')) {
defaults.groupArray.push(fileImgSrc)
}
if (fileImgSrc.includes('ppx')) {
ppx.groupArray.push(fileImgSrc)
}
if (fileImgSrc.includes('grapeman')) {
grapeman.groupArray.push(fileImgSrc)
}
if (fileImgSrc.includes('coolmonkey')) {
coolmonkey.groupArray.push(fileImgSrc)
}
if (fileImgSrc.includes('comcom')) {
comcom.groupArray.push(fileImgSrc)
}
})
// 图片名称
return [defaultEmoji, defaults, ppx, grapeman, coolmonkey, comcom]
} catch (err) {
console.error(err)
}
})
插件配置
import { Boot, type IDomEditor, type IDropPanelMenu } from '@wangeditor/editor'
import attachmentModule from '@wangeditor/plugin-upload-attachment'
import type { DOMElement } from '@wangeditor/editor/dist/editor/src/utils/dom'
import $ from 'jquery'
class EmojiDropPanelMenu implements IDropPanelMenu {
showDropPanel: boolean
title: string
iconSvg?: string | undefined
hotkey?: string | undefined
alwaysEnable?: boolean | undefined
tag: string
width?: number | undefined
emojiArray: emojiList[]
constructor(emojiArray: emojiList[]) {
this.title = '表情'
this.iconSvg =
'<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 17.5c2.33 0 4.3-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5M8.5 11A1.5 1.5 0 0 0 10 9.5A1.5 1.5 0 0 0 8.5 8A1.5 1.5 0 0 0 7 9.5A1.5 1.5 0 0 0 8.5 11m7 0A1.5 1.5 0 0 0 17 9.5A1.5 1.5 0 0 0 15.5 8A1.5 1.5 0 0 0 14 9.5a1.5 1.5 0 0 0 1.5 1.5M12 20a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8m0-18C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg>'
this.tag = 'button'
this.showDropPanel = true
this.emojiArray = emojiArray
}
// 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
isActive(editor: IDomEditor): boolean {
return false
}
// 获取菜单执行时的 value ,用不到则返回空 字符串或 false
getValue(editor: IDomEditor): string | boolean {
return ''
}
// 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
isDisabled(editor: IDomEditor): boolean {
return false
}
// 点击菜单时触发的函数
exec(editor: IDomEditor, value: string | boolean) {
// TS 语法
// exec(editor, value) { // JS 语法
// DropPanel menu ,这个函数不用写,空着即可
}
// 定义 DropPanel 内部的 DOM Element
getPanelContentElem(editor: IDomEditor): DOMElement {
const div = $(`<div class="emoji-box"></div`)
const div2 = $(`<div class="tabs-box"></div`)
div.append(div2)
this.emojiArray.forEach((item) => {
const { groupName, groupType, groupArray } = item
const div3 = $(`<div class="tab-item">${groupName}</div>`)
const div4 = $(`<div class="emoji-list"></div>`)
groupArray.forEach((emoji: string) => {
if (groupType === 'emoji') {
const div5 = $(`<div class="emoji-item">${emoji}</div>`)
div5.on('click', () => {
editor.insertText(emoji)
editor.insertText(' ')
})
div4.append(div5)
}
if (groupType === 'image') {
const div5 = $(`<div class="emoji-item"><img src='${emoji}'/></div>`)
div5.on('click', () => {
//新建一个imageNode
const image = {
type: 'image',
src: emoji,
href: emoji,
alt: 'emoji-image',
style: {},
// 【注意】void node 需要一个空 text 作为 children
children: [{ text: '' }]
}
editor.insertNode(image)
})
div4.append(div5)
}
})
div2.append(div3)
div.append(div4)
})
div2.find('div').each((index, it) => {
$(it).on('click', () => {
$(it).siblings().removeClass('active')
$(it).addClass('active')
div.find('div[class="emoji-list"]').each((ix, i) => {
if (index === ix) {
$(i).css({ display: 'flex' })
} else {
$(i).css({ display: 'none' })
}
})
})
})
$(div2.find('div')[0]).trigger('click')
return div[0]
}
}
//自定义编辑器插件,对获取html进行处理
const withGetHtml = <T extends IDomEditor>(editor: T): T => {
// 获取当前 editor API
const { getHtml } = editor
const newEditor = editor
// 重写 getHtml
newEditor.getHtml = () => {
if (getHtml() === '<p><br></p>') return ''
return getHtml()
}
// 返回 newEditor ,重要!
return newEditor
}
// 注册,要在创建编辑器之前注册,且只能注册一次,不可重复注册。
export default defineNuxtPlugin(async (nuxtApp) => {
// 获取表情数据
const emojiArray = await $fetch('/emoji-path')
// 自定义表情菜单
const emojiMenuConf = {
key: 'emojiMenu',
factory() {
return new EmojiDropPanelMenu(emojiArray)
}
}
//注册自定义插件,处理编辑器默认值为''时获取到的是为'<p><br></p>'
Boot.registerPlugin(withGetHtml)
//注册附件上传插件
Boot.registerModule(attachmentModule)
//注册自定义标签菜单
Boot.registerMenu(emojiMenuConf)
})
编辑器设置
//工具栏配置
const toolbarConfig: Partial<IToolbarConfig> = {
.......
//插入自定义表情菜单配置
insertKeys: {
// 自定义插入的位置
index: 20,
// 自定义表情
keys: ['emojiMenu']
},
.......
}
好了,到此就结束了。
这个方案插入图片作为表情的时候,图片和当前行文字没有垂直居中,是哪里的问题?
@zhengnan0627 作为普通文字时肯定没有垂直居中的,如果你只要一个大标题的话可以试试这个https://cycleccc.github.io/demo/like-qq-doc.html 标题样式独立出来自己维护,这样 wangeditor-next 就不会 norm 你的标题样式了 代码在这儿 https://github.com/end-cycle/wangEditor/blob/238c778f6c0c102933feff29b984f9b8e23fbe1e/packages/editor/demo/like-qq-doc.html#L78