Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

530 请问 mention 提及插件没有做集成吗 #533

Merged
merged 3 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wise-ducks-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@wangeditor-next/plugin-mention': patch
---

feat(plugin-mention): add mention plugin
Empty file.
90 changes: 90 additions & 0 deletions packages/plugin-mention/README-en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# wangEditor mention plugin

[中文文档](./README.md)

## Introduction

[wangeditor-next](https://github.com/cycleccc/wangEditor-next) mention plugin, like `@James`.

![](./_img/demo.png)

## Installation

```shell
yarn add @wangeditor-next/plugin-mention
```

## Usage

[Vue demo source code](https://github.com/wangfupeng1988/vue2-wangeditor-demo/blob/master/src/components/MyEditorWithMention.vue)

### Use in editor

```ts
import { IDomEditor, Boot, IEditorConfig } from '@wangeditor-next/editor'
import mentionModule, { MentionElement } from '@wangeditor-next/plugin-mention'

// Register
// You should register this before create editor, and register only once (not repeatedly).
Boot.registerModule(mentionModule)

// Show your modal
function showModal(editor: IDomEditor) {
// Get cursor's position info, to set modal position
const domSelection = document.getSelection()
const domRange = domSelection.getRangeAt(0)
if (domRange == null) return
const selectionRect = domRange.getBoundingClientRect()

// Get editor container's position info, maybe help to get right modal position
const containerRect = editor.getEditableContainer().getBoundingClientRect()

// Show your modal, and set position
// PS: You must implement the modal yourself, use <div> or Vue React component


// Insert mention node when emit some event.
function insertMention() {
const mentionNode: MentionElement = {
type: 'mention', // must be 'mention'
value: 'James', // text
info: { x: 1, y: 2 }, // extended info
children: [{ text: '' }], // must have an empty text node in children
}

editor.restoreSelection()
editor.deleteBackward('character') // delete '@'
editor.insertNode(mentionNode)
editor.move(1) // move curser
}
}

// hide your modal
function hideModal(editor: IDomEditor) {
// hide your modal
}

// editor config
const editorConfig: Partial<IEditorConfig> = {
EXTEND_CONF: {
mentionConfig: {
showModal, // required
hideModal, // required
},
},

// others...
}

// Then create editor and toolbar, you will use `editorConfig`
```

### Render HTML

You will get a mention's HTML format like this. You need to `decodeURIComponent` the value of `data-info`.

```html
<span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="James" data-info="%7B%22x%22%3A10%7D">@James</span>
```


87 changes: 87 additions & 0 deletions packages/plugin-mention/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# wangEditor mention 插件

[English Documentation](./README-en.md)

## 介绍

[wangeditor-next](https://github.com/cycleccc/wangEditor-next) mention 插件,如 `@张三`

![](./_img/demo.png)

## 安装

```shell
yarn add @wangeditor-next/plugin-mention
```

## 使用

[Vue 示例源码](https://github.com/wangfupeng1988/vue2-wangeditor-demo/blob/master/src/components/MyEditorWithMention.vue)

### 注册到编辑器

```ts
import { IDomEditor, Boot, IEditorConfig } from '@wangeditor-next/editor'
import mentionModule, { MentionElement } from '@wangeditor-next/plugin-mention'

// 注册。要在创建编辑器之前注册,且只能注册一次,不可重复注册。
Boot.registerModule(mentionModule)

// 显示弹框
function showModal(editor: IDomEditor) {
// 获取光标位置,定位 modal
const domSelection = document.getSelection()
const domRange = domSelection.getRangeAt(0)
if (domRange == null) return
const selectionRect = domRange.getBoundingClientRect()

// 获取编辑区域 DOM 节点的位置,以辅助定位
const containerRect = editor.getEditableContainer().getBoundingClientRect()

// 显示 modal 弹框,并定位
// PS:modal 需要自定义,如 <div> 或 Vue React 组件


// 当触发某事件(如点击一个按钮)时,插入 mention 节点
function insertMention() {
const mentionNode: MentionElement = {
type: 'mention', // 必须是 'mention'
value: '张三', // 文本
info: { x: 1, y: 2 }, // 其他信息,自定义
children: [{ text: '' }], // 必须有一个空 text 作为 children
}

editor.restoreSelection() // 恢复选区
editor.deleteBackward('character') // 删除 '@'
editor.insertNode(mentionNode) // 插入 mention
editor.move(1) // 移动光标
}
}

// 隐藏弹框
function hideModal(editor: IDomEditor) {
// 隐藏 modal
}

// 编辑器配置
const editorConfig: Partial<IEditorConfig> = {
EXTEND_CONF: {
mentionConfig: {
showModal, // 必须
hideModal, // 必须
},
},

// 其他...
}

// 创建创建和工具栏,会用到 editorConfig 。具体查看 wangEditor 文档
```

### 显示 HTML

mention 节点返回的 HTML 格式如下,其中 `data-info` 的值需要 `decodeURIComponent` 解析。

```html
<span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="张三" data-info="%7B%22x%22%3A10%7D">@张三</span>
```
Binary file added packages/plugin-mention/_img/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions packages/plugin-mention/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@wangeditor-next/plugin-mention",
"version": "1.0.0",
"description": "wangEditor mention plugin",
"author": "cycleccc <[email protected]>",
"type": "module",
"homepage": "https://github.com/wangeditor-next/wangEditor-next#readme",
"license": "MIT",
"types": "dist/plugin-mention/src/index.d.ts",
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
".": {
"types": "./dist/plugin-mention/src/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./dist/css/style.css": "./dist/css/style.css"
},
"directories": {
"lib": "dist"
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wangeditor-next/wangEditor-next.git"
},
"scripts": {
"dev": "cross-env NODE_ENV=development rollup -c rollup.config.js",
"dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w",
"build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
"dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js",
"size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js"
},
"bugs": {
"url": "https://github.com/wangeditor-next/wangeditor-next/issues"
},
"peerDependencies": {
"@wangeditor-next/editor": "5.6.31",
"snabbdom": "^3.1.0"
}
}
31 changes: 31 additions & 0 deletions packages/plugin-mention/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createRollupConfig } from '@wangeditor-next-shared/rollup-config'

import pkg from './package.json' assert { type: 'json' }

const name = 'WangEditorMentionPlugin'

const configList = []

// esm
const esmConf = createRollupConfig({
output: {
file: pkg.module,
format: 'esm',
name,
},
})

configList.push(esmConf)

// umd
const umdConf = createRollupConfig({
output: {
file: pkg.main,
format: 'umd',
name,
},
})

configList.push(umdConf)

export default configList
11 changes: 11 additions & 0 deletions packages/plugin-mention/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @description src entry
* @author wangfupeng
*/

import module from './module/index'

export * from './module/custom-types'
export * from './module/interface'

export default module
14 changes: 14 additions & 0 deletions packages/plugin-mention/src/module/custom-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @description mention element
* @author wangfupeng
*/

type EmptyText = {
text: ''
}
export type MentionElement = {
type: 'mention'
value: string
info: any
children: EmptyText[] // void 元素必须有一个空 text
}
24 changes: 24 additions & 0 deletions packages/plugin-mention/src/module/elem-to-html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @description elem to html
* @author wangfupeng
*/

import { SlateElement } from '@wangeditor-next/editor'

import { MentionElement } from './custom-types'

// 生成 html 的函数
function mentionToHtml(elem: SlateElement, _childrenHtml: string): string {
const { value = '', info = {} } = elem as MentionElement
const infoStr = encodeURIComponent(JSON.stringify(info))

return `<span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="${value}" data-info="${infoStr}">@${value}</span>`
}

// 配置
const conf = {
type: 'mention', // 节点 type ,重要!!!
elemToHtml: mentionToHtml,
}

export default conf
20 changes: 20 additions & 0 deletions packages/plugin-mention/src/module/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @description mention module entry
* @author wangfupeng
*/

import { IModuleConf } from '@wangeditor-next/editor'

import elemToHtmlConf from './elem-to-html'
import parseHtmlConf from './parse-elem-html'
import withMention from './plugin'
import renderElemConf from './render-elem'

const module: Partial<IModuleConf> = {
editorPlugin: withMention,
renderElems: [renderElemConf],
elemsToHtml: [elemToHtmlConf],
parseElemsHtml: [parseHtmlConf],
}
Comment on lines +13 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the implementation of imported configurations.

The module configuration looks well-structured. However, we should verify that all imported configurations (elemToHtmlConf, parseHtmlConf, renderElemConf) are properly implemented and tested.


🏁 Script executed:

#!/bin/bash
# Check implementation of imported configurations
echo "Checking elem-to-html implementation..."
ast-grep --pattern 'const elemToHtmlConf = {
  type: $_,
  $$$
}'

echo "Checking parse-elem-html implementation..."
ast-grep --pattern 'const parseHtmlConf = {
  selector: $_,
  $$$
}'

