├── .github └── workflows │ ├── pages.yml │ └── publish.yml ├── .gitignore ├── .prettierrc.json ├── .vscode └── extensions.json ├── README.md ├── core ├── LICENSE ├── README.md ├── api-extractor.json ├── global.d.ts ├── package.json ├── src │ ├── core │ │ ├── editable.ts │ │ ├── editor.ts │ │ ├── hooks.ts │ │ ├── toolbar.ts │ │ ├── types.ts │ │ └── utils.ts │ └── index.ts ├── tsconfig.json └── vite.config.ts ├── docs ├── package.json ├── v0 │ ├── .vuepress │ │ ├── config.js │ │ └── public │ │ │ └── images │ │ │ ├── relevance.png │ │ │ └── shadow.png │ ├── README.md │ ├── extension │ │ ├── README.md │ │ └── toggle-mode.md │ ├── guide │ │ ├── README.md │ │ ├── editable-option.md │ │ ├── form-field.md │ │ ├── locale.md │ │ ├── props.md │ │ ├── relevance.md │ │ ├── reloadbefore.md │ │ ├── toolbar-option.md │ │ ├── typescript.md │ │ ├── use-wang-editor.md │ │ ├── v-model.md │ │ └── vue-cli.md │ └── shadow │ │ ├── README.md │ │ ├── css-rule.md │ │ └── start.md └── v1 │ ├── .vuepress │ └── config.js │ ├── README.md │ └── guide │ ├── README.md │ ├── form-validate.md │ ├── global-config.md │ ├── props.md │ ├── typescript.md │ ├── use-wang-editor.md │ └── v-model.md ├── example ├── common │ ├── package.json │ ├── src │ │ ├── assets │ │ │ └── json │ │ │ │ └── article.json │ │ ├── components │ │ │ ├── IContainer │ │ │ │ ├── index.ts │ │ │ │ └── src │ │ │ │ │ ├── IAside.vue │ │ │ │ │ ├── IContainer.vue │ │ │ │ │ ├── IHeader.vue │ │ │ │ │ ├── IPage.vue │ │ │ │ │ └── types.ts │ │ │ ├── IPrism │ │ │ │ ├── IPrism.vue │ │ │ │ └── index.ts │ │ │ ├── IScroll │ │ │ │ ├── IScroll.vue │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── index.ts │ └── tsconfig.json └── elplus │ ├── .gitignore │ ├── README.md │ ├── auto-imports.d.ts │ ├── components.d.ts │ ├── index.html │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.vue │ ├── assets │ │ └── wangeditor.scss │ ├── main.ts │ ├── plugins │ │ └── wangeditor.ts │ ├── router │ │ └── router.ts │ ├── views │ │ ├── async.vue │ │ ├── begin.vue │ │ ├── default-content.vue │ │ ├── extend-cache.vue │ │ ├── form-validate.vue │ │ ├── model-html-string.vue │ │ ├── model-json-array.vue │ │ ├── model-json-string.vue │ │ ├── reload.vue │ │ └── sync-content.vue │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── gulpfile.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── public └── vite.svg /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # @see https://v2.vuepress.vuejs.org/zh/guide/deployment.html#github-pages 2 | 3 | name: pages 4 | 5 | on: 6 | # 每当 push 到 main 分支时触发部署 7 | push: 8 | branches: [main] 9 | paths: ['core/**', 'docs/**', 'example/**'] 10 | # 手动触发部署 11 | workflow_dispatch: 12 | 13 | jobs: 14 | pages: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v2 22 | with: 23 | version: latest 24 | 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v2 27 | with: 28 | # 选择要使用的 node 版本 29 | node-version: 16 30 | cache: 'pnpm' 31 | 32 | - name: Install Dependencies 33 | run: pnpm install 34 | 35 | # 运行构建脚本 36 | - name: Build Pages 37 | run: pnpm run build 38 | 39 | # 查看 workflow 的文档来获取更多信息 40 | # @see https://github.com/crazy-max/ghaction-github-pages 41 | - name: Deploy to GitHub Pages 42 | uses: crazy-max/ghaction-github-pages@v2 43 | with: 44 | # 部署到 gh-pages 分支 45 | target_branch: gh-pages 46 | # 部署目录为 VuePress 的默认输出目录 47 | build_dir: pages 48 | env: 49 | # @see https://docs.github.com/cn/actions/reference/authentication-in-a-workflow#about-the-github_token-secret 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: publish 5 | 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | 11 | jobs: 12 | publish: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v2 19 | with: 20 | version: latest 21 | 22 | - uses: actions/setup-node@v2 23 | with: 24 | node-version: '16.x' 25 | registry-url: https://registry.npmjs.org/ 26 | 27 | - name: Install Dependencies 28 | run: pnpm install 29 | 30 | - name: Build Package 31 | run: pnpm run build:core 32 | 33 | - name: Publish To Npm 34 | run: pnpm run publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.NPM_PUBLISH}} 37 | 38 | # https://github.com/marketplace/actions/zip-release 39 | - name: Archive Release 40 | uses: thedoctor0/zip-release@master 41 | with: 42 | filename: release.zip 43 | exclusions: '*.git* /*node_modules/*' 44 | 45 | # https://github.com/actions/create-release 46 | - name: Create GitHub Release 47 | id: create_release 48 | uses: actions/create-release@v1 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | tag_name: ${{ github.ref }} 53 | release_name: release ${{ github.ref }} 54 | 55 | # https://github.com/actions/upload-release-asset 56 | - name: Upload Release Asset 57 | uses: actions/upload-release-asset@v1 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | upload_url: ${{ steps.create_release.outputs.upload_url }} 62 | asset_path: ./release.zip 63 | asset_name: release.zip 64 | asset_content_type: application/zip 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | pages 15 | 16 | # vuepress 17 | .temp 18 | .cache 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | .DS_Store 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "bracketSameLine": false, 10 | "arrowParens": "always", 11 | "vueIndentScriptAndStyle": true, 12 | "endOfLine": "lf" 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wangeditor5-for-vue3 2 | 3 | 在 [Vue3](https://v3.cn.vuejs.org/) 中使用 [wangEditor5](https://www.wangeditor.com/v5/)。 4 | 5 | ## 运行 6 | 7 | ```sh 8 | # 安装依赖 9 | pnpm install 10 | 11 | # 打包 12 | pnpm run build 13 | 14 | # 运行 element-plus 示例 15 | # 第一次执行前请执行 pnpm run build:core,构建本地依赖 16 | pnpm run elplus 17 | 18 | # 运行文档 19 | pnpm run docs:v1 20 | ``` 21 | -------------------------------------------------------------------------------- /core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 clinfc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # wangeditor5-for-vue3 2 | 3 | 在 [Vue3](https://v3.cn.vuejs.org/) 中使用 [wangEditor5](https://www.wangeditor.com/v5/)。 4 | 5 | > `wangEditor` 是一款开源 `Web` 富文本编辑器,开箱即用,配置简单 6 | 7 | ## 在线示例 8 | 9 | [https://clinfc.github.io/wangeditor5-for-vue3/element-plus](https://clinfc.github.io/wangeditor5-for-vue3/element-plus) 10 | 11 | ## 快速开始 12 | 13 | ```sh 14 | yarn add @wangeditor/editor wangeditor5-for-vue3 15 | // or 16 | npm install @wangeditor/editor wangeditor5-for-vue3 17 | ``` 18 | 19 | ## 用户文档 20 | 21 | - [wangeditor](https://www.wangeditor.com) 22 | - [wangeditor5-for-vue2](https://clinfc.github.io/wangeditor5-for-vue2/) 23 | - [wangeditor5-for-vue3](https://clinfc.github.io/wangeditor5-for-vue3/v1/) 24 | 25 | ## 功能亮点 26 | 27 | - **动态配置** 28 | 29 | 符合 `Vue` 使用习惯,数据驱动,通过修改配置即可更新编辑器(编辑器创建后修改配置项仍生效) 30 | 31 | - **双向绑定** 32 | 33 | 支持 `v-model:json`、`v-model:json.string` 和 `v-model:html` 三种形式的双向绑定,分别对应 `json array`、`json string` 和 `html string` 三种形式的数据 34 | 35 | - **表单验证** 36 | 37 | 目前已支持 `element-plus`、`ant-design-vue@^3` 和 `naive-ui` 的表单验证,还可以自定义表单验证的执行逻辑 38 | 39 | - **初始内容** 40 | 41 | 编辑器创建时的默认内容配置项支持 `json array`、`json string` 和 `html string` 三种格式的数据 42 | 43 | - **优雅切换** 44 | 45 | `defaultContent`/`defaultHtml` + `reloadEditor()` 可优雅的实现在不同文章间的来回切换 46 | 47 | ### 示例代码 48 | 49 | 见 `example/element-plus` 目录。 50 | 51 | ### 兼容性 52 | 53 | - `vue@^3.2.38` 54 | - `@wangeditor/editor@^5.1.15` 55 | 56 | ### 交流 57 | 58 | - 提交 [issues](https://github.com/clinfc/wangeditor5-for-vue3/issues) 59 | - QQ 群:`343186156` 60 | -------------------------------------------------------------------------------- /core/api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | 4 | "projectFolder": "./", 5 | 6 | "mainEntryPointFilePath": "./dist/types/index.d.ts", 7 | 8 | "dtsRollup": { 9 | "enabled": true, 10 | "untrimmedFilePath": "", 11 | "publicTrimmedFilePath": "./dist/types.d.ts" 12 | }, 13 | 14 | "apiReport": { 15 | "enabled": false 16 | }, 17 | 18 | "docModel": { 19 | "enabled": false 20 | }, 21 | 22 | "tsdocMetadata": { 23 | "enabled": false 24 | }, 25 | 26 | "messages": { 27 | "compilerMessageReporting": { 28 | "default": { 29 | "logLevel": "warning" 30 | } 31 | }, 32 | 33 | "extractorMessageReporting": { 34 | "default": { 35 | "logLevel": "warning", 36 | "addToApiReportFile": true 37 | }, 38 | 39 | "ae-missing-release-tag": { 40 | "logLevel": "none" 41 | }, 42 | 43 | "ae-forgotten-export": { 44 | "logLevel": "none" 45 | } 46 | }, 47 | 48 | "tsdocMessageReporting": { 49 | "default": { 50 | "logLevel": "warning" 51 | }, 52 | 53 | "tsdoc-undefined-tag": { 54 | "logLevel": "none" 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vue" { 2 | export interface GlobalComponents { 3 | WeToolbar: typeof import("wangeditor5-for-vue3")["WeToolbar"]; 4 | WeEditable: typeof import("wangeditor5-for-vue3")["WeEditable"]; 5 | WeEditor: typeof import("wangeditor5-for-vue3")["WeEditor"]; 6 | } 7 | } 8 | 9 | export {}; 10 | -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wangeditor5-for-vue3", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "description": "支持动态配置的 wangEditor5 for vue3 组件,同时支持 UI 框架的表单验证", 6 | "author": "翠林 ", 7 | "license": "MIT", 8 | "homepage": "https://clinfc.github.io/wangeditor5-for-vue3/", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/clinfc/wangeditor5-for-vue3" 12 | }, 13 | "scripts": { 14 | "build": "run-s -c clear:dist build:es build:lib build:type clear:types", 15 | "build:es": "tsc", 16 | "build:lib": "vite build", 17 | "build:type": "api-extractor run", 18 | "clear:dist": "rimraf dist", 19 | "clear:types": "rimraf dist/types" 20 | }, 21 | "keywords": [ 22 | "wangeditor", 23 | "@wangeditor/editor", 24 | "vue", 25 | "vue3", 26 | "富文本编辑器" 27 | ], 28 | "files": [ 29 | "dist", 30 | "src", 31 | "global.d.ts" 32 | ], 33 | "main": "dist/lib/index.umd.js", 34 | "module": "dist/es/index.js", 35 | "types": "dist/types.d.ts", 36 | "unpkg": "dist/lib/index.umd.cjs", 37 | "jsdelivr": "dist/lib/index.umd.cjs", 38 | "exports": { 39 | ".": { 40 | "import": "./dist/es/index.js", 41 | "require": "./dist/lib/index.umd.cjs" 42 | }, 43 | "./es": "./dist/es/index.js", 44 | "./es/*/*.js": "./dist/es/*/*.js", 45 | "./lib": "./dist/lib/index.umd.cjs" 46 | }, 47 | "dependencies": { 48 | "lodash.clonedeep": "^4.5.0", 49 | "lodash.debounce": "^4.0.8" 50 | }, 51 | "devDependencies": { 52 | "@types/lodash.clonedeep": "^4.5.7", 53 | "@types/lodash.debounce": "^4.0.7", 54 | "@wangeditor/editor": "^5.1.15", 55 | "vite": "^3.0.9", 56 | "vue": "^3.2.38" 57 | }, 58 | "peerDependencies": { 59 | "@wangeditor/editor": "^5.1.15" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/core/editable.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createEditor, 3 | IDomEditor, 4 | IEditorConfig, 5 | SlateDescendant, 6 | SlateEditor, 7 | SlateTransforms 8 | } from '@wangeditor/editor' 9 | import debounce from 'lodash.debounce' 10 | import { 11 | computed, 12 | defineComponent, 13 | ExtractPropTypes, 14 | h, 15 | InjectionKey, 16 | nextTick, 17 | onBeforeUnmount, 18 | onMounted, 19 | PropType, 20 | Ref, 21 | ref, 22 | shallowReactive, 23 | toRaw, 24 | toRef, 25 | watch, 26 | watchEffect 27 | } from 'vue' 28 | import { useWeContent } from './hooks' 29 | import { Nullish, WeCache, WeEditableCache, WeEditableReloadEvent, WeInjectContent, WeInstance } from './types' 30 | import { withInstall, is } from './utils' 31 | 32 | export const EditableProps = { 33 | handle: { 34 | type: Symbol as PropType>, 35 | required: true 36 | }, 37 | json: { 38 | type: [Array, String] as unknown as PropType 39 | }, 40 | // v-model:json.string 41 | jsonModifiers: { 42 | default: () => ({ string: false }) 43 | }, 44 | html: { 45 | type: String 46 | } 47 | } as const 48 | 49 | export type WeEditableProps = ExtractPropTypes 50 | 51 | export const EditableEmits = ['reload', 'reloaded', 'update:json', 'update:html'] as const 52 | 53 | const Editable = defineComponent({ 54 | name: 'WeEditable', 55 | props: EditableProps, 56 | emits: [...EditableEmits], 57 | setup(props, { emit, expose }) { 58 | const rootRef = ref() as Ref 59 | 60 | const content = shallowReactive({ 61 | json: [], 62 | jstr: '', 63 | html: '' 64 | }) 65 | 66 | const modelJson = computed(() => { 67 | const { json } = props 68 | if (is(json, 'array')) return true 69 | if (is(json, 'string')) return true 70 | return false 71 | }) 72 | 73 | const modelJstr = computed(() => modelJson.value && props.jsonModifiers.string) 74 | 75 | const modelHtml = computed(() => is(props.html, 'string')) 76 | 77 | const { cache, opts, instance, globalAttrs, formFieldInit } = useWeContent(props.handle) 78 | 79 | const formField = formFieldInit() 80 | 81 | /** 82 | * 生成 reload/reloaded 事件传递的数据 83 | */ 84 | function createEvent(): WeEditableReloadEvent { 85 | return { 86 | type: 'editable', 87 | instance: instance.editable! 88 | } 89 | } 90 | 91 | /** 92 | * 菜单编辑区 93 | * @param dispatch 是否需要触发 reload 事件。组件卸载时的销毁不需要触发 reload 事件 94 | * @return 是否需要触发 reloaded 事件 95 | */ 96 | function destory(dispatch?: boolean): boolean { 97 | if (!instance.editable) return false 98 | syncContent() 99 | 100 | dispatch && emit('reload', createEvent()) 101 | 102 | instance.editable.destroy() 103 | instance.editable = null 104 | return true 105 | } 106 | 107 | /** 封装 change 事件,实现数据 v-model v-model:json 和 v-model:html */ 108 | const changes: ((e: IDomEditor) => void)[] = [] 109 | 110 | watchEffect(() => { 111 | changes.length = 0 112 | 113 | if (modelJson.value || modelHtml.value) { 114 | const { delay } = opts.editable 115 | 116 | changes.push(delay > 0 ? debounce(syncContent, delay) : syncContent) 117 | } 118 | 119 | const { onChange } = opts.editable.config 120 | 121 | is(onChange, 'function') && changes.push(onChange) 122 | }) 123 | 124 | /** 125 | * 生成编辑区初始化所需的配置对象 126 | */ 127 | function createOption() { 128 | const { mode, config, defaultContent, extendCache } = toRaw(opts).editable 129 | 130 | const option = { 131 | selector: rootRef.value, 132 | mode: mode ?? 'default', 133 | config: Object.assign({}, config, { 134 | customAlert(info, type) { 135 | opts.editable.config.customAlert?.(info, type) 136 | }, 137 | onCreated(editor) { 138 | opts.editable.config.onCreated?.(editor) 139 | }, 140 | onDestroyed(editor) { 141 | opts.editable.config.onDestroyed?.(editor) 142 | }, 143 | onMaxLength(editor) { 144 | opts.editable.config.onMaxLength?.(editor) 145 | }, 146 | onFocus(editor) { 147 | opts.editable.config.onFocus?.(editor) 148 | }, 149 | onBlur(editor) { 150 | opts.editable.config.onBlur?.(editor) 151 | formField.blur() 152 | }, 153 | onChange(editor) { 154 | nextTick(() => changes.forEach((change) => change(editor))) 155 | } 156 | } as Partial) 157 | } 158 | 159 | let data = '' 160 | 161 | if (extendCache) { 162 | data = 163 | content.jstr.length > 2 164 | ? content.jstr 165 | : Array.isArray(defaultContent) 166 | ? JSON.stringify(defaultContent) 167 | : defaultContent ?? '' 168 | } else { 169 | data = Array.isArray(defaultContent) ? JSON.stringify(defaultContent) : defaultContent ?? content.jstr 170 | } 171 | 172 | try { 173 | const json = JSON.parse(data) 174 | return { content: json, ...option } 175 | } catch (e) { 176 | return { html: data, ...option } 177 | } 178 | } 179 | 180 | const editable: Nullish = { 181 | timer: null, 182 | create() { 183 | if (!rootRef.value) return 184 | 185 | const dispatch = destory(true) 186 | 187 | instance.editable = createEditor(createOption()) 188 | 189 | syncContent() 190 | 191 | dispatch && emit('reloaded', createEvent()) 192 | 193 | if (cache.toolbar?.timer) clearTimeout(cache.toolbar.timer) 194 | cache.toolbar?.create() 195 | }, 196 | throttle() { 197 | if (editable.timer) clearTimeout(editable.timer) 198 | 199 | editable.timer = setTimeout(() => { 200 | editable.timer = null 201 | editable.create() 202 | }, opts.reloadDelay) as unknown as number 203 | } 204 | } 205 | 206 | Object.assign(cache, { 207 | editable, 208 | clearContent, 209 | syncContent 210 | }) 211 | 212 | const { create, throttle } = editable 213 | 214 | onMounted(create) 215 | onBeforeUnmount(destory) 216 | 217 | /** 218 | * 设置 v-model 数据 219 | * @param inst 编辑器实例 220 | * @param insert 执行插入操作 221 | */ 222 | function setContent(inst: IDomEditor, insert: Function) { 223 | const focus = inst.isFocused() 224 | const disable = opts.editable.config.readOnly 225 | const selection = JSON.stringify(inst.selection) 226 | 227 | disable && inst.enable() 228 | inst.clear() 229 | 230 | insert() 231 | 232 | if (disable) { 233 | inst.disable() 234 | } else if (!focus) { 235 | inst.blur() 236 | } else { 237 | try { 238 | inst.select(JSON.parse(selection)) // 恢复选区 239 | } catch (e) { 240 | inst.select(SlateEditor.start(inst, [])) // 选中开头 241 | } 242 | } 243 | } 244 | 245 | // 监听 v-model:json 246 | watch( 247 | () => props.json, 248 | (json) => { 249 | if (!modelJson.value) return 250 | 251 | const value = toRaw(json)! 252 | 253 | if (value === content.json || value === content.jstr) return 254 | 255 | let jstr = is(value, 'array') ? JSON.stringify(value) : value 256 | 257 | if (jstr === content.jstr) return 258 | 259 | try { 260 | json = JSON.parse(jstr) 261 | } catch (e) { 262 | json = [] 263 | jstr = '' 264 | console.error( 265 | Error(`v-model:json${modelJstr.value ? '.string' : ''}'s value is not a json string or object!`) 266 | ) 267 | } 268 | 269 | const { editable: inst } = instance 270 | 271 | if (inst) { 272 | setContent(inst, () => inst.insertFragment(json as Array)) 273 | } else { 274 | Object.assign(content, { json, jstr }) 275 | } 276 | }, 277 | { immediate: true, deep: true } 278 | ) 279 | 280 | // v-model:html 281 | watch( 282 | () => props.html, 283 | (html) => { 284 | // 以 v-model:json 为主,无 v-model:json 时才会继续操作 285 | if (modelJson.value || !modelHtml.value) return 286 | 287 | html = html ?? '' 288 | 289 | if (content.html === html) return 290 | 291 | const { editable: inst } = instance 292 | 293 | if (inst) { 294 | setContent(inst, () => { 295 | // @ts-ignore 296 | SlateTransforms.setNodes(inst!, { type: 'paragraph' }, { mode: 'highest' }) 297 | inst.dangerouslyInsertHtml(html as string) 298 | }) 299 | } else { 300 | content.html = html 301 | } 302 | }, 303 | { immediate: true } 304 | ) 305 | 306 | // 编辑器支持重载的配置项 307 | watch(() => opts.editable.mode, throttle) 308 | watch(() => opts.editable.config.maxLength, throttle) 309 | watch(() => opts.editable.config.decorate, throttle) 310 | watch(() => opts.editable.config.customPaste, throttle) 311 | watch(() => opts.editable.config.hoverbarKeys, throttle, { deep: true }) 312 | watch(() => opts.editable.config.MENU_CONF, throttle, { deep: true }) 313 | watch(() => opts.editable.config.EXTEND_CONF, throttle, { deep: true }) 314 | 315 | // readOnly 316 | watch( 317 | () => opts.editable.config.readOnly, 318 | (nv) => { 319 | const { editable: inst } = instance 320 | inst && (nv ? inst.disable() : inst.enable()) 321 | } 322 | ) 323 | 324 | // placeholder 325 | watch( 326 | () => opts.editable.config.placeholder, 327 | (nv) => { 328 | const target = rootRef.value?.querySelector('.w-e-text-placeholder') 329 | if (target instanceof HTMLElement) target.innerHTML = nv ?? '' 330 | } 331 | ) 332 | 333 | // scroll 334 | watch( 335 | () => opts.editable.config.scroll, 336 | (nv) => { 337 | const target = rootRef.value?.querySelector('.w-e-scroll') 338 | if (target instanceof HTMLElement) target.style.overflowY = nv ? 'auto' : '' 339 | } 340 | ) 341 | 342 | /** 343 | * 更新数据,将编辑器内容同步到父组件。 344 | */ 345 | function syncContent() { 346 | if (!modelJson.value && !modelHtml.value) return 347 | 348 | const { editable: inst } = instance 349 | if (!inst) return 350 | 351 | content.json = inst.isEmpty() ? [] : inst.children 352 | const { json, jstr: oldJstr } = content 353 | const jstr = json.length ? JSON.stringify(json) : '' 354 | 355 | if (oldJstr === jstr) return 356 | 357 | Object.assign(content, { json, jstr }) 358 | 359 | if (modelJstr.value) { 360 | emit('update:json', jstr) 361 | } else if (modelJson.value) { 362 | emit('update:json', json) 363 | } 364 | 365 | if (modelHtml.value) { 366 | content.html = inst.isEmpty() ? '' : inst.getHtml() 367 | emit('update:html', content.html) 368 | } 369 | 370 | formField.change() 371 | } 372 | 373 | /** 374 | * 清除组件中的富文本内容和缓存 375 | */ 376 | function clearContent() { 377 | const { editable: inst } = instance 378 | 379 | // 为初始 || 无内容 380 | if (!inst || inst.isEmpty()) return 381 | 382 | const disable = inst.isDisabled() 383 | 384 | inst.enable() 385 | 386 | inst.clear() 387 | 388 | disable && inst.disable() 389 | 390 | syncContent() 391 | } 392 | 393 | expose({ rootRef, modelJson, modelJstr, modelHtml, instance, syncContent, clearContent }) 394 | 395 | return { 396 | rootRef, 397 | globalAttrs, 398 | modelJson, 399 | modelJstr, 400 | modelHtml, 401 | content, 402 | instance, 403 | syncContent, 404 | clearContent, 405 | option: toRef(opts, 'editable') 406 | } 407 | }, 408 | render() { 409 | return h('div', { ref: 'rootRef', ...this.globalAttrs.editable }) 410 | } 411 | }) 412 | 413 | export type WeEditableExpose = { 414 | rootRef: HTMLDivElement 415 | modelJson: boolean 416 | modelJstr: boolean 417 | modelHtml: boolean 418 | instance: WeInstance 419 | syncContent: () => void 420 | clearContent: () => void 421 | } 422 | 423 | export const WeEditable = withInstall(Editable) 424 | -------------------------------------------------------------------------------- /core/src/core/editor.ts: -------------------------------------------------------------------------------- 1 | import { WeToolbar } from './toolbar' 2 | import { computed, defineComponent, h, mergeProps, PropType } from 'vue' 3 | import { EditableEmits, EditableProps, WeEditable, WeEditableProps } from './editable' 4 | import { omit, pick, withInstall } from './utils' 5 | import { useWeContent } from './hooks' 6 | import { WeReloadEvent } from './types' 7 | 8 | // const PierceEmits = EditableEmits.map((e) => `on${e.charAt(0).toUpperCase()}${e.slice(1)}`) 9 | 10 | const Editor = defineComponent({ 11 | inheritAttrs: false, 12 | name: 'WeEditor', 13 | props: { 14 | toolbarAttrs: { 15 | type: Object as PropType>, 16 | default: () => ({}) 17 | }, 18 | editableAttrs: { 19 | type: Object as PropType>, 20 | default: () => ({}) 21 | }, 22 | ...EditableProps 23 | }, 24 | emits: [...EditableEmits], 25 | setup(props, { attrs, emit }) { 26 | const { globalAttrs, instance } = useWeContent(props.handle) 27 | 28 | const editorProps = computed(() => { 29 | return mergeProps(omit(attrs, ['toolbarAttrs', 'editableAttrs']), globalAttrs.value.editor) 30 | }) 31 | 32 | const editableEmits = EditableEmits.reduce((events, e) => { 33 | events[`on${e.charAt(0).toUpperCase()}${e.slice(1)}`] = (data) => emit(e, data) 34 | return events 35 | }, {} as Record void>) 36 | 37 | const editableProps = computed(() => { 38 | return mergeProps( 39 | editableEmits, 40 | pick(props, ['handle', 'json', 'jsonModifiers', 'html']), 41 | props.editableAttrs 42 | ) as WeEditableProps 43 | }) 44 | 45 | const toolbarProps = computed(() => { 46 | const { handle, toolbarAttrs } = props 47 | 48 | return mergeProps( 49 | { 50 | handle, 51 | onReload(e: WeReloadEvent) { 52 | emit('reload', e) 53 | }, 54 | onReloaded(e: WeReloadEvent) { 55 | emit('reloaded', e) 56 | } 57 | }, 58 | toolbarAttrs 59 | ) 60 | }) 61 | 62 | return { editorProps, editableProps, toolbarProps, instance } 63 | }, 64 | render() { 65 | return h('div', this.editorProps, [h(WeToolbar, this.toolbarProps), h(WeEditable, this.editableProps)]) 66 | } 67 | }) 68 | 69 | export const WeEditor = withInstall(Editor) 70 | -------------------------------------------------------------------------------- /core/src/core/hooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | App, 3 | getCurrentInstance, 4 | inject, 5 | InjectionKey, 6 | Plugin, 7 | provide, 8 | reactive, 9 | Ref, 10 | ref, 11 | shallowReactive, 12 | toRefs, 13 | watch 14 | } from 'vue' 15 | import { 16 | WeCache, 17 | WeInjectContent, 18 | WeInstance, 19 | WeOptions, 20 | WeFormField, 21 | WeFormFieldInit, 22 | WeGlobalConfig, 23 | WeGlobalAttrs, 24 | WeGlobalPropertiesName, 25 | WeGlobalConfigInit 26 | } from './types' 27 | import { deepMerge, is } from './utils' 28 | 29 | let HANDLE_INDEX = 1 30 | 31 | function defaultOptions(): WeOptions { 32 | return { 33 | reloadDelay: 500, 34 | toolbar: { 35 | mode: 'default', 36 | config: {} 37 | }, 38 | editable: { 39 | mode: 'default', 40 | defaultContent: null, 41 | config: {}, 42 | delay: 3000, 43 | extendCache: true 44 | } 45 | } 46 | } 47 | 48 | function defaultAttrs(): Required { 49 | return { editor: {}, toolbar: {}, editable: {} } 50 | } 51 | 52 | function defaultFormFieldInit(): WeFormField { 53 | return { blur() {}, change() {} } 54 | } 55 | 56 | function defaultGlobalConfig(): Required { 57 | return { opts: defaultOptions(), attrs: defaultAttrs(), formFieldInit: defaultFormFieldInit } 58 | } 59 | 60 | function createGlobalPropertyName(name: keyof WeGlobalConfig): WeGlobalPropertiesName { 61 | return `WE-${name.toUpperCase()}` as WeGlobalPropertiesName 62 | } 63 | 64 | /** 65 | * 创建 wangEditor 编辑器的全局配置注册函数 66 | * @param config - 全局配置 67 | */ 68 | export function createWeGlobalConfig(config: WeGlobalConfig): Plugin { 69 | return function (app: App) { 70 | deepMerge(defaultGlobalConfig(), config) 71 | 72 | const configRefs = toRefs(reactive(config)) 73 | const keys = Object.keys(configRefs) as (keyof WeGlobalConfig)[] 74 | 75 | keys.forEach((key) => { 76 | app.config.globalProperties[createGlobalPropertyName(key)] = configRefs[key] 77 | }) 78 | } 79 | } 80 | 81 | const GlobalPropertiesInit = { 82 | opts: defaultOptions, 83 | attrs: defaultAttrs, 84 | formFieldInit: () => defaultFormFieldInit 85 | } as { 86 | [K in keyof WeGlobalConfig]-?: WeGlobalConfigInit 87 | } 88 | 89 | export function useWeGlobalConfig(key: K): Ref>> { 90 | const properties = getCurrentInstance()?.appContext.config.globalProperties 91 | 92 | if (properties) { 93 | const name = createGlobalPropertyName(key) 94 | if (!Reflect.has(properties, name)) { 95 | properties[name] = ref(GlobalPropertiesInit[key]()) 96 | } 97 | return properties[name] 98 | } 99 | 100 | return ref(GlobalPropertiesInit[key]() as any) 101 | } 102 | 103 | /** 104 | * 创建编辑器配置 105 | * @param option - 初始化配置项 106 | */ 107 | export function useWangEditor( 108 | option: Partial> | null = null, 109 | formField?: WeFormFieldInit 110 | ): { 111 | opts: WeOptions 112 | instance: WeInstance 113 | handle: InjectionKey 114 | clearContent: () => void 115 | syncContent: () => void 116 | reloadEditor: () => void 117 | } { 118 | const opts = reactive((is(option, 'object') ? option : {}) as WeOptions) 119 | 120 | const globalOpts = useWeGlobalConfig('opts') 121 | 122 | watch( 123 | globalOpts, 124 | (options) => { 125 | if (is(options, 'object')) { 126 | deepMerge(options, opts) 127 | } 128 | }, 129 | { immediate: true, deep: true } 130 | ) 131 | 132 | const instance = shallowReactive({ 133 | editable: null, 134 | toolbar: null 135 | }) 136 | 137 | const cache: WeCache = {} 138 | 139 | const globalFormField = useWeGlobalConfig('formFieldInit') 140 | 141 | function formFieldInit(): WeFormField { 142 | return [globalFormField.value, formField].reduce((field, init) => { 143 | if (typeof init === 'function') { 144 | const { blur, change } = init() 145 | 146 | if (typeof blur === 'function') field.blur = blur 147 | if (typeof change === 'function') field.change = change 148 | } 149 | 150 | return field 151 | }, defaultFormFieldInit()) 152 | } 153 | 154 | const handle: InjectionKey = Symbol(`WE ${String(HANDLE_INDEX++).padStart(6, '0')}`) 155 | 156 | provide(handle, { 157 | opts, 158 | instance, 159 | cache, 160 | formFieldInit, 161 | globalAttrs: useWeGlobalConfig('attrs') 162 | }) 163 | 164 | function clearContent() { 165 | cache.clearContent?.() 166 | } 167 | 168 | function syncContent() { 169 | cache.syncContent?.() 170 | } 171 | 172 | function reloadEditor() { 173 | cache.editable?.create() 174 | } 175 | 176 | return { opts, instance, handle, clearContent, syncContent, reloadEditor } 177 | } 178 | 179 | /** 180 | * 获取 useWangEditor 中传递的 provide 数据 181 | * @param handle - 句柄 182 | */ 183 | export function useWeContent(handle: InjectionKey): WeInjectContent { 184 | return ( 185 | inject(handle) ?? { 186 | cache: {}, 187 | opts: reactive(defaultOptions()), 188 | instance: shallowReactive({ 189 | toolbar: null, 190 | editable: null 191 | }), 192 | globalAttrs: useWeGlobalConfig('attrs'), 193 | formFieldInit: useWeGlobalConfig('formFieldInit').value 194 | } 195 | ) 196 | } 197 | -------------------------------------------------------------------------------- /core/src/core/toolbar.ts: -------------------------------------------------------------------------------- 1 | import { createToolbar } from '@wangeditor/editor' 2 | import { defineComponent, h, InjectionKey, onBeforeUnmount, PropType, Ref, ref, toRaw, unref, watch } from 'vue' 3 | import { useWeContent } from './hooks' 4 | import { Nullish, WeCache, WeInjectContent, WeToolbarReloadEvent } from './types' 5 | import { withInstall } from './utils' 6 | 7 | const Toolbar = defineComponent({ 8 | name: 'WeToolbar', 9 | props: { 10 | handle: { 11 | type: Symbol as PropType>, 12 | required: true 13 | } 14 | }, 15 | emits: ['reload', 'reloaded'], 16 | setup(props, { emit }) { 17 | const rootRef = ref() as Ref 18 | 19 | const { cache, opts, instance, globalAttrs } = useWeContent(props.handle) 20 | 21 | /** 22 | * 生成 reload/reloaded 事件传递的数据 23 | */ 24 | function createEvent(): WeToolbarReloadEvent { 25 | return { 26 | type: 'toolbar', 27 | instance: instance.toolbar! 28 | } 29 | } 30 | 31 | /** 32 | * 菜单栏销毁 33 | * @param dispatch 是否需要触发 reload 事件,用于区分销毁前的重建和组件的销毁 34 | * @return 是否需要触发 reloaded 事件 35 | */ 36 | function destory(dispatch?: boolean): boolean { 37 | if (!instance.toolbar) return false 38 | 39 | dispatch && emit('reload', createEvent()) 40 | 41 | instance.toolbar.destroy() 42 | instance.toolbar = null 43 | return true 44 | } 45 | 46 | const toolbar: Nullish = { 47 | timer: null, 48 | create() { 49 | const selector = unref(rootRef) 50 | 51 | if (!instance.editable || !selector) return 52 | 53 | // 是否需要触发 reloaded 事件 54 | const dispatch = destory(true) 55 | 56 | delete selector.dataset.wEToolbar 57 | 58 | instance.toolbar = createToolbar({ 59 | selector, 60 | editor: instance.editable, 61 | ...toRaw(opts).toolbar 62 | }) 63 | 64 | dispatch && emit('reloaded', createEvent()) 65 | }, 66 | throttle() { 67 | if (toolbar.timer) clearTimeout(toolbar.timer) 68 | if (cache.editable?.timer) return 69 | 70 | toolbar.timer = setTimeout(() => { 71 | toolbar.timer = null 72 | toolbar.create() 73 | }, opts.reloadDelay) 74 | } 75 | } 76 | 77 | watch(() => opts.toolbar, toolbar.throttle, { deep: true }) 78 | 79 | cache.toolbar = toolbar 80 | 81 | onBeforeUnmount(() => { 82 | destory() 83 | delete cache.toolbar 84 | }) 85 | 86 | return { rootRef, globalAttrs, instance } 87 | }, 88 | render() { 89 | return h('div', { ref: 'rootRef', ...this.globalAttrs.toolbar }) 90 | } 91 | }) 92 | 93 | export const WeToolbar = withInstall(Toolbar) 94 | -------------------------------------------------------------------------------- /core/src/core/types.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from 'vue' 2 | import { IDomEditor, IEditorConfig, IToolbarConfig, SlateDescendant, Toolbar } from '@wangeditor/editor' 3 | 4 | export type Nullish = T extends undefined | null ? never : T 5 | 6 | /** 7 | * 编辑区配置项 8 | */ 9 | export interface WeEditableOption { 10 | /** 编辑器模式 */ 11 | mode?: 'default' | 'simple' 12 | /** 编辑器初始化的默认内容(json array 或 json string 或 html string) */ 13 | defaultContent?: SlateDescendant[] | string | null 14 | /** 编辑器配置 */ 15 | config?: Partial 16 | /** v-model 数据同步的防抖时长,默认值:3000,单位:毫秒 */ 17 | delay?: number 18 | /** 19 | * 编辑器创建时默认内容的优先级排序,默认值:true。 20 | * true:v-model:json > v-model:html > defaultContent > defaultHtml。 21 | * false: defaultContent > defaultHtml > v-model:json > v-model:html。 22 | */ 23 | extendCache?: boolean 24 | } 25 | 26 | /** 27 | * 菜单栏的配置项 28 | */ 29 | export interface WeToolbarOption { 30 | mode?: 'default' | 'simple' 31 | config?: Partial 32 | } 33 | 34 | export type WeInjectContent = { 35 | opts: WeOptions 36 | instance: WeInstance 37 | cache: WeCache 38 | globalAttrs: Ref> 39 | formFieldInit: () => WeFormField 40 | } 41 | 42 | /** 43 | * useWangEditor 的编辑器配置项 44 | */ 45 | export type WeOptions = { 46 | /** 重载的延迟时间,默认值:500,单位:毫秒 */ 47 | reloadDelay: number 48 | toolbar: T extends true ? Required : WeToolbarOption 49 | editable: T extends true ? Required : WeEditableOption 50 | } 51 | 52 | /** 53 | * useWangEditor 返回的编辑器实例 54 | */ 55 | export type WeInstance = { 56 | editable: IDomEditor | null 57 | toolbar: Toolbar | null 58 | } 59 | 60 | /** 61 | * useWangEditor 返回的句柄的形参 62 | */ 63 | export type WeHandleOption = { 64 | cache: WeCache 65 | opts: WeOptions 66 | instance: WeInstance 67 | } 68 | 69 | export type WeSetTimeout = number | null 70 | 71 | export type WeCache = { 72 | editable?: { 73 | create: () => void 74 | throttle: () => void 75 | timer: WeSetTimeout 76 | } 77 | toolbar?: { 78 | create: () => void 79 | throttle: () => void 80 | timer: WeSetTimeout 81 | } 82 | clearContent?: () => void 83 | syncContent?: () => void 84 | } 85 | 86 | /** 87 | * 编辑器内容缓存 88 | */ 89 | export type WeEditableCache = { 90 | json: SlateDescendant[] 91 | jstr: string 92 | html: string 93 | } 94 | 95 | /** 96 | * 菜单栏重载事件 97 | */ 98 | export type WeToolbarReloadEvent = { 99 | type: 'toolbar' 100 | instance: Toolbar 101 | } 102 | 103 | /** 104 | * 编辑区重载事件 105 | */ 106 | export type WeEditableReloadEvent = { 107 | type: 'editable' 108 | instance: IDomEditor 109 | } 110 | 111 | /** 112 | * 重载事件 113 | */ 114 | export type WeReloadEvent = WeToolbarReloadEvent | WeEditableReloadEvent 115 | 116 | /** 117 | * 表单验证触发函数 118 | */ 119 | export type WeFormField = { 120 | blur: () => void 121 | change: () => void 122 | } 123 | 124 | /** 125 | * 初始化“表单验证触发函数”的函数 126 | */ 127 | export type WeFormFieldInit = () => Partial 128 | 129 | export interface WeGlobalAttrs { 130 | editor?: Record 131 | toolbar?: Record 132 | editable?: Record 133 | } 134 | 135 | export interface WeGlobalConfig { 136 | opts?: Partial> 137 | attrs?: WeGlobalAttrs 138 | formFieldInit?: WeFormFieldInit 139 | } 140 | 141 | export type WeGlobalConfigInit = T extends undefined 142 | ? never 143 | : T extends WeFormFieldInit 144 | ? () => () => WeFormField 145 | : T extends WeGlobalAttrs 146 | ? () => Required 147 | : () => WeOptions 148 | 149 | export type WeGlobalPropertiesName = `WE-${Uppercase}` 150 | 151 | export interface TypeMap { 152 | number: number 153 | string: string 154 | boolean: boolean 155 | undefined: undefined 156 | null: null 157 | bigint: BigInt 158 | symbol: Symbol 159 | object: object 160 | array: Array 161 | date: Date 162 | map: Map 163 | set: Set 164 | weakmap: WeakMap 165 | weakset: WeakSet 166 | function: Function 167 | regexp: RegExp 168 | } 169 | 170 | /** 171 | * 从数组中获取值类型 172 | */ 173 | export type ArrayValudOf = T extends ArrayLike ? U : never 174 | -------------------------------------------------------------------------------- /core/src/core/utils.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import deepClone from 'lodash.clonedeep' 3 | import { ArrayValudOf, TypeMap } from './types' 4 | 5 | /** 6 | * 类型判断 7 | * @param tar 需要被判断类型的变量 8 | * @param type 数据类型 9 | */ 10 | export function is(tar: any, type: T): tar is TypeMap[T] { 11 | return ( 12 | Object.prototype.toString 13 | .call(tar) 14 | .replace(/(\[.+?\s|\])/g, '') 15 | .toLowerCase() === type 16 | ) 17 | } 18 | 19 | /** 20 | * 给组件包装 install 函数 21 | * @param component 组件 22 | */ 23 | export function withInstall(component: T) { 24 | function install(app: App) { 25 | app.component(component.name!, component) 26 | } 27 | 28 | return Object.assign(component, { install }) 29 | } 30 | 31 | /** 32 | * 数据合并(深度合并) 33 | * @param from 被合并的 34 | * @param to 合并到 35 | * @param compel 强制覆盖 36 | */ 37 | export function deepMerge(from: T1, to: T2, compel: boolean = false) { 38 | for (let key in from) { 39 | const value = from[key] 40 | 41 | if (Reflect.has(to, key)) { 42 | const oldValue = Reflect.get(to, key) 43 | if (is(oldValue, 'object') && is(value, 'object')) { 44 | deepMerge(value, oldValue, compel) 45 | continue 46 | } else if (!compel) { 47 | continue 48 | } 49 | } 50 | 51 | Reflect.set(to, key, is(value, 'object') ? deepClone(value) : value) 52 | } 53 | } 54 | 55 | /** 56 | * 对 TypeScript 类型推导 Pick 的 JavaScript 实现 57 | * @param data 数据源 58 | * @param keys 被保留的键 59 | */ 60 | export function pick(data: T, keys: K[]) { 61 | const temp = {} as Pick> 62 | keys.forEach((key) => Reflect.set(temp, key, data[key])) 63 | return temp 64 | } 65 | 66 | /** 67 | * 对 TypeScript 类型推导 Omit 的 JavaScript 实现 68 | * @param data 数据源 69 | * @param keys 被剔除的键 70 | */ 71 | export function omit(data: T, keys: K[]) { 72 | const temp: Omit> = { ...data } 73 | keys.forEach((key) => Reflect.deleteProperty(temp, key)) 74 | return temp 75 | } 76 | -------------------------------------------------------------------------------- /core/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | WeCache, 3 | WeEditableCache, 4 | WeEditableOption, 5 | WeEditableReloadEvent, 6 | WeFormField, 7 | WeFormFieldInit, 8 | WeGlobalAttrs, 9 | WeGlobalConfig, 10 | WeGlobalConfigInit, 11 | WeInstance, 12 | WeInjectContent, 13 | WeOptions, 14 | WeReloadEvent, 15 | WeSetTimeout, 16 | WeToolbarReloadEvent, 17 | WeToolbarOption, 18 | } from './core/types' 19 | export type { WeEditableExpose, WeEditableProps } from './core/editable' 20 | export { createWeGlobalConfig, useWangEditor, useWeContent, useWeGlobalConfig } from './core/hooks' 21 | export { WeEditor } from './core/editor' 22 | export { WeEditable } from './core/editable' 23 | export { WeToolbar } from './core/toolbar' 24 | -------------------------------------------------------------------------------- /core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "outDir": "dist/es", 16 | "declaration": true, 17 | "declarationDir": "dist/types", 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "listEmittedFiles": true, 21 | "types": ["@types/lodash.clonedeep", "@types/lodash.debounce"] 22 | }, 23 | "include": ["./src/**/*.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /core/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import { defineConfig } from 'vite' 3 | const { name, version } = JSON.parse(readFileSync('./package.json') as any) 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | build: { 8 | outDir: 'dist/lib', 9 | sourcemap: true, 10 | lib: { 11 | entry: 'src/index.ts', 12 | name: 'wangEditor5ForVue3', 13 | formats: ['es', 'umd'], 14 | fileName: (format) => { 15 | switch (format) { 16 | case 'es': 17 | return `index.esm.js` 18 | case 'umd': 19 | return `index.umd.cjs` 20 | default: 21 | return `index.${format}.js` 22 | } 23 | }, 24 | }, 25 | rollupOptions: { 26 | external: ['vue', '@wangeditor/editor'], 27 | output: { 28 | banner: `/* ${name} v${version} */`, 29 | footer: `/* ${name} version ${version} */`, 30 | globals: { 31 | vue: 'Vue', 32 | '@wangeditor/editor': 'wangEditor', 33 | }, 34 | }, 35 | }, 36 | }, 37 | }) 38 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "run-s v0:build v1:build", 8 | "v0:dev": "vuepress dev v0", 9 | "v0:build": "vuepress build v0", 10 | "v1:dev": "vuepress dev v1", 11 | "v1:build": "vuepress build v1" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "vue": "^3.2.39" 18 | }, 19 | "devDependencies": { 20 | "@vuepress/client": "2.0.0-beta.51", 21 | "vuepress": "2.0.0-beta.51" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/v0/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | import { defineUserConfig, defaultTheme } from 'vuepress' 2 | 3 | export default defineUserConfig({ 4 | // 站点配置 5 | lang: 'zh-CN', 6 | title: 'wangeditor5-for-vue3 v0.x', 7 | description: '支持动态配置的 wangEditor5 for vue3 组件', 8 | base: '/wangeditor5-for-vue3/v0/', 9 | 10 | theme: defaultTheme({ 11 | // logo: 'https://www.wangeditor.com/v5/image/logo.png', 12 | locales: { 13 | '/': { 14 | lang: 'zh-CN', 15 | selectLanguageName: '简体中文', 16 | navbar: [ 17 | { text: '快速开始', link: '/guide/' }, 18 | { text: '增强模式', link: '/shadow/' }, 19 | { 20 | text: 'keywords', 21 | children: [ 22 | { text: 'WeToolbar/WeEditable', link: '/guide/#wetoolbar-weeditable' }, 23 | { text: 'WeEditor', link: '/guide/#weeditor' }, 24 | { text: 'WeEditorPlus', link: '/shadow/start.md#使用示例' }, 25 | { text: 'useWangEditor', link: '/guide/use-wang-editor.md' }, 26 | { text: 'clearContent', link: '/guide/use-wang-editor.md#clearcontent' }, 27 | { text: 'syncContent', link: '/guide/use-wang-editor.md#synccontent' }, 28 | { text: 'getToolbar', link: '/guide/use-wang-editor.md#gettoolbar' }, 29 | { text: 'getEditable', link: '/guide/use-wang-editor.md#geteditable' }, 30 | { text: 'reloadEditor', link: '/guide/use-wang-editor.md#reloadeditor' }, 31 | { text: 'WeEditableOption', link: '/guide/editable-option.md' }, 32 | { text: 'WeToolbarOption', link: '/guide/toolbar-option.md' }, 33 | { text: 'weEditorPlusCssRule', link: '/shadow/css-rule.md#全局样式注入' }, 34 | { text: 'WeCssRuleList', link: '/shadow/css-rule.md#组件样式注入' }, 35 | { text: '触发重载的配置项', link: '/guide/use-wang-editor.md#会触发重载的配置项' } 36 | ] 37 | }, 38 | { text: '扩展', link: '/extension/' }, 39 | { 40 | text: '使用示例', 41 | children: [ 42 | { 43 | text: 'element-plus', 44 | link: 'https://github.com/clinfc/wangeditor5-for-vue3-example' 45 | }, 46 | { 47 | text: 'ant-design-vue@3', 48 | link: 'https://github.com/clinfc/wangeditor5-for-vue3/tree/main/example/ant_design' 49 | }, 50 | { 51 | text: 'naive-ui', 52 | link: 'https://github.com/clinfc/wangeditor5-for-vue3/tree/main/example/naive_ui' 53 | }, 54 | { 55 | text: 'vue-cli', 56 | link: 'https://github.com/clinfc/wangeditor5-for-vue3/tree/main/example/vue_cli' 57 | } 58 | ] 59 | }, 60 | { 61 | text: '友情链接', 62 | children: [ 63 | { text: 'wangEditor', link: 'https://www.wangeditor.com' }, 64 | { text: 'wangeditor5-for-vue2', link: 'https://clinfc.github.io/wangeditor5-for-vue2' }, 65 | { text: 'Vue3', link: 'https://staging-cn.vuejs.org' } 66 | ] 67 | }, 68 | { 69 | text: 'version 0.x', 70 | children: [ 71 | { text: 'v0.x', link: 'https://clinfc.github.io/wangeditor5-for-vue3/v0/' }, 72 | { text: 'v1.x', link: 'https://clinfc.github.io/wangeditor5-for-vue3/' } 73 | ] 74 | }, 75 | { text: 'Github', link: 'https://github.com/clinfc/wangeditor5-for-vue3' } 76 | ], 77 | sidebar: { 78 | '/guide/': [ 79 | '/guide/README.md', 80 | '/guide/props.md', 81 | '/guide/use-wang-editor.md', 82 | '/guide/editable-option.md', 83 | '/guide/toolbar-option.md', 84 | '/guide/reloadbefore.md', 85 | '/guide/v-model.md', 86 | '/guide/form-field.md', 87 | '/guide/typescript.md', 88 | '/guide/vue-cli.md', 89 | '/guide/relevance.md', 90 | '/guide/locale.md' 91 | ], 92 | '/shadow/': ['/shadow/README.md', '/shadow/start.md', '/shadow/css-rule.md'], 93 | '/extension/': ['/extension/README.md', '/extension/toggle-mode.md'] 94 | } 95 | } 96 | } 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /docs/v0/.vuepress/public/images/relevance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clinfc/wangeditor5-for-vue3/3961c8aa0271a12fae4d5a5b3a64d3fccbafed63/docs/v0/.vuepress/public/images/relevance.png -------------------------------------------------------------------------------- /docs/v0/.vuepress/public/images/shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clinfc/wangeditor5-for-vue3/3961c8aa0271a12fae4d5a5b3a64d3fccbafed63/docs/v0/.vuepress/public/images/shadow.png -------------------------------------------------------------------------------- /docs/v0/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | lang: zh-CN 4 | heroText: wangeditor5-for-vue3 5 | tagline: 在 Vue3 中使用 wangEditor v5 6 | actions: 7 | - text: 快速开始 8 | link: /guide/README.md 9 | - text: 在线示例 10 | link: https://clinfc.github.io/wangeditor5-for-vue3-example/ 11 | type: success 12 | features: 13 | - title: 动态配置 14 | details: 符合 Vue 使用习惯,数据驱动,通过修改配置即可更新编辑器(编辑器创建后修改配置项仍生效) 15 | - title: 双向绑定 16 | details: 支持 v-model、v-model:json 和 v-model:html 三种形式的双向绑定,分别对应 json array、json string 和 html string 三种形式的数据 17 | - title: 表单验证 18 | details: 目前已支持 element-plus、ant-design-vue@^3 和 naive-ui 的表单验证,还可以自定义表单验证的执行逻辑 19 | - title: 初始内容 20 | details: 编辑器创建时的默认内容配置项支持 json array、json string 和 html string 三种格式的数据 21 | - title: 优雅切换 22 | details: defaultContent/defaultHtml + reloadEditor() 可优雅的实现在不同文章间的来回切换 23 | - title: 增强模式 24 | details: WeEditorPlus 组件能隔绝全局的样式污染,解决你因样式而造成的困扰 25 | --- 26 | 27 | ### 兼容性 28 | 29 | - `vue@^3` 30 | - `@wangeditor/editor@^5.0.0` 31 | 32 | ### 交流 33 | 34 | - 提交 [issues](https://github.com/clinfc/wangeditor5-for-vue3/issues) 35 | -------------------------------------------------------------------------------- /docs/v0/extension/README.md: -------------------------------------------------------------------------------- 1 | # 扩展 2 | 3 | ## 菜单扩展 4 | 5 | - [toggle-mode](./toggle-mode.md) 6 | -------------------------------------------------------------------------------- /docs/v0/extension/toggle-mode.md: -------------------------------------------------------------------------------- 1 | # toggle-mode 2 | 3 | > `v0.0.9+` 新增 4 | 5 | 用于切换编辑器的模式,可以对**编辑器**、**菜单栏**和**编辑区**的 `mode` 进行切换。_在此文中的**编辑器**代指**菜单栏**和**编辑区**的构成的整体。_ 6 | 7 | > [wangEditor 文档:mode 模式](https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F) 8 | 9 | ## 快速开始 10 | 11 | ```ts 12 | import { createApp } from 'vue' 13 | import { registToggleMode } from 'wangeditor5-for-vue3' 14 | import App from './App.vue' 15 | 16 | registToggleMode() 17 | 18 | createApp(App).mount('#app') 19 | ``` 20 | 21 | ## registToggleMode 22 | 23 | ```ts 24 | declare function registToggleMode(options?: RegistToggleModeOptions): void 25 | ``` 26 | 27 | ## RegistToggleModeOptions 28 | 29 | > 细节请查看 [toggle-mode types](https://github.com/clinfc/wangeditor5-for-vue3/tree/main/src/toggle-mode/types.ts) 30 | 31 | ```ts 32 | interface RegistToggleModeOptions { 33 | /** 34 | * 自定义的多语言集合 35 | */ 36 | locale?: ToggleLocale 37 | /** 38 | * 注册到默认菜单栏的显示格式。为 false 时,将取消注册到全局菜单栏配置。 39 | */ 40 | menu?: ToggleModeMenuKey | false 41 | /** 42 | * 是否在注册的菜单栏前面添加分割线 43 | */ 44 | divider?: boolean 45 | /** 46 | * 当切换方式为整体进行切换时,切换的模式是以哪一个为准。toolbar:以菜单栏为准;editable:以编辑区为准;auto:各自独立切换,相互不影响。 47 | */ 48 | standard?: Standrad 49 | } 50 | ``` 51 | 52 | ### menu 53 | 54 | 用于配置 `toggle-mode` 菜单添加到全局默认菜单栏时的具体方式。 55 | 56 | ```ts 57 | type ToggleModeMenuKey = 'toggleModeButton' | 'toggleModeSelect' 58 | ``` 59 | 60 | - `toggleModeButton` 61 | 62 | 添加到全局默认菜单栏中的为一个按钮菜单,此菜单只支持切换**编辑器**的 `mode` 模式。 63 | 64 | - `toggleModeSelect` 65 | 66 | 添加到全局默认菜单栏中的为一个下拉列表菜单,此菜单的下拉项分别对应**编辑器**、**菜单栏**和**编辑区**的 `mode` 切换选项。 67 | 68 | - `false` 69 | 70 | 不添加到全局菜单栏配置中,如果用户要使用 `toggle-mode` 菜单,需要在 `WeToolbarOption.config.toolbarKeys` 中进行配置才会生效。 71 | 72 | ### divider 73 | 74 | 这个配置项是 `menu` 的附属配置,意在控制是否在 `toggle-mode` 菜单前添加分割线 `|`。默认值:`true`。 75 | 76 | ### standard 77 | 78 | ```ts 79 | type Standrad = 'toolbar' | 'editable' | 'auto' 80 | ``` 81 | 82 | 当进行**编辑器**级别的 `mode` 切换时,如若此时的**菜单栏**和**编辑区**的 `mode` 类型不一致,我们需要以那一个的值为准。默认值:`auto`。 83 | 84 | - `toolbar` 85 | 86 | 以**菜单栏**为准。例如:此时菜单栏的 `mode` 为 `default`,编辑区的 `mode` 为 `simple`,那么切换后的菜单栏和编辑区的 `mode` 统一为 `simple`。 87 | 88 | - `editable` 89 | 90 | 以**编辑区**为准。例如:此时菜单栏的 `mode` 为 `default`,编辑区的 `mode` 为 `simple`,那么切换后的菜单栏和编辑区的 `mode` 统一为 `default`。 91 | 92 | - `auto` 93 | 94 | 各自独立切换,相互不影响。例如:此时菜单栏的 `mode` 为 `default`,编辑区的 `mode` 为 `simple`,那么切换后的菜单栏和编辑区的 `mode` 分别为 `simple` 和 `default`。 95 | 96 | ### locale 97 | 98 | 这是多语言配置,如果你不喜欢默认的配置,那么你可以定义自己想要的,或者在这里添加其它的语言支持(会与默认配置进行合并)。默认支持 `en` 和 `zh-CN` 两种语言。 99 | 100 | ## 多语言/国际化 101 | 102 | `toggle-mode` 扩展菜单的多语言声明如下 103 | 104 | ### 声明 105 | 106 | 107 | 108 | 109 | ```js 110 | const zhTW = { 111 | mode: { 112 | title: '切換模式', 113 | editor: '${mode} 編輯器', 114 | toolbar: '${mode} 菜單欄', 115 | editable: '${mode} 編輯區', 116 | standardAuto: '切換編輯器模式', 117 | }, 118 | } 119 | ``` 120 | 121 | 122 | 123 | 124 | 125 | ```ts 126 | import { ToggleModeLanguage } from 'wangeditor5-for-vue3' 127 | 128 | const zhTW: ToggleModeLanguage = { 129 | mode: { 130 | title: '切換模式', 131 | editor: '${mode} 編輯器', 132 | toolbar: '${mode} 菜單欄', 133 | editable: '${mode} 編輯區', 134 | standardAuto: '切換編輯器模式', 135 | }, 136 | } 137 | ``` 138 | 139 | 140 | 141 | 142 | ### 注册 143 | 144 | **方法一**:使用 `@wangeditor/editor` 内置的多语言注册函数进行注册。 145 | 146 | ```ts 147 | import { i18nAddResources } from '@wangeditor/editor' 148 | 149 | i18nAddResources('zh-tw', zhTW) 150 | ``` 151 | 152 | **方法二**:使用 `wangeditor5-for-vue3` 的 `registToggleMode` 函数,在我们注册 `toggle-mode` 菜单的时候,将新增的多语言放置在多语言配置项中即可多语言的注册。 153 | 154 | ```ts 155 | import { registToggleMode } from 'wangeditor5-for-vue3' 156 | 157 | registToggleMode({ 158 | locale: { 159 | 'zh-tw': zhTW, 160 | }, 161 | }) 162 | ``` 163 | -------------------------------------------------------------------------------- /docs/v0/guide/README.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ## 安装 4 | 5 | 6 | 7 | 8 | ```sh:no-line-numbers 9 | npm install @wangeditor/editor wangeditor5-for-vue3@0.1.0 10 | ``` 11 | 12 | 13 | 14 | 15 | 16 | ```sh:no-line-numbers 17 | yarn add @wangeditor/editor wangeditor5-for-vue3@0.1.0 18 | ``` 19 | 20 | 21 | 22 | 23 | ## 组件注册 24 | 25 | ### 全局注册 26 | 27 | ```ts 28 | import { createApp } from 'vue' 29 | import { WeToolbar, WeEditable, WeEditor } from 'wangeditor5-for-vue3' 30 | import '@wangeditor/editor/dist/css/style.css' 31 | 32 | // 全局注册 WeToolbar, WeEditable,WeEditor 三个组件 33 | const app = createApp(App) 34 | 35 | app.component(WeToolbar.name, WeToolbar) 36 | app.component(WeEditable.name, WeEditable) 37 | app.component(WeEditor.name, WeEditor) 38 | 39 | app.mount('#app') 40 | ``` 41 | 42 | ### 局部注册 43 | 44 | ```ts 45 | import { defineComponent } from 'vue' 46 | import { WeEditable, WeToolbar } from 'wangeditor5-for-vue3' 47 | import '@wangeditor/editor/dist/css/style.css' 48 | 49 | export default defineComponent({ 50 | components: { WeEditable, WeToolbar } 51 | }) 52 | ``` 53 | 54 | 或 55 | 56 | ```ts 57 | import { defineComponent } from 'vue' 58 | import { WeEditor } from 'wangeditor5-for-vue3' 59 | import '@wangeditor/editor/dist/css/style.css' 60 | 61 | export default defineComponent({ 62 | components: { WeEditor } 63 | }) 64 | ``` 65 | 66 | ## 完全示例 67 | 68 | ### WeToolbar + WeEditable 69 | 70 | 在 `wangeditor5-for-vue3` 组件库中,菜单栏和编辑区分别提供了独立的组件,可供使用者在特殊场景下使用。 71 | 72 | ```vue 73 | 83 | ``` 84 | 85 | 86 | 87 | 88 | ```vue 89 | 132 | ``` 133 | 134 | 135 | 136 | 137 | 138 | ```vue 139 | 183 | ``` 184 | 185 | 186 | 187 | 188 | ### WeEditor 189 | 190 | `WeEditor` 组件将 `WeToolbar` 和 `WeEditable` 组件封装在了一个组件中,使用更方便。 191 | 192 | ```vue 193 | 208 | ``` 209 | 210 | 211 | 212 | 213 | ```vue 214 | 257 | ``` 258 | 259 | 260 | 261 | 262 | 263 | ```vue 264 | 308 | ``` 309 | 310 | 311 | 312 | -------------------------------------------------------------------------------- /docs/v0/guide/editable-option.md: -------------------------------------------------------------------------------- 1 | # WeEditableOption 2 | 3 | ```ts 4 | import { IEditorConfig, SlateDescendant } from '@wangeditor/editor' 5 | 6 | /** 7 | * 编辑器配置项 8 | */ 9 | export interface WeEditableOption { 10 | /** 编辑器模式 */ 11 | mode?: 'default' | 'simple' 12 | /** 编辑器初始化的默认内容(json array 或 json string),优先级高于 defaultHtml */ 13 | defaultContent?: SlateDescendant[] | string | null 14 | /** 编辑器初始化的默认内容(html string),优先级低于 defaultContent */ 15 | defaultHtml?: string | null 16 | /** 编辑器配置 */ 17 | config?: Partial 18 | /** v-model 数据同步的防抖时长,默认值:3000,单位:毫秒 */ 19 | delay?: number 20 | /** 21 | * 编辑器创建时默认内容的优先级排序,默认值:true。 22 | * true:v-model > v-model:json > v-model:html > defaultContent > defaultHtml。 23 | * false: defaultContent > defaultHtml > v-model > v-model:json > v-model:html。 24 | */ 25 | extendCache?: boolean 26 | /** 对编辑器实例使用 markRaw 进行标记,默认值:true */ 27 | markRaw?: boolean 28 | } 29 | ``` 30 | 31 | ## mode 32 | 33 | 编辑器的模式,更多详情请查看 [mode 模式](https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F)。 34 | 35 | ## extendCache 36 | 37 | 当 `v-model`/`v-model:json`/`v-model:html` 与 `defaultContent`/`defaultHtml` 同时使用的时候,我们可以使用 `extendCache` 配置项来控制重载后编辑器的默认内容。 38 | 39 | 当 `extendCahce` 为 `true` 时,编辑器**创建**/**重载**时显示内容的优先级为:`v-model` > `v-model:json` > `v-model:html` > `defaultContent` > `defaultHtml`。 40 | 41 | 当 `extendCache` 为 `false` 时,编辑器**创建**/**重载**时显示内容的优先级为:`defaultContent` > `defaultHtml` > `v-model` > `v-model:json` > `v-model:html`。 42 | 43 | > `false` 模式下可能会造成数据的丢失,因此在编辑器重载前一定要做好数据的保存工作,我们可以配合 `reloadbefore` 事件来进行数据的保存。 44 | 45 | ## defaultContent 和 defaultHtml 46 | 47 | `defaultContent`/`defaultHtml` 的变更默认情况下是不会触发编辑器的重载的。在编辑器已创建的情况下,如果需要将 `defaultContent`/`defaultHtml` 内容直接显示出来,我们需要通过 `reloadEditor` API 来强制重载编辑器。并且我们需要注意 `extendCache` 对编辑器创建时默认内容的影响。 48 | 49 | > `defaultContent` 和 `defaultHtml` 不建议同时使用。如果需要切换使用,可以一个赋值为 null 另一个赋值真正的值。如:你需要从 `defaultContent` 切换到 `defaultHtml`,可以先赋值 `defaultContent = null`,然后再赋值 `defaultHtml = '