echo "Checking render-elem implementation..."
ast-grep --pattern 'const renderElemConf = {
  type: $_,
  $$$
}'

Length of output: 4095


I ran a second check using a plain text search to locate the definitions of both elemToHtmlConf and renderElemConf. The search confirmed that while parseHtmlConf is defined (and our previous output shows multiple definitions), neither elemToHtmlConf nor renderElemConf appear to be defined anywhere in the repository.

It appears that in the module configuration for the mention plugin the imported configurations for converting elements to HTML and for rendering elements are missing their implementations. You might need to confirm whether these configurations are defined elsewhere or if they should be added.

  • elemToHtmlConf is not defined.
  • renderElemConf is not defined.

Please verify if these are intentionally omitted (for example, if the mention plugin doesn’t support these features) or if they need to be implemented.


export default module
13 changes: 13 additions & 0 deletions packages/plugin-mention/src/module/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @description interface
* @author wangfupeng
*/

import { IDomEditor } from '@wangeditor-next/editor'

export interface IExtendConfig {
mentionConfig: {
showModal: (editor: IDomEditor) => void
hideModal: (editor: IDomEditor) => void
}
}
41 changes: 41 additions & 0 deletions packages/plugin-mention/src/module/parse-elem-html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @description parse elem html
* @author wangfupeng
*/

import { IDomEditor, SlateDescendant, SlateElement } from '@wangeditor-next/editor'

import { DOMElement } from '../utils/dom'
import { MentionElement } from './custom-types'

function parseHtml(
elem: DOMElement,
_children: SlateDescendant[],
_editor: IDomEditor,
): SlateElement {
// elem HTML 结构 <span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="张三" data-info="xxx">@张三</span>

const value = elem.getAttribute('data-value') || ''
const rawInfo = decodeURIComponent(elem.getAttribute('data-info') || '')
let info: any

try {
info = JSON.parse(rawInfo)
} catch (ex) {
info = rawInfo
}

return {
type: 'mention',
value,
info,
children: [{ text: '' }], // void node 必须有一个空白 text
} as MentionElement
}

const parseHtmlConf = {
selector: 'span[data-w-e-type="mention"]',
parseElemHtml: parseHtml,
}

export default parseHtmlConf
Loading
Loading