标题一

段落

'` 即可。 50 | 51 | ```ts 52 | const { editable, toolbar, reloadEditor } = useWangEditor() 53 | 54 | onMounted(() => { 55 | setTimeout(() => { 56 | // 当你进行了 v-model/v-model:json/v-model:html 绑定时, 57 | // 如果你想在编辑器重载后将 defaultContent 显示为编辑器的默认内容, 58 | // 那么你需要设置 extendCache 为 false,这会导致编辑器内容的丢失, 59 | // 可以合理搭配 reloadbefore 事件进行处理 60 | editable.extendCache = false 61 | 62 | // 然后再修改配置 63 | editable.defaultContent = [{ type: 'header1', children: [{ text: '标题一' }] }] 64 | 65 | // 同时还支持字符串形式的 JSON 66 | editable.defaultContent = '[{"type":"header1","children":[{"text":"标题一"}]}]' 67 | 68 | // or:配置 HTML 字符串 69 | editable.defaultHtml = '

标题一

段落

' 70 | 71 | // 最后,你还需要强制重载编辑器 72 | reloadEditor() 73 | }, 5000) 74 | }) 75 | ``` 76 | 77 | ## config 78 | 79 | 这是编辑器的具体配置,详细的配置项以 `wangEditor v5` 官方文档为准(认准文档中的 `editorConfig` 关键字)。[前往查看](https://www.wangeditor.com/v5/editor-config.html)。 80 | 81 | ## delay 82 | 83 | 由于内部为了节约资源,对 v-model 进行了防抖处理,此参数为防抖时间的配置项。当 `delay` 的值大于零时才具有防抖效果,否则内容一切变化都将同步给 `v-model`。支持动态配置。 84 | 85 | 当 `delay > 0` 时,我们可以使用 [`syncContent`](./use-wang-editor.md#synccontent) 来强制同步 `v-model` 数据,避免数据不一致。 86 | 87 | ## markRaw 88 | 89 | 是否使用 composition api 中的 `markRaw` 对编辑区实例进行标记。标记后的好处是可以杜绝响应式特性造成的未知 bug。[查看 markRaw 详情](https://staging-cn.vuejs.org/api/reactivity-advanced.html#markraw)。 90 | -------------------------------------------------------------------------------- /docs/v0/guide/form-field.md: -------------------------------------------------------------------------------- 1 | # 表单验证 2 | 3 | `wangeditor5-for-vue3` 的组件支持在其它 UI 框架的表单中使用,同时支持 `blur` 和 `change` 两种 `trigger` 模式。 4 | 5 | 在提交表单前,或手动触发表单验证前,请使用 [`syncContent`](./use-wang-editor.md#synccontent) 来强制同步 `v-model` 数据,避免数据丢失。更多详情请查看 [`syncContent`](./use-wang-editor.md#synccontent)。 6 | 7 | ## 目前已支持的 UI 框架 8 | 9 | - `element-plus` [查看](https://github.com/clinfc/wangeditor5-for-vue3-example#表单验证) 10 | - `ant-design-vue@^3` [查看](https://github.com/clinfc/wangeditor5-for-vue3/tree/main/example/ant_design#表单验证) 11 | - `naive-ui` [查看](https://github.com/clinfc/wangeditor5-for-vue3/tree/main/example/naive_ui#表单验证) 12 | 13 | > 如果你希望能支持某个 UI 库的表单验证,可以前往 [issues](https://github.com/clinfc/wangeditor5-for-vue3/issues) 提交需求 14 | 15 | ## 表单验证定义规则 16 | 17 | ### 定义表单验证初始化函数 18 | 19 | 20 | 21 | 22 | ```js 23 | function weFormFields() { 24 | return { 25 | blurField() { 26 | // trigger: blur 的具体逻辑 27 | }, 28 | changeField() { 29 | // trigger: change 的具体逻辑 30 | }, 31 | } 32 | } 33 | ``` 34 | 35 | 36 | 37 | 38 | 39 | ```ts 40 | import { WeEditorFormFields } from 'wangeditor5-for-vue3' 41 | 42 | function weFormFields() { 43 | const formFields: WeEditorFormFields = { 44 | blurField() { 45 | // trigger: blur 的具体逻辑 46 | }, 47 | changeField() { 48 | // trigger: change 的具体逻辑 49 | }, 50 | } 51 | 52 | return formFields 53 | } 54 | ``` 55 | 56 | ```ts 57 | interface WeEditorFormFields { 58 | blurField?: () => void 59 | changeField?: () => void 60 | } 61 | ``` 62 | 63 | 64 | 65 | 66 | ### 注入表单验证初始化函数 67 | 68 | #### 全局挂载 69 | 70 | ```ts 71 | import { createApp } from 'vue' 72 | 73 | const app = createApp(App) 74 | 75 | app.config.globalProperties.$weFormFields = weFormFields 76 | 77 | app.mount('#app') 78 | ``` 79 | 80 | #### provide 81 | 82 | ```ts 83 | import { createApp } from 'vue' 84 | import { weFormFieldInjectionKey } from 'wangeditor5-for-vue3' 85 | 86 | const app = createApp(App) 87 | 88 | app.provide(weFormFieldInjectionKey, weFormFields) 89 | 90 | app.mount('#app') 91 | ``` 92 | 93 | 或 94 | 95 | ```ts 96 | import { defineComponent, provide } from 'vue' 97 | import { weFormFieldInjectionKey } from 'wangeditor5-for-vue3' 98 | 99 | export default defineComponent({ 100 | setup() { 101 | provide(weFormFieldInjectionKey, weFormFields) 102 | 103 | return {} 104 | }, 105 | }) 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/v0/guide/locale.md: -------------------------------------------------------------------------------- 1 | # 多语言/国际化 2 | 3 | > `v0.1.0+` 新增 4 | 5 | `wangeditor5-for-vue3` 组件库的内部报错信息支持多语言配置,默认支持**简体中文**和**英语**两种语言。 6 | 7 | 8 | 9 | 10 | ```js 11 | import { i18nAddResources } from '@wangeditor/editor' 12 | 13 | const zhTW = { 14 | vcomponent: { 15 | initialize: '你必須使用由 “useWangEditor” 函數創建的 ${component} Option!', 16 | instance: '無法獲取 ${component} 實例!', 17 | }, 18 | } 19 | 20 | // 注册繁体中文 21 | i18nAddResources('zh-tw', zhTW) 22 | ``` 23 | 24 | 25 | 26 | 27 | 28 | ```ts 29 | import { VComponentLanguage } from 'wangeditor5-for-vue3' 30 | import { i18nAddResources } from '@wangeditor/editor' 31 | 32 | const zhTW: VComponentLanguage = { 33 | vcomponent: { 34 | initialize: '你必須使用由 “useWangEditor” 函數創建的 ${component} Option!', 35 | instance: '無法獲取 ${component} 實例!', 36 | }, 37 | } 38 | 39 | // 注册繁体中文 40 | i18nAddResources('zh-tw', zhTW) 41 | ``` 42 | 43 | 44 | 45 | 46 | 如果你希望在自己的扩展中能像 `wangeditor5-for-vue3` 一样支持模板字面量语法,那么你可以依赖 `wangeditor5-for-vue3` 中的 `tt` 函数进行开发。 47 | 48 | _如果你依赖了 `tt` 函数,那么你的私人定制将离不开 `wangeditor5-for-vue3` 组件库,除非你将 `tt` 函数的代码拷贝到你的私人定制中。_ 49 | 50 | `tt` 函数的第一个参数为 `@wangeditor/editor` 导出的多语言 [t 函数](https://www.wangeditor.com/v5/i18n.html)的参数,第二个到第 n 个参数依次对应模板字符串中的变量。 51 | 52 | 53 | 54 | 55 | 56 | ```js 57 | import { i18nAddResources } from '@wangeditor/editor' 58 | import { tt } from 'wangeditor5-for-vue3' 59 | 60 | // 注册多语言 61 | i18nAddResources('zh-CN', { 62 | info: 'name:${name},age:${age},sex:${sex}', 63 | }) 64 | 65 | // 使用多语言 66 | const info = tt('info', '匿名', 18, '男') // 'name:匿名,age:18,sex:男' 67 | ``` 68 | 69 | 70 | 71 | 72 | 73 | ```ts 74 | import { i18nAddResources } from '@wangeditor/editor' 75 | import { tt } from 'wangeditor5-for-vue3' 76 | 77 | // 注册多语言 78 | i18nAddResources('zh-CN', { 79 | info: 'name:${name},age:${age},sex:${sex}', 80 | }) 81 | 82 | // 使用多语言 83 | const info = tt('info', '匿名', 18, '男') // 'name:匿名,age:18,sex:男' 84 | ``` 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /docs/v0/guide/props.md: -------------------------------------------------------------------------------- 1 | # props 2 | 3 | ## WeToolbar 4 | 5 | | prop | 描述 | 类型 | 6 | | :------------- | :--------------------- | :--------------------------- | 7 | | `option` | 菜单栏配置项 | `Required` | 8 | | `reloadbefore` | 菜单栏重载前的回调函数 | `(toolbar: Toolbar) => void` | 9 | 10 | ## WeEditable 11 | 12 | | prop | 描述 | 类型 | 13 | | :------------- | :--------------------- | :----------------------------- | 14 | | `option` | 编辑区配置项 | `Required` | 15 | | `reloadbefore` | 编辑区重载前的回到函数 | `(editor: IDomEditor) => void` | 16 | 17 | ## WeEditor 18 | 19 | | prop | 描述 | 类型 | 20 | | :--------------------- | :------------------------- | :----------------------------- | 21 | | `toolbarOption` | 菜单栏配置项 | `Required` | 22 | | `toolbarClass` | 菜单栏的 `class attribute` | `String` | 23 | | `toolbarStyle` | 菜单栏的 `style attribute` | `String` | 24 | | `toolbarReloadbefore` | 菜单栏重载前的回调函数 | `(toolbar: Toolbar) => void` | 25 | | `editableOption` | 编辑区配置项 | `Required` | 26 | | `editableClass` | 编辑区的 `class attribute` | `String` | 27 | | `editableStyle` | 编辑区的 `style attribute` | `String` | 28 | | `editableReloadbefore` | 编辑区重载前的回到函数 | `(editor: IDomEditor) => void` | 29 | -------------------------------------------------------------------------------- /docs/v0/guide/relevance.md: -------------------------------------------------------------------------------- 1 | # 组件关系 2 | 3 | 整个组件关系中,`WeToolbar` 和 `WeEditable` 是最基础的组件,在此基础上封装的 `WeEditor` 组件,而 `WeEditorPlus` 组件则是完全独立的组件。 4 | 5 | ![组件关系图](/images/relevance.png) 6 | -------------------------------------------------------------------------------- /docs/v0/guide/reloadbefore.md: -------------------------------------------------------------------------------- 1 | # reloadbefore 2 | 3 | 在编辑器重载之前,会调用 `reloadbefore` 回调。当 `WeEditableOption.extendCahce` 为 `false` 时,我们可以配置此事件进行数据提交/缓存以防止数据丢失。 4 | 5 | ```vue 6 | 10 | ``` 11 | 12 | 13 | 14 | 15 | ```vue 16 | 46 | ``` 47 | 48 | 49 | 50 | 51 | 52 | ```vue 53 | 84 | ``` 85 | 86 | 87 | 88 | 89 | ## 注意事项 90 | 91 | - `WeToolbar` 和 `WeEditable` 的 `reloadbefore` 回调可以通过 `reloadbefore prop` 进行配置 92 | - `WeEditor`/`WeEditorPlus` 需要通过 `toolbar-reloadbefore prop` 和 `editable-reloadbefore prop` 进行配置。 93 | - [会触发重载的配置项](../guide/use-wang-editor.md#会触发重载的配置项) 94 | -------------------------------------------------------------------------------- /docs/v0/guide/toolbar-option.md: -------------------------------------------------------------------------------- 1 | # WeToolbarOption 2 | 3 | ```ts 4 | import { IToolbarConfig } from '@wangeditor/editor' 5 | 6 | /** 7 | * 菜单栏的配置项 8 | */ 9 | export interface WeToolbarOption { 10 | mode?: 'default' | 'simple' 11 | config?: Partial 12 | /** 对菜单栏实例使用 markRaw 进行标记,默认值:true */ 13 | markRaw?: boolean 14 | } 15 | ``` 16 | 17 | ## mode 18 | 19 | 菜单栏的模式,更多详情请查看 [mode 模式](https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F)。 20 | 21 | ## config 22 | 23 | 这是菜单栏的具体配置,详细的配置项以 `wangEditor v5` 官方文档为准(认准文档中的 `toolbarConfig` 关键字)。[前往查看](https://www.wangeditor.com/v5/toolbar-config.html)。 24 | 25 | ## markRaw 26 | 27 | 是否使用 composition api 中的 `markRaw` 对菜单栏实例进行标记。标记后的好处是可以杜绝响应式特性造成的未知 bug。[查看 markRaw 详情](https://staging-cn.vuejs.org/api/reactivity-advanced.html#markraw)。 28 | -------------------------------------------------------------------------------- /docs/v0/guide/typescript.md: -------------------------------------------------------------------------------- 1 | # TS 2 | 3 | 如果您使用 `Volar`,请在 `tsconfig.json` 中通过 `compilerOptions.type` 指定全局组件类型。 4 | 5 | ```json 6 | { 7 | "compilerOptions": { 8 | "types": ["wangeditor5-for-vue3/global"] 9 | } 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/v0/guide/use-wang-editor.md: -------------------------------------------------------------------------------- 1 | # useWangEditor 2 | 3 | 经过 `useWangEditor` 处理后,返回的 `editable` 和 `toolbar` 分别对应**编辑器**和**菜单栏**的配置项,不过此时的配置项对象具备了响应式特性,我们可以直接修改 `editable`/`toolbar` 对应属性来**更新**或**重载**编辑器。 4 | 5 | 如果传入的 `editableOption` 和 `toolbarOption` 是响应式数据,内部将自动解除与之前的关联,也就意味着经过 `useWangEditor` 处理后得到的 `editable` 和 `toolbar` 配置对象,即使内容发生变化也不会触发之前的依赖更新!!! 6 | 7 | ## useWangEditor 的类型 8 | 9 | ```ts 10 | /** 11 | * vue hook,用于实现编辑器配置项的动态绑定 12 | * @param {Object} editableOption 编辑器主体部分的配置 13 | * @param {Object} toolbarOption 菜单栏配置 14 | * @param {Number} reloadDelay 防抖时长,用于重载的延迟控制,默认值:500,单位:毫秒 15 | */ 16 | declare function useWangEditor( 17 | editableOption: WeEditableOption | null = null, 18 | toolbarOption: WeToolbarOption | null = null, 19 | reloadDelay: number 20 | ): { 21 | editable: Required 22 | toolbar: Required 23 | getEditable: { 24 | (): IDomEditor | undefined 25 | (timeout: number): Promise 26 | } 27 | getToolbar: { 28 | (): Toolbar | undefined 29 | (timeout: number): Promise 30 | } 31 | clearContent: () => void 32 | syncContent: () => void 33 | reloadEditor: () => void 34 | } 35 | ``` 36 | 37 | ## 动态修改配置 38 | 39 | 修改 `editable` 或 `toolbar` 的属性即可。 40 | 41 | ```ts 42 | const { editable, toolbar } = useWangEditor() 43 | 44 | editable.config.placeholder = '新的 placeholder' 45 | 46 | // 切换为只读模式 47 | editable.config.readOnly = true 48 | 49 | toolbar.mode = 'simple' 50 | ``` 51 | 52 | ## API 53 | 54 | ### clearContent 55 | 56 | 不仅会清除编辑器内容,还会同步 `v-model/v-model:json/v-model:html` 数据。 57 | 58 | ```ts 59 | const { clearContent } = useWangEditor() 60 | 61 | clearContent() 62 | ``` 63 | 64 | ### syncContent 65 | 66 | > `v0.0.7+` 新增 67 | 68 | 由于组件内部对 `v-model` 的数据更新做了防抖处理(防抖时长由 `WeEditableOption.delay` 控制)。当 `delay` 的数值稍大,我们在输入内容后快速点击提交表单,那么此时 `v-model` 的数据将不是最新的,这将得不偿失。因此我们可以在表单提交前执行 `syncContent` 来解除 `WeEditableOption.delay` 的副作用,强制更新 `v-model` 数据,即可防止数据丢失。 69 | 70 | 以 `element-plus` 为例,在调用 `ElForm.validate` 方法前执行 `syncContent` 方法,即可避免数据丢失。 71 | 72 | ```vue 73 | 83 | ``` 84 | 85 | 86 | 87 | 88 | ```vue 89 | 126 | ``` 127 | 128 | 129 | 130 | 131 | 132 | ```vue 133 | 172 | ``` 173 | 174 | 175 | 176 | 177 | ### getToolbar 178 | 179 | 获取菜单栏实例 180 | 181 | #### 同步模式 182 | 183 | 当我们不传入参数或传入的不是一个数字时,此时为同步模式。 184 | 185 | 186 | 187 | 188 | ```ts 189 | const { getToolbar } = useWangEditor() 190 | 191 | const toolbarInstance = getToolbar() 192 | if (toolbarInstance) { 193 | // do somthing 194 | } else { 195 | // do somthing 196 | } 197 | ``` 198 | 199 | 200 | 201 | 202 | 203 | ```ts 204 | const { getToolbar } = useWangEditor() 205 | 206 | const toolbarInstance: Toolbar | undefined = getToolbar() 207 | if (toolbarInstance) { 208 | // do somthing 209 | } else { 210 | // do somthing 211 | } 212 | ``` 213 | 214 | 215 | 216 | 217 | #### 异步模式 218 | 219 | > `v0.0.5+` 新增 220 | 221 | 当我们传入一个数字时,传入的是异步超时时间。单位:毫秒。 222 | 223 | 224 | 225 | 226 | ```ts 227 | const { getToolbar } = useWangEditor() 228 | 229 | getToolbar(3000) 230 | .then((toolbarInstance) => { 231 | // do somthing 232 | }) 233 | .catch((error) => { 234 | // do somthing 235 | }) 236 | ``` 237 | 238 | 239 | 240 | 241 | 242 | ```ts 243 | const { getToolbar } = useWangEditor() 244 | 245 | getToolbar(3000) 246 | .then((toolbarInstance: Toolbar) => { 247 | // do somthing 248 | }) 249 | .catch((error: Error) => { 250 | // do somthing 251 | }) 252 | ``` 253 | 254 | 255 | 256 | 257 | ### getEditable 258 | 259 | 获取编辑器实例 260 | 261 | #### 同步模式 262 | 263 | 当我们不传入参数或传入的不是一个数字时,此时为同步模式。 264 | 265 | 266 | 267 | 268 | ```js 269 | const { getEditable } = useWangEditor() 270 | 271 | const editableInstance = getEditable() 272 | if (editableInstance) { 273 | console.log(editableInstance.children) 274 | } else { 275 | console.error('编辑器未实例化') 276 | } 277 | ``` 278 | 279 | 280 | 281 | 282 | 283 | ```ts 284 | const { getEditable } = useWangEditor() 285 | 286 | const editableInstance: IDomEditor | undefined = getEditable() 287 | if (editableInstance) { 288 | console.log(editableInstance.children) 289 | } else { 290 | console.error('编辑器未实例化') 291 | } 292 | ``` 293 | 294 | 295 | 296 | 297 | #### 异步模式 298 | 299 | > `v0.0.5+` 新增 300 | 301 | 当我们传入一个数字时,传入的是异步超时时间。单位:毫秒。 302 | 303 | 304 | 305 | 306 | ```js 307 | const { getEditable } = useWangEditor() 308 | 309 | getEditable(3000) 310 | .then((editableInstance) => { 311 | // do somthing 312 | }) 313 | .catch((error) => { 314 | // do somthing 315 | }) 316 | ``` 317 | 318 | 319 | 320 | 321 | 322 | ```ts 323 | const { getEditable } = useWangEditor() 324 | 325 | getEditable(3000) 326 | .then((editableInstance: IDomEditor) => { 327 | // do somthing 328 | }) 329 | .catch((error: Error) => { 330 | // do somthing 331 | }) 332 | ``` 333 | 334 | 335 | 336 | 337 | ### reloadEditor 338 | 339 | 重载编辑器(销毁并重新创建)。 340 | 341 | > 重载分为编辑器重载和菜单栏重载,编辑器重载会自动触发菜单栏重载,而菜单栏重载却不会触发编辑器重载。 342 | 343 | ```ts 344 | const { reloadEditor } = useWangEditor() 345 | 346 | // 强制重载编辑器 347 | reloadEditor() 348 | ``` 349 | 350 | ## 会触发重载的配置项 351 | 352 | - 菜单栏 353 | - `WeToolbarOption` 的所有属性 354 | - 编辑器 355 | - `WeEditableOption.mode` 356 | - `WeEditableOption.config.customPaste` 357 | - `WeEditableOption.config.decorate` 358 | - `WeEditableOption.config.hoverbarKeys` 359 | - `WeEditableOption.config.maxLength` 360 | - `WeEditableOption.config.EXTEND_CONF` 361 | - `WeEditableOption.config.MENU_CONF` 362 | 363 | > `WeEditableOption` 的其它配置项虽不会触发重载,但是支持动态配置 364 | -------------------------------------------------------------------------------- /docs/v0/guide/v-model.md: -------------------------------------------------------------------------------- 1 | # 双向绑定 2 | 3 | `WeEditable`/`WeEditor`/`WeEditorPlus` 组件同时支持 `v-model`、`v-model:json` 和 `v-model:html` 三种形式的双向绑定,分别对应 `json array`、`json string` 和 `html string` 三种格式的数据。 4 | 5 | **注意事项:** 6 | 7 | - 注意 `WeEditableOption.extendCache` 可能存在的影响!!! 8 | - 当我们进行 `v-model` 绑定时,推荐使用 `shallowReactive`/`shallowRef` 来缓存 `json array` 数据。如果你执意使用 `reactive`/`ref` 进行数据缓存,那么在出现未知错误时你可能找不到问题所在。 9 | - 在提交表单前,或手动触发表单验证前,请使用 [`syncContent`](./use-wang-editor.md#synccontent) 来强制同步 `v-model` 数据,避免数据不一致。 10 | - 双向绑定多个同时使用时,存在 `v-model` > `v-model:json` > `v-model:html` 的优先级关系。即:如果你使用优先级低的来设置数据的话,设置将被拦截(设置无效)。 11 | 12 | > 最优搭配为 `v-html:json` 或 `v-model:json` + `v-model:html`。`v-model:json` 相对 `v-model` 而言,能减少大量内存消耗和计算消耗。 13 | 14 | ```vue 15 | 18 | ``` 19 | 20 | 21 | 22 | 23 | ```vue 24 | 42 | ``` 43 | 44 | 45 | 46 | 47 | 48 | ```vue 49 | 68 | ``` 69 | 70 | 71 | 72 | 73 | 或 74 | 75 | ```html 76 | 79 | ``` 80 | 81 | 82 | 83 | 84 | ```vue 85 | 103 | ``` 104 | 105 | 106 | 107 | 108 | 109 | ```vue 110 | 129 | ``` 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /docs/v0/guide/vue-cli.md: -------------------------------------------------------------------------------- 1 | # vue-cli 2 | 3 | `WeEditorPlus` 组件在 `vue-cli` 项目中无法正常使用,正则解决中... 4 | -------------------------------------------------------------------------------- /docs/v0/shadow/README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 除了常规模式外(`WeToolbar`/`WeEditable`/`WeEditor`组件),`wangeditor5-for-vue3` 还提供了增强模式(`WeEditorPlus`组件),意在解决全局样式污染的问题,让你在使用时不会因全局样式而出现不合预期的状况。 4 | 5 | 当然这不是没有代价的,虽然经过了封装,但是上手依然有些许难度,同时在[兼容性](./start.md#兼容性)上略差。 6 | 7 | 增强模式底层基于浏览器原生的 `Web Component` 和 `Shadow Dom`,请谨慎使用。 8 | 9 | 参考文档: 10 | 11 | - [WebComponents](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components) 12 | - [ShadowDom](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM) 13 | - [ShadowRoot](https://developer.mozilla.org/zh-CN/docs/Web/API/ShadowRoot) 14 | -------------------------------------------------------------------------------- /docs/v0/shadow/css-rule.md: -------------------------------------------------------------------------------- 1 | # 样式处理 2 | 3 | 我们先来看一下 `WeEditorPlus` 组件渲染完成后的 `Shadow DOM` 结构 4 | 5 | ![DOM Tree](/images/shadow.png) 6 | 7 | 上图是浏览器渲染后得到的完整 DOM 树结构,而我们需要配置的是用红线框出来那部分的样式。 8 | 9 | 我们可以通过[**全局样式注入**](#全局样式注入)或[**组件样式注入**](#组件样式注入)的方式注入样式规则,然后通过配置 `WeEditorPlus` 的 `containerClass`、`containerStyle`、`toolbarClass`、`toolbarStyle`、`editableClass` 和 `editableStyle` 这六个 `prop` 来动态设置样式。 10 | 11 | ## 样式注入 12 | 13 | ### 全局样式注入 14 | 15 | ```ts 16 | import { weEditorPlusCssRule } from 'wangeditor5-for-vue3' 17 | 18 | // 注入 css 文本 19 | weEditorPlusCssRule(`.box { font-size: 18px; color: red; }`) 20 | 21 | // 注入 css 文件路径 22 | weEditorPlusCssRule(`xxx/xxx/xxx.css`) 23 | ``` 24 | 25 | ### 组件样式注入 26 | 27 | 组件样式注入主要是通过设置 `WeEditorPlus` 组件的 `cssRule` 属性来实现 28 | 29 | ```vue 30 | 33 | ``` 34 | 35 | 36 | 37 | 38 | ```js 39 | // 可以是 css 文本 40 | const rule = `.toolbar { border: 1px solid #d9d9d9; }` 41 | 42 | // 可以是一个对象 43 | const rule = { 44 | '.container': { 45 | zIndex: 100, 46 | }, 47 | '.toolbar': { 48 | color: 'pink', 49 | }, 50 | '.editable': { 51 | height: '500px', 52 | }, 53 | } 54 | 55 | // 还可以是一个数组 56 | const rule = [ 57 | `.toolbar { border: 1px solid #d9d9d9; }`, 58 | { 59 | '.container': { 60 | zIndex: 100, 61 | }, 62 | '.toolbar': { 63 | color: 'pink', 64 | }, 65 | '.editable': { 66 | height: '500px', 67 | }, 68 | }, 69 | ] 70 | ``` 71 | 72 | 73 | 74 | 75 | 76 | ```ts 77 | import { WeCssRuleList } from 'wangeditor5-for-vue3' 78 | 79 | // 可以是 css 文本 80 | const rule: WeCssRuleList = `.toolbar { border: 1px solid #d9d9d9; }` 81 | 82 | // 可以是一个对象 83 | const rule: WeCssRuleList = { 84 | '.container': { 85 | zIndex: 100, 86 | }, 87 | '.toolbar': { 88 | color: 'pink', 89 | }, 90 | '.editable': { 91 | height: '500px', 92 | }, 93 | } 94 | 95 | // 还可以是一个数组 96 | const rule: WeCssRuleList = [ 97 | `.toolbar { border: 1px solid #d9d9d9; }`, 98 | { 99 | '.container': { 100 | zIndex: 100, 101 | }, 102 | '.toolbar': { 103 | color: 'pink', 104 | }, 105 | '.editable': { 106 | height: '500px', 107 | }, 108 | }, 109 | ] 110 | ``` 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/v0/shadow/start.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ## 组件注册 4 | 5 | ### 全局注册 6 | 7 | ```ts 8 | import App from './App.vue' 9 | 10 | import { createApp } from 'vue' 11 | import { weEditorPlusCssRule, WeEditorPlus } from 'wangeditor5-for-vue3' 12 | 13 | // 将 wangEditor 的 css 文件引入为 纯文本/路径 14 | import wecss from '@wangeditor/editor/dist/css/style.css' // vite 15 | 16 | // 注入到 shadow dom 中。 17 | // 注入的样式是全局性的(注入的 css 会影响后续创建的每一个编辑器) 18 | weEditorPlusCssRule(wecss) 19 | 20 | const app = createApp(App) 21 | 22 | app.component(WeEditorPlus.name, WeEditorPlus) 23 | 24 | app.mount('#app') 25 | ``` 26 | 27 | ### 局部注册 28 | 29 | ```ts 30 | import { defineComponent } from 'vue' 31 | import { weEditorPlusCssRule, WeEditorPlus } from 'wangeditor5-for-vue3' 32 | 33 | import wecss from '@wangeditor/editor/dist/css/style.css' // vite 34 | 35 | weEditorPlusCssRule(wecss) 36 | 37 | export default defineComponent({ 38 | components: { WeEditorPlus }, 39 | }) 40 | ``` 41 | 42 | ## 使用示例 43 | 44 | ```vue 45 | 63 | ``` 64 | 65 | 66 | 67 | 68 | ```vue 69 | 108 | ``` 109 | 110 | 111 | 112 | 113 | 114 | ```vue 115 | 155 | ``` 156 | 157 | 158 | 159 | 160 | ## props 161 | 162 | `WeEditorPlus` 继承了 [`WeEditor`](../guide/README.md#weeditor) 的所有 [`props`](../guide/props.md#weeditor) 属性,同时还存在以下 `props` 属性。 163 | 164 | | prop | 描述 | 类型 | 165 | | :--------------- | :------------------------------------------- | :-------------- | 166 | | `containerClass` | 菜单栏与编辑区公共父容器的 `class attribute` | `String` | 167 | | `containerStyle` | 菜单栏与编辑区公共父容器的 `style attribute` | `String` | 168 | | `cssRule` | 组件级的样式注入 | `WeCssRuleList` | 169 | 170 | ## 兼容性 171 | 172 | `WeEditorPlus` 组件在 `Firefox` 浏览器和 `vue-cli` 项目中无法正常使用。 173 | -------------------------------------------------------------------------------- /docs/v1/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | import { defineUserConfig, defaultTheme } from 'vuepress' 2 | 3 | export default defineUserConfig({ 4 | lang: 'zh-CN', 5 | title: 'wangeditor5-for-vue3 v1.x', 6 | description: '支持动态配置的 wangEditor5 for vue3 组件', 7 | base: '/wangeditor5-for-vue3/', 8 | 9 | theme: defaultTheme({ 10 | // logo: 'https://www.wangeditor.com/v5/image/logo.png', 11 | locales: { 12 | '/': { 13 | lang: 'zh-CN', 14 | selectLanguageName: '简体中文', 15 | navbar: [ 16 | { text: '快速开始', link: '/guide/' }, 17 | { 18 | text: '使用示例', 19 | children: [ 20 | { 21 | text: 'element-plus', 22 | link: 'https://clinfc.github.io/wangeditor5-for-vue3/element-plus/' 23 | } 24 | ] 25 | }, 26 | { 27 | text: '友情链接', 28 | children: [ 29 | { text: 'wangEditor', link: 'https://www.wangeditor.com' }, 30 | { text: 'wangeditor5-for-vue2', link: 'https://clinfc.github.io/wangeditor5-for-vue2' }, 31 | { text: 'Vue3', link: 'https://staging-cn.vuejs.org' } 32 | ] 33 | }, 34 | { 35 | text: 'version 1.x', 36 | children: [ 37 | { text: 'v0.x', link: 'https://clinfc.github.io/wangeditor5-for-vue3/v0/' }, 38 | { text: 'v1.x', link: 'https://clinfc.github.io/wangeditor5-for-vue3/' } 39 | ] 40 | }, 41 | { text: 'Github', link: 'https://github.com/clinfc/wangeditor5-for-vue3' } 42 | ], 43 | sidebar: { 44 | '/guide/': [ 45 | '/guide/README.md', 46 | '/guide/props.md', 47 | '/guide/global-config.md', 48 | '/guide/use-wang-editor.md', 49 | '/guide/v-model.md', 50 | '/guide/form-validate.md', 51 | '/guide/typescript.md' 52 | ] 53 | } 54 | } 55 | } 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /docs/v1/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | lang: zh-CN 4 | heroText: wangeditor5-for-vue3 5 | tagline: 在 Vue3 中使用 wangEditor v5 6 | actions: 7 | - text: 快速开始 8 | link: /guide/README.md 9 | - text: 在线示例 10 | link: https://clinfc.github.io/wangeditor5-for-vue3/element-plus 11 | type: success 12 | features: 13 | - title: 动态配置 14 | details: 符合 Vue 使用习惯,数据驱动,通过修改配置即可更新编辑器(编辑器创建后修改配置项仍生效) 15 | - title: 双向绑定 16 | details: 支持 v-model:json、v-model:json.string 和 v-model:html 三种形式的双向绑定,分别对应 json array、json string 和 html string 三种形式的数据 17 | - title: 表单验证 18 | details: 可以自定义表单验证的执行逻辑 19 | - title: 初始内容 20 | details: 编辑器创建时的默认内容配置项支持 json array、json string 和 html string 三种格式的数据 21 | - title: 优雅切换 22 | details: defaultContent/defaultHtml + reloadEditor() 可优雅的实现在不同文章间的来回切换 23 | --- 24 | 25 | ### 兼容性 26 | 27 | - `vue@^3.2.38` 28 | - `@wangeditor/editor@^5.1.15` 29 | 30 | ### 交流 31 | 32 | - 提交 [issues](https://github.com/clinfc/wangeditor5-for-vue3/issues) 33 | -------------------------------------------------------------------------------- /docs/v1/guide/README.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ## 前言 4 | 5 | > 此次更新是断崖式的,很多设计和实现都进行的重构,使用方式也有较大变化,请谨慎升级!!![前往 v0.x 文档](https://clinfc.github.io/wangeditor5-for-vue3/v0/) 6 | 7 | ## 安装 8 | 9 | 10 | 11 | 12 | ```sh:no-line-numbers 13 | npm install @wangeditor/editor wangeditor5-for-vue3 14 | ``` 15 | 16 | 17 | 18 | 19 | 20 | ```sh:no-line-numbers 21 | yarn add @wangeditor/editor wangeditor5-for-vue3 22 | ``` 23 | 24 | 25 | 26 | 27 | ## 组件注册 28 | 29 | ### 全局注册 30 | 31 | ```ts 32 | import '@wangeditor/editor/dist/css/style.css' 33 | import { createApp } from 'vue' 34 | import { createWeGlobalConfig, WeToolbar, WeEditable, WeEditor } from 'wangeditor5-for-vue3' 35 | import { useFormItem } from 'element-plus' // v2.2.16 36 | 37 | const app = createApp(App) 38 | 39 | // 全局注册 WeToolbar, WeEditable,WeEditor 三个组件 40 | app.use(WeToolbar) 41 | app.use(WeEditable) 42 | app.use(WeEditor) 43 | 44 | // 注册全局配置 45 | app.use( 46 | createWeGlobalConfig({ 47 | // 组件的全局 attribute 配置 48 | attrs: { 49 | toolbar: { 50 | class: 'el-we-toolbar' // WeToolbar 根节点的 class attribute 51 | }, 52 | editable: { 53 | class: 'el-we-editable' // WeEditable 根节点的 class attribute 54 | }, 55 | editor: { 56 | class: 'el-we-editor' // WeEditor 根节点的 class attribute 57 | } 58 | }, 59 | // 编辑器的全局公共配置 60 | opts: { 61 | editable: { 62 | config: { 63 | placeholder: '请输入' 64 | } 65 | } 66 | }, 67 | // 表单验证触发函数的初始化函数 68 | formFieldInit() { 69 | const { formItem } = useFormItem() 70 | 71 | return { 72 | blur() { 73 | formItem?.validate('blur').catch(debugWarn) 74 | }, 75 | change() { 76 | formItem?.validate('change').catch(debugWarn) 77 | } 78 | } 79 | } 80 | }) 81 | ) 82 | 83 | app.mount('#app') 84 | ``` 85 | 86 | ### 局部注册 87 | 88 | ```ts 89 | import '@wangeditor/editor/dist/css/style.css' 90 | import { defineComponent } from 'vue' 91 | import { WeEditable, WeToolbar } from 'wangeditor5-for-vue3' 92 | 93 | export default defineComponent({ 94 | components: { WeEditable, WeToolbar } 95 | }) 96 | ``` 97 | 98 | 或 99 | 100 | ```ts 101 | import '@wangeditor/editor/dist/css/style.css' 102 | import { defineComponent } from 'vue' 103 | import { WeEditor } from 'wangeditor5-for-vue3' 104 | 105 | export default defineComponent({ 106 | components: { WeEditor } 107 | }) 108 | ``` 109 | 110 | ## 示例 111 | 112 | ### WeToolbar + WeEditable 113 | 114 | 在 `wangeditor5-for-vue3` 组件库中,菜单栏和编辑区分别提供了独立的组件,可供使用者在特殊场景下使用。 115 | 116 | ```vue 117 | 123 | ``` 124 | 125 | 126 | 127 | 128 | ```vue 129 | 159 | ``` 160 | 161 | 162 | 163 | 164 | 165 | ```vue 166 | 197 | ``` 198 | 199 | 200 | 201 | 202 | ### WeEditor 203 | 204 | `WeEditor` 组件将 `WeToolbar` 和 `WeEditable` 组件封装在了一个组件中,使用更方便。 205 | 206 | ```vue 207 | 212 | ``` 213 | 214 | 215 | 216 | 217 | ```vue 218 | 248 | ``` 249 | 250 | 251 | 252 | 253 | 254 | ```vue 255 | 286 | ``` 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /docs/v1/guide/form-validate.md: -------------------------------------------------------------------------------- 1 | # 表单验证 2 | 3 | `wangeditor5-for-vue3` 的组件支持在其它 UI 框架的表单中使用,同时支持 `blur` 和 `change` 两种 `trigger` 模式。 4 | 5 | 在提交表单前,或手动触发表单验证前,请使用 [`syncContent`](./use-wang-editor.md#synccontent) 来强制同步 `v-model` 数据,避免数据丢失。更多详情请查看 [`syncContent`](./use-wang-editor.md#synccontent)。 6 | 7 | ## 表单验证触发函数配置 8 | 9 | ### 定义表单验证初始化函数 10 | 11 | 12 | 13 | 14 | ```js 15 | function formFieldInit() { 16 | return { 17 | blur() { 18 | // trigger: blur 的具体逻辑 19 | }, 20 | change() { 21 | // trigger: change 的具体逻辑 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | ```ts 32 | import { WeFormField, WeFormFieldInit } from 'wangeditor5-for-vue3' 33 | 34 | // 方式一 35 | function formFieldInit(): Partial { 36 | return { 37 | blur() { 38 | // trigger: blur 的具体逻辑 39 | }, 40 | change() { 41 | // trigger: change 的具体逻辑 42 | } 43 | } 44 | } 45 | 46 | // 方式二 47 | const formFieldInit: WeFormFieldInit = () => { 48 | return { 49 | blur() { 50 | // trigger: blur 的具体逻辑 51 | }, 52 | change() { 53 | // trigger: change 的具体逻辑 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | ```ts 60 | /** 61 | * 表单验证触发函数 62 | */ 63 | declare type WeFormField = { 64 | blur: () => void 65 | change: () => void 66 | } 67 | 68 | /** 69 | * 初始化“表单验证触发函数”的函数 70 | */ 71 | declare type WeFormFieldInit = () => Partial 72 | ``` 73 | 74 | 75 | 76 | 77 | ### 注入表单验证初始化函数 78 | 79 | #### 全局注册 80 | 81 | 请见 [全局注册](./README.md#全局注册) 82 | 83 | #### 局部注册 84 | 85 | ```ts 86 | import { useWangEditor } from 'wangeditor5-for-vue3' 87 | 88 | const { handle } = useWangEditor(null, formFieldInit) 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/v1/guide/global-config.md: -------------------------------------------------------------------------------- 1 | # createWeGlobalConfig 2 | 3 | 创建 `wangEditor` 编辑器的全局配置的 `vue.use` 注册函数。另见 [全局注册](./README.md#全局注册)。 4 | 5 | ```ts 6 | import { Plugin } from 'vue' 7 | 8 | declare function createWeGlobalConfig(config: WeGlobalConfig): Plugin 9 | ``` 10 | 11 | ```ts 12 | declare interface WeGlobalConfig { 13 | opts?: Partial> 14 | attrs?: WeGlobalAttrs 15 | formFieldInit?: WeFormFieldInit 16 | } 17 | ``` 18 | 19 | ```ts 20 | declare interface WeGlobalAttrs { 21 | editor?: Record 22 | toolbar?: Record 23 | editable?: Record 24 | } 25 | ``` 26 | 27 | - [WeOption](./typescript.md#weoptions) 28 | - [WeFormFieldInit](./form-validate.md#定义表单验证初始化函数) 29 | -------------------------------------------------------------------------------- /docs/v1/guide/props.md: -------------------------------------------------------------------------------- 1 | # 属性和事件 2 | 3 | ## WeToolbar 4 | 5 | ### 属性 6 | 7 | | prop | 描述 | 类型 | 必选 | 默认值 | 8 | | :------- | :-------------------- | :------- | :----- | :----- | 9 | | `handle` | `provide/inject` 句柄 | `Symbol` | `true` | - | 10 | 11 | ### 事件 12 | 13 | | event | 描述 | 回调函数 | 14 | | :--------- | :------------------- | :---------------------------------- | 15 | | `reload` | 菜单栏即将重载的事件 | `(e: WeToolbarReloadEvent) => void` | 16 | | `reloaded` | 菜单栏重载结束的事件 | `(e: WeToolbarReloadEvent) => void` | 17 | 18 | ## WeEditable 19 | 20 | ### 属性 21 | 22 | | prop | 描述 | 类型 | 必选 | 默认值 | 23 | | :-------------- | :-------------------------------------------------------------------------- | :--------------------------- | :------ | :----- | 24 | | `handle` | `provide/inject` 句柄 | `Symbol` | `true` | - | 25 | | `json(v-model)` | 双向绑定 `JSON`(`String`/`Array`) 数据。使用 `.string` 修饰符时绑定为字符串 | `SlateDescendant[]`/`string` | `false` | - | 26 | | `html(v-model)` | 双向绑定 `HTML`(`String`) 数据 | `string` | `false` | - | 27 | 28 | ### 事件 29 | 30 | | event | 描述 | 回调函数 | 31 | | :--------- | :------------------- | :----------------------------------- | 32 | | `reload` | 编辑区即将重载的事件 | `(e: WeEditableReloadEvent) => void` | 33 | | `reloaded` | 编辑区重载结束的事件 | `(e: WeEditableReloadEvent) => void` | 34 | 35 | ## WeEditor 36 | 37 | ### 属性 38 | 39 | | prop | 描述 | 类型 | 必选 | 默认值 | 40 | | :-------------- | :-------------------------------------------------------------------------- | :--------------------------- | :------ | :----- | 41 | | `handle` | `provide/inject` 句柄 | `Symbol` | `true` | - | 42 | | `json(v-model)` | 双向绑定 `JSON`(`String`/`Array`) 数据。使用 `.string` 修饰符时绑定为字符串 | `SlateDescendant[]`/`string` | `false` | - | 43 | | `html(v-model)` | 双向绑定 `HTML`(`String`) 数据 | `string` | `false` | - | 44 | | `toolbarAttrs` | 传递到 `WeToolbar` 的 `attributes` | `Record` | `false` | `{}` | 45 | | `editableAttrs` | 传递到 `WeEditable` 的 `attributes` | `Record` | `false` | `{}` | 46 | 47 | ### 事件 48 | 49 | | event | 描述 | 回调函数 | 50 | | :--------- | :-------------------------- | :--------------------------- | 51 | | `reload` | 菜单栏/编辑区即将重载的事件 | `(e: WeReloadEvent) => void` | 52 | | `reloaded` | 菜单栏/编辑区重载结束的事件 | `(e: WeReloadEvent) => void` | 53 | -------------------------------------------------------------------------------- /docs/v1/guide/typescript.md: -------------------------------------------------------------------------------- 1 | # TypeScript 2 | 3 | ## WeOptions 4 | 5 | ```ts 6 | /** 7 | * 编辑器配置项 8 | */ 9 | declare type WeOptions = { 10 | /** 重载的延迟时间,默认值:500,单位:毫秒 */ 11 | reloadDelay: number 12 | toolbar: T extends true ? Required : WeToolbarOption 13 | editable: T extends true ? Required : WeEditableOption 14 | } 15 | ``` 16 | 17 | 另见 [useWangEditor](./use-wang-editor.md) 或 [createWeGlobalConfig](./global-config.md)。 18 | 19 | ## WeToolbarOption 20 | 21 | ```ts 22 | import { IToolbarConfig } from '@wangeditor/editor' 23 | 24 | /** 25 | * 菜单栏的配置项 26 | */ 27 | export interface WeToolbarOption { 28 | mode?: 'default' | 'simple' 29 | config?: Partial 30 | } 31 | ``` 32 | 33 | ### mode 34 | 35 | 菜单栏的模式,更多详情请查看 [mode 模式](https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F)。 36 | 37 | ### config 38 | 39 | 这是菜单栏的具体配置,详细的配置项以 `wangEditor v5` 官方文档为准(认准文档中的 `toolbarConfig` 关键字)。[前往查看](https://www.wangeditor.com/v5/toolbar-config.html)。 40 | 41 | ## WeEditableOption 42 | 43 | ```ts 44 | import { IEditorConfig, SlateDescendant } from '@wangeditor/editor' 45 | 46 | /** 47 | * 编辑器配置项 48 | */ 49 | declare interface WeEditableOption { 50 | /** 编辑器模式 */ 51 | mode?: 'default' | 'simple' 52 | /** 编辑器初始化的默认内容(json array 或 json string 或 html string) */ 53 | defaultContent?: SlateDescendant[] | string | null 54 | /** 编辑器配置 */ 55 | config?: Partial 56 | /** v-model 数据同步的防抖时长,默认值:3000,单位:毫秒 */ 57 | delay?: number 58 | /** 59 | * 编辑器创建时默认内容的优先级排序,默认值:true。 60 | * true:v-model:json > v-model:html > defaultContent > defaultHtml。 61 | * false: defaultContent > defaultHtml > v-model:json > v-model:html。 62 | */ 63 | extendCache?: boolean 64 | } 65 | ``` 66 | 67 | ### mode 68 | 69 | 编辑器的模式,更多详情请查看 [mode 模式](https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F)。 70 | 71 | ### extendCache 72 | 73 | 当 `v-model:json`/`v-model:json.string`/`v-model:html` 与 `defaultContent` 同时使用的时候,我们可以使用 `extendCache` 配置项来控制重载后编辑器的默认内容。 74 | 75 | 当 `extendCahce` 为 `true` 时,编辑器**创建**/**重载**时显示内容的优先级为:`v-model:json`/`v-model:json.string` > `v-model:html` > `defaultContent`。 76 | 77 | 当 `extendCache` 为 `false` 时,编辑器**创建**/**重载**时显示内容的优先级为:`defaultContent` > `v-model:json`/`v-model:json.string` > `v-model:html`。 78 | 79 | > `false` 模式下可能会造成数据的丢失,因此在编辑器重载前一定要做好数据的保存工作,我们可以配合 `reload` 事件来进行数据的保存。 80 | 81 | ### defaultContent 82 | 83 | `defaultContent` 的变更默认情况下是不会触发编辑器的重载的。在编辑器已创建的情况下,如果需要将 `defaultContent` 内容直接显示出来,我们需要通过 [reloadEditor](./use-wang-editor.md#reloadeditor) API 来强制重载编辑器。并且我们需要注意 `extendCache` 对编辑器创建时默认内容的影响。 84 | 85 | ```ts 86 | const { opts, reloadEditor } = useWangEditor() 87 | 88 | onMounted(() => { 89 | setTimeout(() => { 90 | // 当你进行了 v-model:json/v-model:json.string/v-model:html 绑定时, 91 | // 如果你想在编辑器重载后将 defaultContent 显示为编辑器的默认内容, 92 | // 那么你需要设置 extendCache 为 false,这会导致编辑器内容的丢失, 93 | // 可以合理搭配 reload 事件进行处理 94 | opts.editable.extendCache = false 95 | 96 | // 然后再修改配置 97 | opts.editable.defaultContent = [{ type: 'header1', children: [{ text: '标题一' }] }] 98 | 99 | // 同时还支持字符串形式的 JSON 100 | opts.editable.defaultContent = '[{"type":"header1","children":[{"text":"标题一"}]}]' 101 | 102 | // or:配置 HTML 字符串 103 | opts.editable.defaultContent = '

标题一

段落

' 104 | 105 | // 最后,你还需要强制重载编辑器 106 | reloadEditor() 107 | }, 5000) 108 | }) 109 | ``` 110 | 111 | ### config 112 | 113 | 这是编辑器的具体配置,详细的配置项以 `wangEditor v5` 官方文档为准(认准文档中的 `editorConfig` 关键字)。[前往查看](https://www.wangeditor.com/v5/editor-config.html)。 114 | 115 | ### delay 116 | 117 | 由于内部为了节约资源,对 v-model 进行了防抖处理,此参数为防抖时间的配置项。当 `delay` 的值大于零时才具有防抖效果,否则内容一切变化都将同步给 `v-model`。支持动态配置。 118 | 119 | 当 `delay > 0` 时,我们可以使用 [`syncContent`](./use-wang-editor.md#synccontent) 来强制同步 `v-model` 数据,避免数据不一致。 120 | 121 | ## Volar 122 | 123 | 如果您使用 `Volar`,请在 `tsconfig.json` 中通过 `compilerOptions.type` 指定全局组件类型。 124 | 125 | ```json 126 | { 127 | "compilerOptions": { 128 | "types": ["wangeditor5-for-vue3/global"] 129 | } 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /docs/v1/guide/use-wang-editor.md: -------------------------------------------------------------------------------- 1 | # useWangEditor 2 | 3 | 初始化一个菜单栏与编辑区相关联的配置项。 4 | 5 | ```ts 6 | declare function useWangEditor( 7 | option?: Partial> | null, 8 | formField?: WeFormFieldInit 9 | ): { 10 | opts: WeOptions 11 | instance: WeInstance 12 | handle: InjectionKey 13 | clearContent: () => void 14 | syncContent: () => void 15 | reloadEditor: () => void 16 | } 17 | ``` 18 | 19 | ## opts 20 | 21 | 具有响应式特性的编辑器配置项,可直接修改配置项去动态配置编辑器。 22 | 23 | ```ts 24 | const { opts } = useWangEditor() 25 | 26 | // 设置编辑器的菜单栏模式 27 | opts.toolbar.mode = 'simple' 28 | 29 | // 修改编辑器的编辑区模式 30 | opts.editable.mode = 'simple' 31 | ``` 32 | 33 | ### 会触发重载的配置项 34 | 35 | - 菜单栏 36 | - `WeToolbarOption` 的所有属性 37 | - 编辑器 38 | - `WeEditableOption.mode` 39 | - `WeEditableOption.config.customPaste` 40 | - `WeEditableOption.config.decorate` 41 | - `WeEditableOption.config.hoverbarKeys` 42 | - `WeEditableOption.config.maxLength` 43 | - `WeEditableOption.config.EXTEND_CONF` 44 | - `WeEditableOption.config.MENU_CONF` 45 | 46 | > `WeEditableOption` 的其它配置项虽不会触发重载,但是支持动态配置。 47 | 48 | ## instance 49 | 50 | 菜单栏和编辑区的实例。 51 | 52 | ```ts 53 | import { Toolbar, IDomEditor } from '@wangeditor/editor' 54 | 55 | declare type WeInstance = { 56 | /** 57 | * 菜单栏实例 58 | */ 59 | toolbar: Toolbar | null 60 | /** 61 | * 编辑区实例 62 | */ 63 | editable: IDomEditor | null 64 | } 65 | ``` 66 | 67 | ## handle 68 | 69 | `provide`/`inject` 句柄,`WeToolbar` 和 `WeEditable` 组件内部将通过此句柄拿取配置进行初始化,这也是 `WeToolbar` 与 `WeEditable` 进行关联的唯一凭证。 70 | 71 | ## clearContent 72 | 73 | 清除编辑器内容并同步 `v-model:json/v-model:html` 数据。 74 | 75 | ```ts 76 | const { clearContent } = useWangEditor() 77 | 78 | clearContent() 79 | ``` 80 | 81 | ## syncContent 82 | 83 | 由于组件内部对 `v-model` 的数据更新做了防抖处理(防抖时长由 `WeEditableOption.delay` 控制)。当 `delay` 的数值稍大,我们在输入内容后快速点击提交表单,那么此时 `v-model` 的数据将不是最新的,这将得不偿失。因此我们可以在表单提交前执行 `syncContent` 来强制更新 `v-model` 数据,防止数据丢失。 84 | 85 | 以 `element-plus` 为例,在调用 `ElForm.validate` 方法前执行 `syncContent` 方法,即可避免数据丢失。 86 | 87 | ```vue 88 | 98 | ``` 99 | 100 | 101 | 102 | 103 | ```vue 104 | 139 | ``` 140 | 141 | 142 | 143 | 144 | 145 | ```vue 146 | 182 | ``` 183 | 184 | 185 | 186 | 187 | ## reloadEditor 188 | 189 | 重载编辑器(销毁并重新创建)。 190 | 191 | > 重载分为编辑器重载和菜单栏重载,编辑器重载会自动触发菜单栏重载,而菜单栏重载却不会触发编辑器重载。 192 | 193 | ```ts 194 | const { reloadEditor } = useWangEditor() 195 | 196 | // 强制重载编辑器 197 | reloadEditor() 198 | ``` 199 | -------------------------------------------------------------------------------- /docs/v1/guide/v-model.md: -------------------------------------------------------------------------------- 1 | # 双向绑定 2 | 3 | `WeEditable`/`WeEditor` 组件同时支持 `v-model:json`、`v-model:json.string` 和 `v-model:html` 三种形式的双向绑定,分别对应 `json array`、`json string` 和 `html string` 三种格式的数据。 4 | 5 | **注意事项:** 6 | 7 | - 注意 [WeEditableOption.extendCache](./typescript.md#extendcache) 可能存在的影响!!! 8 | - 当我们进行 `v-model` 绑定时,推荐使用 `shallowReactive`/`shallowRef` 来缓存 `json array` 数据。如果你执意使用 `reactive`/`ref` 进行数据缓存,那么在出现未知错误时你可能找不到问题所在。 9 | - 在提交表单前,或手动触发表单验证前,请使用 [syncContent](./use-wang-editor.md#synccontent) 来强制同步 `v-model` 数据,避免数据丢失。 10 | - `JSON` 与 `HTML` 双向绑定同时使用时,存在 `v-model:json`/`v-model:json.string` > `v-model:html` 的优先级关系。即:如果你使用优先级低的来设置数据的话,设置将被拦截(设置无效)。 11 | 12 | 13 | 14 | 15 | ```vue 16 | 19 | ``` 20 | 21 | 22 | 23 | 24 | 25 | ```vue 26 | 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | ```vue 36 | 39 | ``` 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ```vue 48 | 66 | ``` 67 | 68 | 69 | 70 | 71 | 72 | ```vue 73 | 92 | ``` 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /example/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-common", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.ts", 6 | "dependencies": { 7 | "@types/prismjs": "^1.26.0", 8 | "prismjs": "^1.29.0", 9 | "vue": "^3.2.39" 10 | }, 11 | "devDependencies": { 12 | "typescript": "^4.6.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/common/src/assets/json/article.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "空", 4 | "content": [], 5 | "html": "" 6 | }, 7 | { 8 | "title": "闺怨 - 王昌龄[唐]", 9 | "content": [ 10 | { "type": "header1", "children": [{ "text": "闺怨" }], "textAlign": "center" }, 11 | { 12 | "type": "paragraph", 13 | "children": [ 14 | { "text": " 王昌龄", "bold": true }, 15 | { "text": "   " }, 16 | { "text": "〔唐代〕", "italic": true }, 17 | { "text": " " } 18 | ], 19 | "textAlign": "center" 20 | }, 21 | { "type": "paragraph", "children": [{ "text": "" }], "textAlign": "center" }, 22 | { 23 | "type": "paragraph", 24 | "children": [{ "text": "闺中少妇不知愁,春日凝妆上翠楼。(不知 一作:不曾)" }], 25 | "textAlign": "center" 26 | }, 27 | { "type": "paragraph", "children": [{ "text": "忽见陌头杨柳色,悔教夫婿觅封侯。" }], "textAlign": "center" }, 28 | { "type": "paragraph", "children": [{ "text": "" }] }, 29 | { "type": "paragraph", "children": [{ "text": "" }] }, 30 | { "type": "header2", "children": [{ "text": "译文及注释" }], "textAlign": "center" }, 31 | { "type": "header3", "children": [{ "text": "", "bold": true }], "textAlign": "left" }, 32 | { "type": "header3", "textAlign": "center", "children": [{ "bold": true, "text": "译文一" }] }, 33 | { 34 | "type": "paragraph", 35 | "children": [ 36 | { 37 | "text": "闺中少妇未曾有过相思离别之愁,在明媚的春日,她精心装扮之后兴高采烈登上翠楼。忽见野外杨柳青青春意浓,真后悔让丈夫从军边塞,建功封侯。" 38 | } 39 | ] 40 | }, 41 | { "type": "header3", "children": [{ "text": "", "bold": true }], "textAlign": "left" }, 42 | { "type": "header3", "textAlign": "center", "children": [{ "bold": true, "text": "译文二" }] }, 43 | { 44 | "type": "paragraph", 45 | "children": [ 46 | { 47 | "text": "闺阁中的少妇从来不知忧愁;初春来临细心装扮,独自登上翠楼。忽然见到路边杨柳新绿,心中一阵忧愁,悔不该叫夫君去从军建功封爵。" 48 | } 49 | ] 50 | }, 51 | { "type": "header3", "children": [{ "text": "", "bold": true }], "textAlign": "left" }, 52 | { "type": "header3", "textAlign": "center", "children": [{ "bold": true, "text": "注释" }] }, 53 | { 54 | "type": "paragraph", 55 | "children": [ 56 | { 57 | "text": "闺怨:少妇的幽怨。闺,女子卧室,借指女子。一般指少女或少妇。古人\"闺怨\"之作,一般是写少女的青春寂寞,或少妇的离别相思之情。以此题材写的诗称“闺怨诗”。\"不知愁\"一作\"不曾愁\",则诗意大减。" 58 | } 59 | ] 60 | }, 61 | { "type": "paragraph", "children": [{ "text": "凝妆:盛妆。" }] }, 62 | { "type": "paragraph", "children": [{ "text": "陌头:路边。" }] }, 63 | { "type": "paragraph", "children": [{ "text": "觅封侯:为求得封侯而从军。觅,寻求。" }] }, 64 | { "type": "paragraph", "children": [{ "text": "悔教:后悔让" }] } 65 | ], 66 | "html": "

闺怨

 王昌龄   〔唐代〕 


闺中少妇不知愁,春日凝妆上翠楼。(不知 一作:不曾)

忽见陌头杨柳色,悔教夫婿觅封侯。



译文及注释

译文一

闺中少妇未曾有过相思离别之愁,在明媚的春日,她精心装扮之后兴高采烈登上翠楼。忽见野外杨柳青青春意浓,真后悔让丈夫从军边塞,建功封侯。

译文二

闺阁中的少妇从来不知忧愁;初春来临细心装扮,独自登上翠楼。忽然见到路边杨柳新绿,心中一阵忧愁,悔不该叫夫君去从军建功封爵。

注释

闺怨:少妇的幽怨。闺,女子卧室,借指女子。一般指少女或少妇。古人'闺怨'之作,一般是写少女的青春寂寞,或少妇的离别相思之情。以此题材写的诗称“闺怨诗”。'不知愁'一作'不曾愁',则诗意大减。

凝妆:盛妆。

陌头:路边。

觅封侯:为求得封侯而从军。觅,寻求。

悔教:后悔让

" 67 | }, 68 | { 69 | "title": "蜀道难 - 李白[唐]", 70 | "content": [ 71 | { "type": "header1", "children": [{ "text": "蜀道难" }], "textAlign": "center" }, 72 | { 73 | "type": "paragraph", 74 | "children": [ 75 | { "text": " " }, 76 | { "text": "李白 ", "bold": true }, 77 | { "text": "  " }, 78 | { "text": "〔唐代〕", "italic": true }, 79 | { "text": " " } 80 | ], 81 | "textAlign": "center" 82 | }, 83 | { "type": "paragraph", "children": [{ "text": "" }] }, 84 | { "type": "paragraph", "children": [{ "text": "噫吁嚱,危乎高哉!" }], "textAlign": "center" }, 85 | { "type": "paragraph", "children": [{ "text": "蜀道之难,难于上青天!" }], "textAlign": "center" }, 86 | { "type": "paragraph", "children": [{ "text": "蚕丛及鱼凫,开国何茫然!" }], "textAlign": "center" }, 87 | { "type": "paragraph", "children": [{ "text": "尔来四万八千岁,不与秦塞通人烟。" }], "textAlign": "center" }, 88 | { "type": "paragraph", "children": [{ "text": "西当太白有鸟道,可以横绝峨眉巅。" }], "textAlign": "center" }, 89 | { "type": "paragraph", "children": [{ "text": "地崩山摧壮士死,然后天梯石栈相钩连。" }], "textAlign": "center" }, 90 | { 91 | "type": "paragraph", 92 | "children": [{ "text": "上有六龙回日之高标,下有冲波逆折之回川。" }], 93 | "textAlign": "center" 94 | }, 95 | { "type": "paragraph", "children": [{ "text": "黄鹤之飞尚不得过,猿猱欲度愁攀援。" }], "textAlign": "center" }, 96 | { "type": "paragraph", "children": [{ "text": "青泥何盘盘,百步九折萦岩峦。" }], "textAlign": "center" }, 97 | { "type": "paragraph", "children": [{ "text": "扪参历井仰胁息,以手抚膺坐长叹。" }], "textAlign": "center" }, 98 | { "type": "paragraph", "children": [{ "text": "问君西游何时还?畏途巉岩不可攀。" }], "textAlign": "center" }, 99 | { "type": "paragraph", "children": [{ "text": "但见悲鸟号古木,雄飞雌从绕林间。" }], "textAlign": "center" }, 100 | { "type": "paragraph", "children": [{ "text": "又闻子规啼夜月,愁空山。" }], "textAlign": "center" }, 101 | { 102 | "type": "paragraph", 103 | "children": [{ "text": "蜀道之难,难于上青天,使人听此凋朱颜!" }], 104 | "textAlign": "center" 105 | }, 106 | { "type": "paragraph", "children": [{ "text": "连峰去天不盈尺,枯松倒挂倚绝壁。" }], "textAlign": "center" }, 107 | { "type": "paragraph", "children": [{ "text": "飞湍瀑流争喧豗,砯崖转石万壑雷。" }], "textAlign": "center" }, 108 | { 109 | "type": "paragraph", 110 | "children": [{ "text": "其险也如此,嗟尔远道之人胡为乎来哉!(也如此 一作:也若此)" }], 111 | "textAlign": "center" 112 | }, 113 | { "type": "paragraph", "children": [{ "text": "剑阁峥嵘而崔嵬,一夫当关,万夫莫开。" }], "textAlign": "center" }, 114 | { "type": "paragraph", "children": [{ "text": "所守或匪亲,化为狼与豺。" }], "textAlign": "center" }, 115 | { 116 | "type": "paragraph", 117 | "children": [{ "text": "朝避猛虎,夕避长蛇,磨牙吮血,杀人如麻。" }], 118 | "textAlign": "center" 119 | }, 120 | { "type": "paragraph", "children": [{ "text": "锦城虽云乐,不如早还家。" }], "textAlign": "center" }, 121 | { "type": "paragraph", "children": [{ "text": "蜀道之难,难于上青天,侧身西望长咨嗟!" }], "textAlign": "center" } 122 | ], 123 | "html": "

蜀道难

 李白   〔唐代〕 


噫吁嚱,危乎高哉!

蜀道之难,难于上青天!

蚕丛及鱼凫,开国何茫然!

尔来四万八千岁,不与秦塞通人烟。

西当太白有鸟道,可以横绝峨眉巅。

地崩山摧壮士死,然后天梯石栈相钩连。

上有六龙回日之高标,下有冲波逆折之回川。

黄鹤之飞尚不得过,猿猱欲度愁攀援。

青泥何盘盘,百步九折萦岩峦。

扪参历井仰胁息,以手抚膺坐长叹。

问君西游何时还?畏途巉岩不可攀。

但见悲鸟号古木,雄飞雌从绕林间。

又闻子规啼夜月,愁空山。

蜀道之难,难于上青天,使人听此凋朱颜!

连峰去天不盈尺,枯松倒挂倚绝壁。

飞湍瀑流争喧豗,砯崖转石万壑雷。

其险也如此,嗟尔远道之人胡为乎来哉!(也如此 一作:也若此)

剑阁峥嵘而崔嵬,一夫当关,万夫莫开。

所守或匪亲,化为狼与豺。

朝避猛虎,夕避长蛇,磨牙吮血,杀人如麻。

锦城虽云乐,不如早还家。

蜀道之难,难于上青天,侧身西望长咨嗟!

" 124 | }, 125 | { 126 | "title": "长相思·其二 - 李白[唐]", 127 | "content": [ 128 | { "type": "header1", "children": [{ "text": "长相思·其二" }], "textAlign": "center" }, 129 | { 130 | "type": "paragraph", 131 | "children": [ 132 | { "text": " " }, 133 | { "text": "李白", "bold": true }, 134 | { "text": "   " }, 135 | { "text": "〔唐代〕", "italic": true }, 136 | { "text": " " } 137 | ], 138 | "textAlign": "center" 139 | }, 140 | { "type": "paragraph", "children": [{ "text": "" }], "textAlign": "center" }, 141 | { 142 | "type": "paragraph", 143 | "children": [{ "text": "日色欲尽花含烟,月明如素愁不眠。(如素 一作:欲素)" }], 144 | "textAlign": "center" 145 | }, 146 | { "type": "paragraph", "children": [{ "text": "赵瑟初停凤凰柱,蜀琴欲奏鸳鸯弦。" }], "textAlign": "center" }, 147 | { "type": "paragraph", "children": [{ "text": "此曲有意无人传,愿随春风寄燕然。" }], "textAlign": "center" }, 148 | { 149 | "type": "paragraph", 150 | "children": [{ "text": "忆君迢迢隔青天。昔日横波目,今作流泪泉。" }], 151 | "textAlign": "center" 152 | }, 153 | { 154 | "type": "paragraph", 155 | "children": [{ "text": "不信妾断肠,归来看取明镜前。(断肠 一作:肠断)" }], 156 | "textAlign": "center" 157 | }, 158 | { "type": "paragraph", "textAlign": "left", "children": [{ "text": "" }] }, 159 | { "type": "header2", "textAlign": "left", "children": [{ "text": "译文及注释" }] }, 160 | { 161 | "type": "paragraph", 162 | "textAlign": "left", 163 | "children": [ 164 | { "text": " 韵译", "bold": true }, 165 | { 166 | "text": "夕阳西下暮色朦胧,花蕊笼罩轻烟,月华如练,我思念着情郎终夜不眠。柱上雕饰凤凰的赵瑟,我刚刚停奏,心想再弹奏蜀琴,又怕触动鸳鸯弦。这饱含情意的曲调,可惜无人传递,但愿它随着春风,送到遥远的燕然。忆情郎呵、情郎他迢迢隔在天那边,当年递送秋波的双眼,而今成了流泪的源泉。您若不信贱妾怀思肝肠欲断,请归来看看明镜前我的容颜!" 167 | } 168 | ] 169 | }, 170 | { "type": "header2", "textAlign": "left", "children": [{ "text": "注释", "bold": true }] }, 171 | { "type": "paragraph", "textAlign": "left", "children": [{ "text": "赵瑟:相传古代赵国的人善弹瑟。" }] }, 172 | { "type": "paragraph", "textAlign": "left", "children": [{ "text": "瑟:弦乐器。" }] }, 173 | { "type": "paragraph", "textAlign": "left", "children": [{ "text": "凤凰柱:或是瑟柱上雕饰凤凰形状。" }] }, 174 | { 175 | "type": "paragraph", 176 | "textAlign": "left", 177 | "children": [ 178 | { 179 | "text": "蜀琴句:旧注谓蜀琴与司马相如 琴挑故事有关。按: 鲍照 有“蜀琴抽白雪”句。 白居易 也有“蜀琴安膝上,《周易》在床头”句。 李贺 “吴丝蜀桐张高秋”,王琦注云:“蜀中桐木宜为乐器,故曰蜀桐。”蜀桐实即蜀琴。似古人诗中常以蜀琴喻佳琴,恐与司马相如、 卓文君 事无关。鸳鸯弦也只是为了强对凤凰柱。" 180 | } 181 | ] 182 | } 183 | ], 184 | "html": "

长相思·其二

 李白   〔唐代〕 


日色欲尽花含烟,月明如素愁不眠。(如素 一作:欲素)

赵瑟初停凤凰柱,蜀琴欲奏鸳鸯弦。

此曲有意无人传,愿随春风寄燕然。

忆君迢迢隔青天。昔日横波目,今作流泪泉。

不信妾断肠,归来看取明镜前。(断肠 一作:肠断)


译文及注释

      韵译夕阳西下暮色朦胧,花蕊笼罩轻烟,月华如练,我思念着情郎终夜不眠。柱上雕饰凤凰的赵瑟,我刚刚停奏,心想再弹奏蜀琴,又怕触动鸳鸯弦。这饱含情意的曲调,可惜无人传递,但愿它随着春风,送到遥远的燕然。忆情郎呵、情郎他迢迢隔在天那边,当年递送秋波的双眼,而今成了流泪的源泉。您若不信贱妾怀思肝肠欲断,请归来看看明镜前我的容颜!

注释

赵瑟:相传古代赵国的人善弹瑟。

瑟:弦乐器。

凤凰柱:或是瑟柱上雕饰凤凰形状。

蜀琴句:旧注谓蜀琴与司马相如 琴挑故事有关。按: 鲍照 有“蜀琴抽白雪”句。 白居易 也有“蜀琴安膝上,《周易》在床头”句。 李贺 “吴丝蜀桐张高秋”,王琦注云:“蜀中桐木宜为乐器,故曰蜀桐。”蜀桐实即蜀琴。似古人诗中常以蜀琴喻佳琴,恐与司马相如、 卓文君 事无关。鸳鸯弦也只是为了强对凤凰柱。

" 185 | } 186 | ] 187 | -------------------------------------------------------------------------------- /example/common/src/components/IContainer/index.ts: -------------------------------------------------------------------------------- 1 | export type { AsideMenu } from './src/types' 2 | export { default as IContainer } from './src/IContainer.vue' 3 | export { default as IPage } from './src/IPage.vue' 4 | -------------------------------------------------------------------------------- /example/common/src/components/IContainer/src/IAside.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | 31 | 66 | -------------------------------------------------------------------------------- /example/common/src/components/IContainer/src/IContainer.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | 43 | 78 | -------------------------------------------------------------------------------- /example/common/src/components/IContainer/src/IHeader.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | 37 | 118 | -------------------------------------------------------------------------------- /example/common/src/components/IContainer/src/IPage.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 62 | -------------------------------------------------------------------------------- /example/common/src/components/IContainer/src/types.ts: -------------------------------------------------------------------------------- 1 | export type AsideMenu = { 2 | key: string 3 | title: string 4 | callback: () => void 5 | } 6 | -------------------------------------------------------------------------------- /example/common/src/components/IPrism/IPrism.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 46 | -------------------------------------------------------------------------------- /example/common/src/components/IPrism/index.ts: -------------------------------------------------------------------------------- 1 | export { default as IPrism } from './IPrism.vue' 2 | -------------------------------------------------------------------------------- /example/common/src/components/IScroll/IScroll.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | 25 | 48 | -------------------------------------------------------------------------------- /example/common/src/components/IScroll/index.ts: -------------------------------------------------------------------------------- 1 | export { default as IScroll } from './IScroll.vue' 2 | -------------------------------------------------------------------------------- /example/common/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './IContainer' 2 | export * from './IPrism' 3 | export * from './IScroll' 4 | -------------------------------------------------------------------------------- /example/common/src/index.ts: -------------------------------------------------------------------------------- 1 | import json from './assets/json/article.json' 2 | 3 | export type Article = { 4 | title: string 5 | content: any[] 6 | html: string 7 | } 8 | 9 | export const articleList = json as Article[] 10 | 11 | export const articleJsonList = json.map(({ title, content }) => ({ title, content })) 12 | 13 | export const articleHtmlList = json.map(({ title, html }) => ({ title, content: html })) 14 | 15 | export * from './components' 16 | -------------------------------------------------------------------------------- /example/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "declaration": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "listEmittedFiles": true 19 | }, 20 | "include": ["./src/**/*.ts", "./src/**/*.vue"] 21 | } 22 | -------------------------------------------------------------------------------- /example/elplus/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /example/elplus/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/elplus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elplus", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc --noEmit && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@wangeditor/editor": "^5.1.15", 13 | "element-plus": "^2.2.16", 14 | "example-common": "workspace:^1.0.0", 15 | "vue": "^3.2.37", 16 | "vue-router": "^4.1.5", 17 | "wangeditor5-for-vue3": "workspace:^1.0.0" 18 | }, 19 | "devDependencies": { 20 | "@vitejs/plugin-vue": "^3.1.0", 21 | "sass": "^1.54.9", 22 | "typescript": "^4.6.4", 23 | "unplugin-auto-import": "^0.11.2", 24 | "unplugin-vue-components": "^0.22.7", 25 | "vite": "^3.1.0", 26 | "vue-tsc": "^0.40.4" 27 | } 28 | } -------------------------------------------------------------------------------- /example/elplus/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/elplus/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /example/elplus/src/assets/wangeditor.scss: -------------------------------------------------------------------------------- 1 | .el-we-toolbar { 2 | border: 1px solid var(--we-bc, #e5e5e5); 3 | 4 | & + .el-we-editable { 5 | border-top: none; 6 | } 7 | } 8 | 9 | .el-we-editable { 10 | border: 1px solid var(--we-bc, #e5e5e5); 11 | height: 300px; 12 | } 13 | 14 | .el-we-editor { 15 | } 16 | 17 | .el-form-item.is-error { 18 | --we-bc: red; 19 | } 20 | -------------------------------------------------------------------------------- /example/elplus/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | 3 | import App from './App.vue' 4 | import wangeditor from './plugins/wangeditor' 5 | import router from './router/router' 6 | 7 | const app = createApp(App) 8 | 9 | app.use(wangeditor) 10 | app.use(router) 11 | 12 | app.mount('#app') 13 | -------------------------------------------------------------------------------- /example/elplus/src/plugins/wangeditor.ts: -------------------------------------------------------------------------------- 1 | import '@wangeditor/editor/dist/css/style.css' 2 | import '@assets/wangeditor.scss' 3 | import { App } from 'vue' 4 | import { createWeGlobalConfig, WeToolbar, WeEditable, WeEditor } from 'wangeditor5-for-vue3' 5 | import { useFormItem } from 'element-plus' 6 | 7 | export default function (app: App) { 8 | app.use(WeToolbar) 9 | app.use(WeEditable) 10 | app.use(WeEditor) 11 | app.use( 12 | createWeGlobalConfig({ 13 | // 组件的 attribute 配置 14 | attrs: { 15 | toolbar: { 16 | class: 'el-we-toolbar' // WeToolbar 根节点的 class attribute 17 | }, 18 | editable: { 19 | class: 'el-we-editable' // WeEditable 根节点的 class attribute 20 | }, 21 | editor: { 22 | class: 'el-we-editor' // WeEditor 根节点的 class attribute 23 | } 24 | }, 25 | // 编辑器的全局公共配置 26 | opts: { 27 | editable: { 28 | config: { 29 | placeholder: '请输入' 30 | } 31 | } 32 | }, 33 | // 表单验证逻辑的初始化函数 34 | formFieldInit() { 35 | const { formItem } = useFormItem() 36 | 37 | return { 38 | blur() { 39 | formItem?.validate('blur').catch(debugWarn) 40 | }, 41 | change() { 42 | formItem?.validate('change').catch(debugWarn) 43 | } 44 | } 45 | } 46 | }) 47 | ) 48 | } 49 | 50 | function debugWarn(error: Error) { 51 | if (process.env.NODE_ENV !== 'production') { 52 | console.warn(error) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/elplus/src/router/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' 2 | 3 | import Index from '@views/begin.vue' 4 | import { AsideMenu } from 'example-common' 5 | import { ref } from 'vue' 6 | 7 | const routes: RouteRecordRaw[] = [ 8 | { 9 | name: 'Index', 10 | path: '/', 11 | component: Index, 12 | meta: { 13 | title: '基础案例' 14 | } 15 | }, 16 | { 17 | name: 'defaultContent', 18 | path: '/default-content', 19 | component: () => import('@views/default-content.vue'), 20 | meta: { 21 | title: 'defaultContent' 22 | } 23 | }, 24 | { 25 | name: 'enableExtendCache', 26 | path: '/extend-cache/true', 27 | component: () => import('@views/extend-cache.vue'), 28 | meta: { 29 | title: '使用缓存模式' 30 | } 31 | }, 32 | { 33 | name: 'disableExtendCache', 34 | path: '/extend-cache/false', 35 | component: () => import('@views/extend-cache.vue'), 36 | meta: { 37 | title: '禁用缓存模式' 38 | } 39 | }, 40 | { 41 | name: 'async', 42 | path: '/async', 43 | component: () => import('@views/async.vue'), 44 | meta: { 45 | title: '在弹窗/抽屉中使用' 46 | } 47 | }, 48 | { 49 | name: 'reloadEvent', 50 | path: '/reload-event', 51 | component: () => import('@views/reload.vue'), 52 | meta: { 53 | title: 'reload 事件' 54 | } 55 | }, 56 | { 57 | name: 'modelJsonArray', 58 | path: '/model-json-array', 59 | component: () => import('@views/model-json-array.vue'), 60 | meta: { 61 | title: 'v-model:json' 62 | } 63 | }, 64 | { 65 | name: 'modelJsonString', 66 | path: '/model-json-string', 67 | component: () => import('@views/model-json-string.vue'), 68 | meta: { 69 | title: 'v-model:json.string' 70 | } 71 | }, 72 | { 73 | name: 'modelHtmlString', 74 | path: '/model-html-string', 75 | component: () => import('@views/model-html-string.vue'), 76 | meta: { 77 | title: 'v-model:html' 78 | } 79 | }, 80 | { 81 | name: 'FormValidate', 82 | path: '/form-validate', 83 | component: () => import('@views/form-validate.vue'), 84 | meta: { 85 | title: '表单验证' 86 | } 87 | }, 88 | { 89 | name: 'SyncContent', 90 | path: '/sync-content', 91 | component: () => import('@views/sync-content.vue'), 92 | meta: { 93 | title: '强制同步数据' 94 | } 95 | } 96 | ] 97 | 98 | const router = createRouter({ 99 | routes: routes, 100 | history: createWebHashHistory() 101 | }) 102 | 103 | router.afterEach((to, from, failure) => { 104 | if (failure) return 105 | 106 | document.title = to.meta!.title as string 107 | }) 108 | 109 | export const activeMenu = ref(router.options.history.state.current as string) 110 | 111 | export const asideMenus = routes.map((item) => { 112 | return { 113 | key: item.path, 114 | title: item.meta!.title, 115 | callback() { 116 | activeMenu.value = item.path 117 | router.push(item.path) 118 | } 119 | } as AsideMenu 120 | }) 121 | 122 | export default router 123 | -------------------------------------------------------------------------------- /example/elplus/src/views/async.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 49 | -------------------------------------------------------------------------------- /example/elplus/src/views/begin.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | -------------------------------------------------------------------------------- /example/elplus/src/views/default-content.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 58 | -------------------------------------------------------------------------------- /example/elplus/src/views/extend-cache.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 105 | -------------------------------------------------------------------------------- /example/elplus/src/views/form-validate.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 81 | 82 | 102 | -------------------------------------------------------------------------------- /example/elplus/src/views/model-html-string.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 46 | -------------------------------------------------------------------------------- /example/elplus/src/views/model-json-array.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 48 | -------------------------------------------------------------------------------- /example/elplus/src/views/model-json-string.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 46 | -------------------------------------------------------------------------------- /example/elplus/src/views/reload.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 129 | -------------------------------------------------------------------------------- /example/elplus/src/views/sync-content.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 57 | -------------------------------------------------------------------------------- /example/elplus/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /example/elplus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "types": ["wangeditor5-for-vue3/global", "element-plus/global"], 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./src/*"] 19 | } 20 | }, 21 | "include": ["./src/**/*.ts", "./src/**/*.d.ts", "./src/**/*.tsx", "./src/**/*.vue"], 22 | "references": [{ "path": "./tsconfig.node.json" }] 23 | } 24 | -------------------------------------------------------------------------------- /example/elplus/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /example/elplus/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import AutoImport from 'unplugin-auto-import/vite' 4 | import Components from 'unplugin-vue-components/vite' 5 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 6 | import { resolve } from 'path' 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | base: '/wangeditor5-for-vue3/element-plus/', 11 | plugins: [ 12 | vue(), 13 | AutoImport({ 14 | resolvers: [ElementPlusResolver()] 15 | }), 16 | Components({ 17 | resolvers: [ElementPlusResolver()] 18 | }) 19 | ], 20 | resolve: { 21 | alias: { 22 | '@assets': resolve(__dirname, 'src/assets'), 23 | '@views': resolve(__dirname, 'src/views') 24 | } 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /gulpfile.ts: -------------------------------------------------------------------------------- 1 | import { dest, series, src } from 'gulp' 2 | 3 | const folder: Record<'from' | 'to', string>[] = [ 4 | { from: 'docs/v1/.vuepress/dist/**/*', to: 'pages' }, 5 | { from: 'docs/v0/.vuepress/dist/**/*', to: 'pages/v0' }, 6 | { from: 'example/elplus/dist/**/*', to: 'pages/element-plus' } 7 | ] 8 | 9 | export const copyPages = series( 10 | ...folder.map(({ from, to }) => { 11 | return function () { 12 | return src(from).pipe(dest(to)) 13 | } 14 | }) 15 | ) 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wangeditor5-for-vue3-monorepo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "build": "run-s build:core build:shared build:docs build:elplus combine:pages", 8 | "build:core": "pnpm --filter ./core build", 9 | "build:shared": "pnpm --filter ./shared build", 10 | "build:elplus": "pnpm --filter ./example/elplus build", 11 | "build:docs": "pnpm --filter ./docs build", 12 | "combine:pages": "rimraf pages && gulp copyPages --require sucrase/register/ts -f gulpfile.ts", 13 | "elplus": "pnpm --filter ./example/elplus dev", 14 | "docs:v0": "pnpm --filter ./docs v0:dev", 15 | "docs:v1": "pnpm --filter ./docs v1:dev", 16 | "publish": "pnpm publish --filter ./core --access=public --no-git-checks" 17 | }, 18 | "devDependencies": { 19 | "@microsoft/api-extractor": "^7.30.0", 20 | "@types/gulp": "^4.0.9", 21 | "gulp": "^4.0.2", 22 | "npm-run-all": "^4.1.5", 23 | "rimraf": "^3.0.2", 24 | "sucrase": "^3.27.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | # 核心仓库 3 | - 'core/**' 4 | # 文档 5 | - 'docs/**' 6 | # 示例项目 7 | - 'example/*' 8 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------