diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index 8701b689f..000000000 --- a/.browserslistrc +++ /dev/null @@ -1,3 +0,0 @@ -defaults -not IE 11 -maintained node versions \ No newline at end of file diff --git a/.cz-config.js b/.cz-config.js deleted file mode 100644 index e72a22c02..000000000 --- a/.cz-config.js +++ /dev/null @@ -1,59 +0,0 @@ -module.exports = { - types: [ - { - value: 'WIP', - name: '💡 WIP: Work in progress', - }, - { - value: 'feat', - name: '🚀 feat: A new feature', - }, - { - value: 'fix', - name: '🔧 fix: A bug fix', - }, - { - value: 'refactor', - name: '🔨 refactor: A code change that neither fixes a bug nor adds a feature', - }, - { - value: 'release', - name: '🛳 release: Bump to a new Semantic version', - }, - { - value: 'docs', - name: '📚 docs: Documentation only changes', - }, - { - value: 'test', - name: '🔍 test: Add missing tests or correcting existing tests', - }, - { - value: 'perf', - name: '⚡️ perf: Changes that improve performance', - }, - { - value: 'chore', - name: - "🚬 chore: Changes that don't modify src or test files. Such as updating build tasks, package manager", - }, - { - value: 'workflow', - name: - '📦 workflow: Changes that only affect the workflow. Such as updating build systems or CI etc.', - }, - { - value: 'style', - name: - '💅 style: Code Style, Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)', - }, - { - value: 'revert', - name: '⏱ revert: Revert to a commit', - }, - ], - // Specify the scopes for your particular project - scopes: [], - allowCustomScopes: true, - allowBreakingChanges: ['feat', 'fix'], -} diff --git a/.eslintignore b/.eslintignore index c27f6abef..c02d10911 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,2 @@ -node_modules/* -dist/ -lib/ -*.html \ No newline at end of file +src/js/util/ierange.js +src/js/util/poly-fill.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index d651a1df3..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - env: { - browser: true, - es6: true, - mocha: true, - jest: true, - node: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier - 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. - ], - globals: { - Atomics: 'readonly', - SharedArrayBuffer: 'readonly', - }, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - }, - plugins: ['@typescript-eslint', 'prettier'], - rules: { - 'no-unused-vars': 0, - }, -} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..0148b387f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,38 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true + }, + "globals": { + "ENV": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "no-console":0, + "indent": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single", + { + "allowTemplateLiterals": true + } + ], + "semi": [ + "error", + "never" + ], + "no-unused-vars": 0, + "no-debugger": 0 + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..412eeda78 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index a96006a4c..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -open_collective: wangeditor diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index 6c3d8c8ec..000000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: 提交 bug -about: 请大家一定要按照该模板填写,以方便我们更快复现,否则该 issue 将不予受理! ---- - -## bug 描述 - -*请输入内容……* - -## 你预期的样子是? - -*请输入内容……* - -## 系统和浏览器及版本号 - -- 操作系统 -- 浏览器和版本 - -## wangEditor 版本 - -*请输入内容……* - -## demo 能否复现该 bug ? - -能/不能 - -- 中文 demo https://www.wangeditor.com/demo/ -- English demo https://www.wangeditor.com/demo/?lang=en - -## 在线 demo - -*请尽量提供在线 demo (推荐以下网站),帮助我们最低成本复现 bug* - -- https://codesandbox.io/ -- https://codepen.io/ -- https://stackblitz.com/ - -## 最小成本的复现步骤 - -(请告诉我们,如何最快的复现该 bug) - -- 步骤一 -- 步骤二 -- 步骤三 diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md deleted file mode 100644 index 91bbfb2ab..000000000 --- a/.github/ISSUE_TEMPLATE/feature.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: 建议增加新功能 -about: 请按照该模板填写,以便我们能真正了解你的需求,否则该 issue 将不予受理! ---- - -## 功能描述 - -*请输入内容……* - -## 提炼几个功能点 - -- 功能1 -- 功能2 -- 功能3 - -## 原型图 - -*涉及到 UI 改动的功能,请一定提供原型图。原型图能表明功能即可,不要求规范和美观* - -## 可参考的案例 - -*是否已有可参考的案例(如其他编辑器),有的话请给出链接* diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index c7d4f52c3..000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: 使用时遇到了问题(非 bug) -about: 请按照该模板填写,以便我们能真正了解你的问题,否则该 issue 将不予受理! ---- - -## 问题描述 - -*请输入遇到的问题...* - -## wangEditor 版本 - -*请输入内容……* - -## 是否查阅了文档 ? - -(文档链接 [www.wangeditor.com](https://www.wangeditor.com/) ) - -*是/否* - -## 最小成本的复现步骤 - -(请告诉我们,如何**最快的**复现该问题?) - -- 步骤一 -- 步骤二 -- 步骤三 diff --git a/.github/workflows/deploy-demos.yml b/.github/workflows/deploy-demos.yml deleted file mode 100644 index ca4bc4668..000000000 --- a/.github/workflows/deploy-demos.yml +++ /dev/null @@ -1,44 +0,0 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions -# github actions 中文文档 https://docs.github.com/cn/actions/getting-started-with-github-actions - -name: deploy demos - -on: - push: - branches: [ deploy-demos ] # 特定分支 -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: set ssh key # 临时设置 ssh key - run: | - mkdir -p ~/.ssh/ - echo "${{secrets.SSH_KEY_FOR_GITHUB_WANGEDITOR}}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan "github.com" >> ~/.ssh/known_hosts - echo "---------- set ssh-key ok ----------" - - name: download and replace # 下载现有文件,替换 - run: | - git clone git@github.com:wangEditor/demo.git - echo "---------- git clone ok ----------" - cp -r ./packages/editor/demo/* ./demo ## 用最新构建出来的文件,替换现有的 - echo "---------- replace ok ----------" - - name: upload # 上传文件 - run: | - cd ./demo - git config user.name "github-actions" - git config user.email "github-actions@github.com" - echo "---------- begin git status ----------" - echo `git status` - echo "---------- end git status ----------" - git add . - git commit -m "update by github actions" - echo "---------- begin git push ----------" - git push origin main - echo "---------- end git push ----------" - - name: delete ssh key # 删除 ssh key - run: rm -rf ~/.ssh/id_rsa - - diff --git a/.github/workflows/deploy-examples.yml.bak b/.github/workflows/deploy-examples.yml.bak deleted file mode 100644 index 2639494ed..000000000 --- a/.github/workflows/deploy-examples.yml.bak +++ /dev/null @@ -1,69 +0,0 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions -# github actions 中文文档 https://docs.github.com/cn/actions/getting-started-with-github-actions - -name: deploy to baidu server - example page - -on: - push: - branches: - - 'main' - - 'master' - - 'dev' - - 'feature-*' - - 'fix-*' - - 'hotfix-*' - - 'refactor-*' - paths: - - '.github/workflows/*' - - 'packages/**' - - 'tests/**' - - 'build/**' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 12.x - registry-url: https://registry.npmjs.com - - name: Install dependencies - run: yarn run bootstrap - - name: Build packages - run: yarn build - - name: Unit test - run: yarn run test - - - - # 2022.06.07 百度云服务器到期,就不再部署到测试机了 - wangfupeng - - name: set ssh key # 临时设置 ssh key - run: | - mkdir -p ~/.ssh/ - echo "${{secrets.WFP_ID_RSA}}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan ${{secrets.BAIDU_SERVER}} >> ~/.ssh/known_hosts - - name: scp example files # 拷贝测试页面,到远程服务器 - run: | - ## 获取当前分支名称,并创建一个同名的文件夹 - currentBranchName=`git branch | awk '$1 == "*"{print $2}'` - mkdir -p $currentBranchName/dist/css - - ## 将 dist examples 移到刚创建的文件夹之内 - mv packages/editor/dist/css/* $currentBranchName/dist/css/ - mv packages/editor/dist/index.js $currentBranchName/dist/index.js - mv packages/editor/dist/index.js.map $currentBranchName/dist/index.js.map - mv packages/editor/examples/ $currentBranchName/examples/ - - ## 将该文件夹,及其所有文件,上传到服务器 - echo current branch name is: $currentBranchName - ssh work@${{secrets.BAIDU_SERVER}} "rm -rf /home/work/wangEditor-team/v5-examples/$currentBranchName" - scp -r ./$currentBranchName work@${{secrets.BAIDU_SERVER}}:/home/work/wangEditor-team/v5-examples/$currentBranchName - - name: delete ssh key # 删除 ssh key - run: rm -rf ~/.ssh/id_rsa diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 0d8135240..000000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,45 +0,0 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions -# github actions 中文文档 https://docs.github.com/cn/actions/getting-started-with-github-actions - -name: Cypress tests - -on: - push: - branches: - - 'master' - - 'dev' - - 'feature-*' - - 'fix-*' - - 'hotfix-*' - - 'refactor-*' - - 'test-*' - paths: - - '.github/workflows/*' - - 'packages/**' - - 'scripts/**' - - 'tests/**' - - 'build/**' - - 'cypress/**' - - 'babel.config.json' - - 'cypress.json' - -jobs: - test-e2e: - runs-on: ubuntu-latest - container: cypress/browsers:node12.13.0-chrome78-ff70 - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install dependencies - run: yarn install - - name: Build packages - run: yarn build - - uses: cypress-io/github-action@v2 - with: - browser: chrome - start: yarn run example - wait-on: 'http://localhost:8881/examples/default-mode.html' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 3c87a53c0..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*' - -jobs: - release: - runs-on: ubuntu-latest - strategy: - max-parallel: 1 - - steps: - - name: Checkout repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14.x - registry-url: https://registry.npmjs.com - - - name: Install dependencies - run: yarn run bootstrap - - - name: Build packages - run: yarn build - - - name: Unit test - run: yarn run test - - - name: E2E test - uses: cypress-io/github-action@v2 - with: - browser: chrome - start: yarn run example - wait-on: 'http://localhost:8881/examples/default-mode.html' - - - name: Publish npm - run: yarn run release:publish - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 261f5eee2..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,41 +0,0 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions -# github actions 中文文档 https://docs.github.com/cn/actions/getting-started-with-github-actions - -name: Build and test - -on: - push: - branches: - - 'main' - - 'master' - - 'dev' - - 'feature-*' - - 'fix-*' - - 'hotfix-*' - - 'refactor-*' - paths: - - '.github/workflows/*' - - 'packages/**' - - 'tests/**' - - 'build/**' - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14.x - registry-url: https://registry.npmjs.com - - name: Install dependencies - run: yarn run bootstrap - - name: Build packages - run: yarn build - - name: Unit test - run: yarn run test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3bd857626..3fc20ae74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,126 +1,50 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +#忽略 +**/node_modules/* +node_modules/* +npm-debug.log -# Runtime data -pids -*.pid -*.seed -*.pid.lock +# Windows image file caches +Thumbs.db +ehthumbs.db -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov +# Folder config file +Desktop.ini -# Coverage directory used by tools like istanbul -coverage -*.lcov +# Recycle Bin used on file shares +$RECYCLE.BIN/ -# nyc test coverage -.nyc_output +# Windows Installer files +*.cab +*.msi +*.msm +*.msp -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt +# ========================= +# Operating System Files +# ========================= -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -package-lock.json +# OSX +# ========================= .DS_Store +.AppleDouble +.LSOverride -ISSUE.md -ISSUE1.md - -.idea/ +# Icon must end with two \r +Icon -# cypress -*/cypress/videos -*/cypress/screenshots -cypress/videos -cypress/screenshots -cypress/results -cypress/logs -packages/*/dist -packages/*/dist-example +# Thumbnails +._* -bak/ +# Files that might appear on external disk +.Spotlight-V100 +.Trashes -# rollup 分析包体积结果 -stats.html +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/.npmignore b/.npmignore index 6e4498a1e..333dd252d 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,5 @@ -.github/ -.vscode +node_modules/* +npm-debug.log +docs/* +src/* +example/* \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 162904ad0..000000000 --- a/.prettierrc.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = { - // 箭头函数只有一个参数的时候可以忽略括号 - arrowParens: 'avoid', - // 括号内部不要出现空格 - bracketSpacing: true, - // 行结束符使用 Unix 格式 - endOfLine: 'lf', - // true: Put > on the last line instead of at a new line - jsxBracketSameLine: false, - // 行宽 - printWidth: 100, - // 换行方式 - proseWrap: 'preserve', - // 分号 - semi: false, - // 使用单引号 - singleQuote: true, - // 缩进 - tabWidth: 2, - // 使用 tab 缩进 - useTabs: false, - // 后置逗号,多行对象、数组在最后一行增加逗号 - trailingComma: 'es5', - parser: 'typescript', -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 41406fa83..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "editor.formatOnSave": true, - "cSpell.words": [ - "beforeinput", - "bodyparser", - "browserslist", - "chmod", - "clonedeep", - "compositionend", - "compositionstart", - "config", - "contenteditable", - "elems", - "hoverbar", - "img", - "isequal", - "js", - "keyscan", - "luochao", - "middlewares", - "mkdir", - "next", - "nocheck", - "prettier", - "prettierrc", - "prismjs", - "snabbdom", - "src", - "team", - "tecent", - "toarray", - "ts", - "uppy", - "vdom", - "vnode", - "wangeditor", - "wangfupeng", - "we", - "write", - "yuque" - ], - "typescript.tsdk": "node_modules/typescript/lib", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - }, - "eslint.validate": ["javascript"], - - "editor.detectIndentation": false, - "editor.tabSize": 2 -} \ No newline at end of file diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index f4074f57d..000000000 --- a/.yarnrc +++ /dev/null @@ -1 +0,0 @@ -registry "https://registry.npm.taobao.org" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 71acc5fb4..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Changelog Link -- [basic-modules](./packages/basic-modules/CHANGELOG.md) -- [code-highlight](./packages/code-highlight/CHANGELOG.md) -- [core](./packages/core/CHANGELOG.md) -- [editor](./packages/editor/CHANGELOG.md) -- [list-module](./packages/list-module/CHANGELOG.md) -- [table-module](./packages/table-module/CHANGELOG.md) -- [upload-image-module](./packages/upload-image-module/CHANGELOG.md) -- [video-module](./packages/video-module/CHANGELOG.md) \ No newline at end of file diff --git a/ISSUE.md b/ISSUE.md new file mode 100644 index 000000000..f49d2bf28 --- /dev/null +++ b/ISSUE.md @@ -0,0 +1,50 @@ +# 问题记录 + +## 版本修复 + +### v3.0.1 + +- [done] 如何设置自动增加高度(补充文档) +- [done] src/js/editor/Bar 改为 Progress,仅供上传图片使用 +- [done] Panel 在右上角增加一个“关闭”按钮 +- [done] 显示页面 table、quote、code 等样式,说明一下 +- [done] 增加自定义上传回调函数,用以自定义返回图片的格式 +- [done] 上传附带的参数,也加入到 form-data 中一份 +- [done] 编辑器默认情况下,菜单栏不能点击,必须focus了编辑器求之后才能点击 +- [done] 点击菜单弹出panel之后,再点击编辑器区域其他地方,panel不消失 +- [done] 自定义filename,v2版本就有 +- [done] ff 中的 bug +- [done] ff 中粘贴图片和文字出现问题 https://github.com/wangfupeng1988/wangEditor/issues/609 +- [done] 火狐浏览器下,创建表格,编辑表格内容时,会出现两个控制点(有人提供了解决方案) +- [done] 配置最多上传的文件个数 +- [done] 连续给两段内容 添加有/无序列表时,样式会出问题,且其他内容找不到了,并且编辑器不处于编辑状态。 +- [done] onchange +- [done] IE11下面一直报错。并且表格无法正常使用 + +### v3.0.2 + +- [done] 用 onchange 完善 vue react 的 demo +- [done] 插入图片之后,光标移动到图片的前面,然后回车,图片消失,并且不能撤销 +- [done] 修复上传图片 customInsert 无效的bug +- [done] 编辑区域 z-index 可配置 +- [done] 上传图片不应该把状态码限制在 200,而是 2xx +- [done] editor.txt.html() 之后,没有定位光标位置 + +### 下次发版解决 + +- React-demo运行在IE11会卡顿,表格加载不出来 +- list undo redo 有问题 +- widows 下 word excel 的粘贴,存在垃圾数据 +- IE 中粘贴的问题 +- IE 中无法实现“引用”功能 +- IE 中输入多行代码有报错 + +## 待排期 + +- 删除掉`./release`之后,执行`npm run release`会报错,原因是`fonts`文件没拷贝全,就要去替换`css`中的字体文件为`base64`格式,导致找不到文件。 +- 针对编辑区域的`max-height`和`min-height`做一些处理,而且和全屏还不能通用 +- 先点击'B'再输入内容这种形式,前期先支持 webkit 和 IE,火狐的支持后面再加上 +- 公式 +- 图片压缩 canvas https://github.com/think2011/localResizeIMG +- github 徽章 https://github.com/EyreFree/GitHubBadgeIntroduction + diff --git a/LICENSE b/LICENSE index cc86ea13a..5239660db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +The MIT License (MIT) -Copyright (c) 2021 - present wangEditor-team +Copyright (c) 2017 王福朋 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README-en.md b/README-en.md deleted file mode 100644 index 6fbc27274..000000000 --- a/README-en.md +++ /dev/null @@ -1,20 +0,0 @@ -# wangEditor 5 - -[中文](./README.md) - -## Introduction - -Open source web rich text editor, run right out of the box. Support JS Vue React. - -- [Document](https://www.wangeditor.com/en/) -- [Demo](https://www.wangeditor.com/demo/?lang=en) - -![](./docs/images/editor-en.png) - -## Communication - -You can [commit an issue]((https://github.com/wangeditor-team/wangEditor/issues)) if you have any question. - -## Donation - -Support wangEditor open-source work https://opencollective.com/wangeditor diff --git a/README.md b/README.md index abff91ee9..e4325ec5a 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,69 @@ -# wangEditor 5 -[English](./README-en.md) +# wangEditor ## 介绍 -开源 Web 富文本编辑器,开箱即用,配置简单。支持 JS Vue React 。 +**wangEditor** —— 轻量级 web 富文本编辑器,配置方便,使用简单。支持 IE10+ 浏览器。 -- [文档](https://www.wangeditor.com/) -- [demo](https://www.wangeditor.com/demo/) +- 官网:[www.wangEditor.com](http://www.wangeditor.com/) +- 文档:[www.kancloud.cn/wangfupeng/wangeditor3/332599](http://www.kancloud.cn/wangfupeng/wangeditor3/332599) -![](./docs/images/editor.png) +![图片](http://images2015.cnblogs.com/blog/138012/201705/138012-20170530202905633-1840158981.png) + +*查看 v2 版本的代码和文档点击[这里](https://github.com/wangfupeng1988/wangEditor/tree/v2)* + + +## 下载 + +- 直接下载:[https://github.com/wangfupeng1988/wangEditor/releases](https://github.com/wangfupeng1988/wangEditor/releases) +- 使用`npm`下载:`npm install wangeditor` (注意 `wangeditor` 全部是**小写字母**) +- 使用`bower`下载:`bower install wangEditor` (前提保证电脑已安装了`bower`) +- 使用CDN:[//unpkg.com/wangeditor/release/wangEditor.min.js](https://unpkg.com/wangeditor/release/wangEditor.min.js) + + +## 使用 + +```javascript +var E = window.wangEditor +var editor2 = new E('#div3') +editor2.create() +``` + + +## 运行 demo + +- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git` +- 安装或者升级最新版本 node(最低`v6.x.x`) +- 进入目录,安装依赖包 `cd wangEditor && npm i` +- 安装包完成之后,windows 用户运行`npm run win-example`,Mac 用户运行`npm run example` +- 打开浏览器访问[localhost:3000/index.html](http://localhost:3000/index.html) +- 用于 React 或者 vue 可查阅[文档](http://www.kancloud.cn/wangfupeng/wangeditor3/332599)中“其他”章节中的相关介绍 ## 交流 -- [讨论问题和建议](https://github.com/wangeditor-team/wangEditor/issues) +### QQ 群 + +以下 QQ 群欢迎加入交流问题(可能有些群已经满员) + +- 164999061 +- 281268320 + +### 提问 + +注意,作者只受理以下几种提问方式,其他方式直接忽略 + +- 直接在 [github issues](https://github.com/wangfupeng1988/wangEditor/issues) 提交问题 +- 去[知乎](https://www.zhihu.com/)提问,并邀请[作者](https://www.zhihu.com/people/wang-fu-peng-54/activities)来回答 +- 去[segmentfault](https://segmentfault.com)提问,并邀请[作者](https://segmentfault.com/u/wangfupeng1988)来回答 + +每次升级版本修复的问题记录在[这里](./ISSUE.md) + +## 关于作者 + +- 关注作者的博客 - 《[深入理解javascript原型和闭包系列](http://www.cnblogs.com/wangfupeng1988/p/4001284.html)》《[深入理解javascript异步系列](https://github.com/wangfupeng1988/js-async-tutorial)》《[CSS知多少](http://www.cnblogs.com/wangfupeng1988/p/4325007.html)》 +- 学习作者的教程 - 《[React.js模拟大众点评webapp](http://coding.imooc.com/class/99.html)》《[zepto设计与源码分析](http://www.imooc.com/learn/745)》《[用grunt搭建自动化的web前端开发环境](http://study.163.com/course/courseMain.htm?courseId=1103003)》《[json2.js源码解读](http://study.163.com/course/courseMain.htm?courseId=691008)》 + +如果你感觉有收获,欢迎给我打赏 ———— 以激励我更多输出优质开源内容 -## 捐赠 +![图片](https://camo.githubusercontent.com/e1558b631931e0a1606c769a61f48770cc0ccb56/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3133383031322f3230313730322f3133383031322d32303137303232383131323233373739382d313530373139363634332e706e67) -支持 wangEditor 开源工作 https://opencollective.com/wangeditor diff --git a/babel.config.json b/babel.config.json deleted file mode 100644 index ad09d556f..000000000 --- a/babel.config.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "modules": false, - "useBuiltIns": "usage", - "corejs": 3, - "targets": "ie 11" - } - ], - "@babel/preset-typescript" - ], - "plugins": [ - [ - "@babel/plugin-transform-runtime", - { - "absoluteRuntime": false, - "corejs": 3, - "helpers": true, - "regenerator": true, - "useESModules": false - } - ] - ] -} \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 000000000..0c4ed0d23 --- /dev/null +++ b/bower.json @@ -0,0 +1,20 @@ +{ + "name": "wangEditor", + "description": "wangEditor - 基于javascript和css开发的 web 富文本编辑器, 轻量、简洁、易用、开源免费", + "main": "release/wangEditor.js", + "authors": [ + "wangfupeng " + ], + "license": "MIT", + "keywords": [ + "wangEditor", + "web 富文本编辑器" + ], + "homepage": "https://github.com/wangfupeng1988/wangEditor", + "moduleType": [ + "amd", + "cmd", + "node" + ], + "private": true +} diff --git a/build/build-all.sh b/build/build-all.sh deleted file mode 100644 index 3b9a282a2..000000000 --- a/build/build-all.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -## 一键打包所有 package - -# 获取 yarn dev/build 类型 -buildType=build -if [ -n "$1" ]; then - buildType=$1 -fi - -cd ./packages - -# core 要第一个打包 -cd ./core -rm -rf dist # 清空 dist 目录 -yarn "$buildType" - -cd ../basic-modules -rm -rf dist # 清空 dist 目录 -yarn "$buildType" - -# code-highlight 依赖于 basic-modules 中的 code-block -cd ../code-highlight -rm -rf dist # 清空 dist 目录 -yarn "$buildType" - -cd ../list-module -rm -rf dist # 清空 dist 目录 -yarn "$buildType" - -cd ../table-module -rm -rf dist # 清空 dist 目录 -yarn "$buildType" - -# upload-image 依赖于 basic-modules 中的 image -cd ../upload-image-module -rm -rf dist # 清空 dist 目录 -yarn "$buildType" - -cd ../video-module -rm -rf dist # 清空 dist 目录 -yarn "$buildType" - -# editor 依赖于上述的 core + modules -cd ../editor -rm -rf dist # 清空 dist 目录 -yarn "$buildType" diff --git a/build/config/common.js b/build/config/common.js deleted file mode 100644 index dfef3f956..000000000 --- a/build/config/common.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @description rollup common config - * @author wangfupeng - */ - -import path from 'path' -import commonjs from '@rollup/plugin-commonjs' -import json from '@rollup/plugin-json' -import nodeResolve from '@rollup/plugin-node-resolve' -import typescript from 'rollup-plugin-typescript2' -import replace from '@rollup/plugin-replace' -import peerDepsExternal from 'rollup-plugin-peer-deps-external' -// import del from 'rollup-plugin-delete' - -export const extensions = ['.js', '.jsx', '.ts', '.tsx'] -const isProd = process.env.NODE_ENV === 'production' - -/** - * 生成 common conf - * @param {string} format 'umd' 'esm' - * @returns common conf - */ -function genCommonConf(format) { - return { - input: path.resolve(__dirname, './src/index.ts'), - output: { - // 属性有 file format name sourcemap 等 - // https://www.rollupjs.com/guide/big-list-of-options - }, - plugins: [ - peerDepsExternal(), // 打包结果不包含 package.json 的 peerDependencies - json({ - compact: true, - indent: ' ', - preferConst: true, - }), - typescript({ - clean: true, - tsconfig: path.resolve(__dirname, './tsconfig.json'), - }), - nodeResolve({ - browser: true, // 重要 - mainFields: format === 'esm' ? ['module', 'main'] : ['main'], - extensions, - }), - commonjs(), - replace({ - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - preventAssignment: true, - }), - // del({ targets: 'dist/*' }), - ], - } -} - -export default genCommonConf diff --git a/build/config/dev.js b/build/config/dev.js deleted file mode 100644 index 97101aae2..000000000 --- a/build/config/dev.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @description rollup dev config - * @author wangfupeng - */ - -import postcss from 'rollup-plugin-postcss' -import autoprefixer from 'autoprefixer' -import genCommonConf from './common' - -/** - * 生成 dev config - * @param {string} format 'umd' 'esm' - */ -function genDevConf(format) { - const { input, output = {}, plugins = [], external } = genCommonConf(format) - - return { - input, - output, - external, - plugins: [ - ...plugins, - - postcss({ - plugins: [autoprefixer()], - extract: 'css/style.css', - }), - ], - } -} - -export default genDevConf diff --git a/build/config/prd.js b/build/config/prd.js deleted file mode 100644 index 0b7d5e43b..000000000 --- a/build/config/prd.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @description rollup prd config - * @author wangfupeng - */ - -import babel from '@rollup/plugin-babel' -import postcss from 'rollup-plugin-postcss' -import autoprefixer from 'autoprefixer' -import cssnano from 'cssnano' -import { terser } from 'rollup-plugin-terser' -import cleanup from 'rollup-plugin-cleanup' -import genCommonConf from './common' -import { extensions } from './common' - -/** - * 生成 prd config - * @param {string} format 'umd' 'esm' - */ -function genPrdConf(format) { - const { input, output = {}, plugins = [], external } = genCommonConf(format) - - const finalPlugins = [ - ...plugins, - babel({ - rootMode: 'upward', - babelHelpers: 'runtime', - exclude: 'node_modules/**', - include: 'src/**', - extensions, - }), - postcss({ - plugins: [ - autoprefixer(), - cssnano(), // 压缩 css - ], - extract: 'css/style.css', - }), - cleanup({ - comments: 'none', - extensions: ['.ts', '.tsx'], - }), - terser(), // 压缩 js - ] - - return { - input, - output: { - sourcemap: true, - ...output, - }, - external, - plugins: finalPlugins, - } -} - -export default genPrdConf diff --git a/build/create-rollup-config.js b/build/create-rollup-config.js deleted file mode 100644 index f037e81d5..000000000 --- a/build/create-rollup-config.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @description 创建 rollup 配置 - * @author wangfupeng - */ - -import { merge } from 'lodash' -import { visualizer } from 'rollup-plugin-visualizer' -import genDevConf from './config/dev' -import genPrdConf from './config/prd' - -// 环境变量 -const ENV = process.env.NODE_ENV || 'production' -const IS_SIZE_STATS = ENV.indexOf('size_stats') >= 0 // 分析包体积 -export const IS_DEV = ENV.indexOf('development') >= 0 -export const IS_PRD = ENV.indexOf('production') >= 0 - -/** - * 生成单个 rollup 配置 - * @param {object} customConfig { input, output, plugins ... } - */ -export function createRollupConfig(customConfig = {}) { - const { input, output = {}, plugins = [] } = customConfig - const { format } = output - - let baseConfig - if (IS_PRD) { - baseConfig = genPrdConf(format) - } else { - baseConfig = genDevConf(format) - } - - if (IS_SIZE_STATS) { - // 分析包体积。运行之后可查看 package 下的 `stats.html` - plugins.push(visualizer()) - } - - const config = { - input: input ? input : baseConfig.input, - output, - plugins, - } - - const res = merge({}, baseConfig, config) - return res -} diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 6620114a0..000000000 --- a/commitlint.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - extends: ['cz'], - rules: { - 'type-empty': [2, 'never'], - }, -} diff --git a/cypress.json b/cypress.json deleted file mode 100644 index b4b3c48b1..000000000 --- a/cypress.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "baseUrl": "http://localhost:8881", - "defaultCommandTimeout": 8000, - "video": false -} \ No newline at end of file diff --git a/cypress/cypress.d.ts b/cypress/cypress.d.ts deleted file mode 100644 index 56c562f79..000000000 --- a/cypress/cypress.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/// - -declare namespace Cypress { - interface CustomWindow extends Window {} - - interface Chainable { - /** - * Window object with additional properties used during test. - */ - window(options?: Partial): Chainable - - getByClass(dataTestAttribute: string, args?: any): Chainable - } -} diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json deleted file mode 100644 index 02e425437..000000000 --- a/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/cypress/integration/editor.spec.ts b/cypress/integration/editor.spec.ts deleted file mode 100644 index fe957cec3..000000000 --- a/cypress/integration/editor.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -describe('Basic Editor', () => { - it('create editor', () => { - cy.visit('/examples/default-mode.html') - - cy.get('#btn-create').click() - - cy.get('#editor-toolbar').should('have.attr', 'data-w-e-toolbar', 'true') - cy.get('#editor-text-area').should('have.attr', 'data-w-e-textarea', 'true') - cy.get('#w-e-textarea-1').contains('一行标题') - }) -}) diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts deleted file mode 100644 index fe9f896e1..000000000 --- a/cypress/plugins/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ -export default (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - // codeCoverageTask(on, config) - return config -} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts deleted file mode 100644 index 40f6374e1..000000000 --- a/cypress/support/commands.ts +++ /dev/null @@ -1,3 +0,0 @@ -Cypress.Commands.add('getByClass', (selector, ...args) => { - return cy.get(`.w-e-${selector}`, ...args) -}) diff --git a/cypress/support/index.ts b/cypress/support/index.ts deleted file mode 100644 index 43c03b759..000000000 --- a/cypress/support/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './commands' diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json deleted file mode 100644 index afa4af8b6..000000000 --- a/cypress/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["es2015", "dom", "esnext"], - "types": ["cypress"], - "isolatedModules": false, - "allowJs": true, - "noEmit": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - }, - "include": [ - "./**/*.ts" - ] -} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 9f331a0d9..000000000 --- a/docs/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 文档 - -- [开发文档](./dev.md) -- [发布到 npm](./publish.md) -- [加入研发团队](./join.md) diff --git a/docs/dev.md b/docs/dev.md deleted file mode 100644 index 762235b8f..000000000 --- a/docs/dev.md +++ /dev/null @@ -1,38 +0,0 @@ -# 开发 - -## 准备工作 - -- 了解 slate.js -- 了解 vdom 和 snabbdom.js -- 了解 lerna -- 已安装 yarn - -## 本地启动 - -### 打包 - -- 下载代码到本地,进入 `wangEditor` 目录 -- 安装所有依赖 `yarn bootstrap` -- 打包所有模块 `yarn dev` 或者 `yarn build` - -### 运行 demo - -- 进入 `packages/editor` 目录,运行 `yarn example` ,浏览器打开 `http://localhost:8881/examples/` - -## 注意事项 - -- 修改代码、重新打包后,要**强制刷新**浏览器 -- 如果本地包依赖有问题,试试 `lerna link` 关联内部包 - -## 记录 - -全局安装一个插件 `yarn add xxx --dev -W` - -注意合理使用 `peerDependencies` 和 `dependencies` ,不要重复打包一个第三方库 - -执行 `lerna add ...` 之后,需要重新 `lerna link` 建立内部连接 - -分析包体积 -- 命令行,进入某个 package ,如 `cd packages/editor` -- 执行 `yarn size-stats` ,等待执行完成 -- 结果会记录在 `packages/editor/stats.html` 用浏览器打开 \ No newline at end of file diff --git a/docs/dev/README.md b/docs/dev/README.md new file mode 100644 index 000000000..af8156321 --- /dev/null +++ b/docs/dev/README.md @@ -0,0 +1,26 @@ +面向开发者的文档 + + +框架介绍 + +- 下载和运行 +- 目录结构介绍 +- `example`目录 +- `src`目录(`js`目录,`less`目录) +- `package.json` +- `gulpfile.js` + +如何提交 PR + + + +上线 + +- 修改`package.json`版本 +- 提交到github,并创建tag +- 提交到 npm +- 同步到官网 min.js +- 更新 .md 文档 +- 文档同步到 kancloud +- …… + diff --git a/docs/images/cypress-run.jpg b/docs/images/cypress-run.jpg deleted file mode 100644 index 7218764a0..000000000 Binary files a/docs/images/cypress-run.jpg and /dev/null differ diff --git a/docs/images/cypress.jpg b/docs/images/cypress.jpg deleted file mode 100644 index bc6f39311..000000000 Binary files a/docs/images/cypress.jpg and /dev/null differ diff --git a/docs/images/editor-en.png b/docs/images/editor-en.png deleted file mode 100644 index a35b3e73b..000000000 Binary files a/docs/images/editor-en.png and /dev/null differ diff --git a/docs/images/editor.png b/docs/images/editor.png deleted file mode 100644 index 570dc8e7b..000000000 Binary files a/docs/images/editor.png and /dev/null differ diff --git a/docs/join.md b/docs/join.md deleted file mode 100644 index 78a5da4d5..000000000 --- a/docs/join.md +++ /dev/null @@ -1,24 +0,0 @@ -# 加入团队 - -欢迎加入 wangEditor 研发团队~ - -## V5 研发人员 - -- [王福朋](https://github.com/wangfupeng1988/) - wangEditor 创始人,资深前端工程师,PMP,曾就职于百度、滴滴 -- [罗超](https://github.com/echoLC) - 天才就是百分之一的灵感加上百分之九十九的努力 -- [TGuoW](https://github.com/TGuoW) -- [刘庆华(火热) ](https://github.com/liuqh0609) - 热爱着,年轻着 -- [haha](https://github.com/hahaaha) - -## 加入条件 - -- 熟悉 typescript ,并实际应用过 -- 熟悉 webpack 或者 rollup -- 熟悉 React 或者 Vue -- 熟悉 vdom 结构,熟悉 [snabbdom.js](https://github.com/snabbdom/snabbdom) (不了解的可以先去学习一下) -- 熟悉 [slate.js](https://www.slatejs.org/) 包括:熟悉数据模型,熟悉 API,看过源码(不了解的可以先去学习一下) - -## 申请加入 - -- 首先自我评价,符合上述加入条件 -- 加入 QQ 群,私聊群主,发送一份个人简历 diff --git a/docs/publish.md b/docs/publish.md deleted file mode 100644 index b9e9cea91..000000000 --- a/docs/publish.md +++ /dev/null @@ -1,40 +0,0 @@ -# 发布到 NPM - -因为我们的项目是使用 `independent` 的方式组织 `muti-packgae`,所以每个包都有单独的版本号,默认使用 `lerna publish` 发布包,我们需要根据包的修改内容选择合适的版本号。**对于没有变动的 `package`,lerna 发布的时候不会算在本次发布的内容里面**。 - -发布的流程分两步: - -第一步:将所有要发版的代码合并到 `master` 分支后,先在本地执行 `yarn release:version` 生成各个本次变动的 `package` 的版本后,自动生成 `changelog`,接着 lerna 会生成 `git tag` 并 `push` 到远程。 - -第二步:上面步骤完成后, `lerna` push `git tag` 到远程的时候会触发我们配置的 `git action`,走完正常的发版 `action`,具体看 [`action` 配置]('./../.github/workflows/release.yml') 。 - -因为目前我们还在开发当中,所以为了更加方便发版到 `npm` 进行测试,目前,项目中集成了以下 `release` 的 `script command`: - -## 正常发布一个版本 - -```bash -yarn release:publish -``` - -## 发布指定的 dist-tag 版本 - -发布一个 `experimental` [dist-tag](https://docs.npmjs.com/cli/v7/commands/npm-dist-tag) 的版本: - -```bash -yarn release:publish:experimental -``` - -发布一个 `next` [dist-tag](https://docs.npmjs.com/cli/v7/commands/npm-dist-tag) 的版本: - -```bash -yarn release:next -``` - -## 发布 canary 版本 - -发布一个 `canary` 版本: -```bash -# 1.0.0 => 1.0.1-alpha.0+${SHA} of packages changed since the previous commit -lerna publish --canary -``` - diff --git a/docs/test.md b/docs/test.md deleted file mode 100644 index d74cd0b9a..000000000 --- a/docs/test.md +++ /dev/null @@ -1,44 +0,0 @@ -# 测试 -目前我们的项目已经集成了基于 `Jest` 的单元测试和基于 `Cypress` 的 `E2E` 测试,下面简单介绍两种测试运行和编写的方式。 - -## 单元测试 -单元测试是从最底层 `API` 的角度出发,保证编辑功能的质量。虽然我们是基于 `lerna` 的 monorepo 管理方式,为了方便组织,我们的所有测试还是都放在根目录下的 `tests/units` 下,每个 `packages` 下面的包都在 `tests/units` 下对应一个目录。所以如果需要新增 `test`,可以按照这个目录组织方式决定把新的 `test` 放在哪个目录。 - -### 运行单元测试 -目前单元测试的运行已经集成在 CI 流程中,如果本地开发后,需要自动执行单元测试,运行如下 `scripts` 命令: -```bash -yarn run test -``` -查看单元测试的覆盖率: -```bash -yarn run test-c -``` - -### 注意事项 -- **因为各个模块依赖了 `core`,如果修改了 `core` 的代码,增加了 `API`,需要运行 `yarn build` 命令,使得各个模块能读到最新的代码**。 - -## E2E 测试 -目前我们的项目 `E2E` 测试基于 [Cypress](https://docs.cypress.io/),对于编辑器这种强依赖用户交互运作的产品,通过 `E2E` 保证编辑器交互更加稳定。 - -目前 `E2E` 测试只写了基本的创建基础编辑器的用例,保证打包后的代码能正常创建编辑器。 - -**`E2E` 测试用例目前都放在根目录下的 `cypress/integration` 目录下,如果需要增加新的测试用例,应该在此目录下创建文件。** - -### 运行 E2E 测试 -目前 E2E 测试的同样集成到了 CI 流程中,如果本地开发后,需要编写 E2E 测试,运行如下 `scripts` 命令: -```bash -yarn e2e:dev -``` -该命令首先会启动 `packages/editor` 下面的 `example` 服务,然后再启动 Cypress 的命令, Cypress 会在本地调起 UI 界面: - -![cypress](images/cypress.jpg) - -然后你可以选择想要执行 E2E 的用例,然后执行后 Cypress 会调起浏览器,运行所有的测试用例,你可以直接在受控的浏览器直接调试你的测试: - -![cypress-run](images/cypress-run.jpg) - -如果不是为了开发新的测试用例,只是想要本地运行所有的 E2E 测试,则执行: -```bash -yarn e2e -``` -Cypress 则会自己后台运行所有测试,并不会打开 UI 界面和浏览器。 \ No newline at end of file diff --git a/docs/usage/01-getstart/01-demo.md b/docs/usage/01-getstart/01-demo.md new file mode 100644 index 000000000..b71612fe2 --- /dev/null +++ b/docs/usage/01-getstart/01-demo.md @@ -0,0 +1,41 @@ +# 简单的 demo + +## 下载 + +- 点击 [https://github.com/wangfupeng1988/wangEditor/releases](https://github.com/wangfupeng1988/wangEditor/releases) 下载最新版。进入`release`文件夹下找到`wangEditor.js`或者`wangEditor.min.js`即可 +- 使用CDN:[//unpkg.com/wangeditor/release/wangEditor.min.js](https://unpkg.com/wangeditor/release/wangEditor.min.js) +- 使用`bower`下载:`bower install wangEditor` (前提保证电脑已安装了`bower`) + +*PS:支持`npm`安装,请参见后面的章节* + +## 制作 demo + +编辑器效果如下。 + +![图片](https://camo.githubusercontent.com/f3d072718d8fcbbacf8cc80465a34cceffcf5b4a/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3133383031322f3230313730352f3133383031322d32303137303533303230323930353633332d313834303135383938312e706e67) + +代码示例如下。**注意,以下代码中无需引用任何 CSS 文件!!!** + +```html + + + + + wangEditor demo + + +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + + +``` diff --git a/docs/usage/01-getstart/02-use-module.md b/docs/usage/01-getstart/02-use-module.md new file mode 100644 index 000000000..0356a68ca --- /dev/null +++ b/docs/usage/01-getstart/02-use-module.md @@ -0,0 +1,49 @@ +# 使用模块定义 + +wangEditor 除了直接使用` + + +``` + +## CommonJS + +可以使用`npm install wangeditor`安装(注意,这里`wangeditor`全是**小写字母**) + +```javascript +// 引用 +var E = require('wangeditor') // 使用 npm 安装 +var E = require('/wangEditor.min.js') // 使用下载的源码 + +// 创建编辑器 +var editor = new E('#editor') +editor.create() +``` diff --git a/docs/usage/01-getstart/03-sperate.md b/docs/usage/01-getstart/03-sperate.md new file mode 100644 index 000000000..0fcd276e4 --- /dev/null +++ b/docs/usage/01-getstart/03-sperate.md @@ -0,0 +1,48 @@ +# 菜单和编辑区域分离 + +如果你想要像 知乎专栏、简书、石墨、网易云笔记 这些编辑页面一样,将编辑区域和菜单分离,也可以实现。 + +这样,菜单和编辑器区域就是使用者可自己控制的元素,可自定义样式。例如:将菜单`fixed`、编辑器区域高度自动增加等 + +## 代码示例 + +```html + + + + + wangEditor 菜单和编辑器区域分离 + + + +
+
+
中间隔离带
+
+

请输入内容

+
+ + + + + +``` + +## 显示效果 + +从上面代码可以看出,菜单和编辑区域其实就是两个单独的`
`,位置、尺寸都可以随便定义。 + +![](http://images2015.cnblogs.com/blog/138012/201705/138012-20170531224756289-7442240.png) + diff --git a/docs/usage/01-getstart/04-multi.md b/docs/usage/01-getstart/04-multi.md new file mode 100644 index 000000000..cd5991ad2 --- /dev/null +++ b/docs/usage/01-getstart/04-multi.md @@ -0,0 +1,50 @@ +# 通一个页面创建多个编辑器 + +wangEditor 支持一个页面创建多个编辑器 + +## 代码示例 + +```html + + + + + wangEditor 一个页面多个编辑器 + + + +
+
+
中间隔离带
+
+

第一个 demo(菜单和编辑器区域分开)

+
+ +
+

第二个 demo(常规)

+
+ + + + + + +``` + diff --git a/docs/usage/02-content/01-set-content.md b/docs/usage/02-content/01-set-content.md new file mode 100644 index 000000000..7631f6f4c --- /dev/null +++ b/docs/usage/02-content/01-set-content.md @@ -0,0 +1,46 @@ +# 设置内容 + +以下方式中,如果条件允许,尽量使用第一种方式,效率最高。 + +## html 初始化内容 + +直接将内容写到要创建编辑器的`
`标签中 + +```html +
+

初始化的内容

+

初始化的内容

+
+ + + +``` + +## js 设置内容 + +创建编辑器之后,使用`editor.txt.html(...)`设置编辑器内容 + +```html +
+
+ + + +``` + +## 追加内容 + +创建编辑器之后,可使用`editor.txt.append('

追加的内容

')`继续追加内容。 + +## 清空内容 + +可使用`editor.txt.clear()`清空编辑器内容 diff --git a/docs/usage/02-content/02-get-content.md b/docs/usage/02-content/02-get-content.md new file mode 100644 index 000000000..e21c2772d --- /dev/null +++ b/docs/usage/02-content/02-get-content.md @@ -0,0 +1,80 @@ +# 读取内容 + +可以`html`和`text`的方式读取编辑器的内容。 + +```html +
+

欢迎使用 wangEditor 编辑器

+
+ + + + + +``` + +需要注意的是:**从编辑器中获取的 html 代码是不包含任何样式的纯 html**,如果显示的时候需要对其中的`` `` `
`等标签进行自定义样式(这样既可实现多皮肤功能),下面提供了编辑器中使用的样式供参考 + +```css +/* table 样式 */ +table { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; +} +table td, +table th { + border-bottom: 1px solid #ccc; + border-right: 1px solid #ccc; + padding: 3px 5px; +} +table th { + border-bottom: 2px solid #ccc; + text-align: center; +} + +/* blockquote 样式 */ +blockquote { + display: block; + border-left: 8px solid #d0e5f2; + padding: 5px 10px; + margin: 10px 0; + line-height: 1.4; + font-size: 100%; + background-color: #f1f1f1; +} + +/* code 样式 */ +code { + display: inline-block; + *display: inline; + *zoom: 1; + background-color: #f1f1f1; + border-radius: 3px; + padding: 3px 5px; + margin: 0 3px; +} +pre code { + display: block; +} + +/* ul ol 样式 */ +ul, ol { + margin: 10px 0 10px 20px; +} +``` + diff --git a/docs/usage/03-config/01-menu.md b/docs/usage/03-config/01-menu.md new file mode 100644 index 000000000..93ea8a745 --- /dev/null +++ b/docs/usage/03-config/01-menu.md @@ -0,0 +1,52 @@ +# 自定义菜单 + +编辑器创建之前,可使用`editor.customConfig.menus`定义显示哪些菜单和菜单的顺序 + +## 代码示例 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +## 默认菜单 + +编辑默认的菜单配置如下 + +```javascript +[ + 'head', // 标题 + 'bold', // 粗体 + 'italic', // 斜体 + 'underline', // 下划线 + 'strikeThrough', // 删除线 + 'foreColor', // 文字颜色 + 'backColor', // 背景颜色 + 'link', // 插入链接 + 'list', // 列表 + 'justify', // 对齐方式 + 'quote', // 引用 + 'emoticon', // 表情 + 'image', // 插入图片 + 'table', // 表格 + 'video', // 插入视频 + 'code', // 插入代码 + 'undo', // 撤销 + 'redo' // 重复 +] +``` diff --git a/docs/usage/03-config/02-debug.md b/docs/usage/03-config/02-debug.md new file mode 100644 index 000000000..e94d7a463 --- /dev/null +++ b/docs/usage/03-config/02-debug.md @@ -0,0 +1,21 @@ +# 定义 debug 模式 + +可通过`editor.customConfig.debug = true`配置`debug`模式,`debug`模式下,有 JS 错误会以`throw Error`方式提示出来。默认值为`false`,即不会抛出异常。 + +但是,在实际开发中不建议直接定义为`true`或者`false`,可通过 url 参数进行干预,示例如下: + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + diff --git a/docs/usage/03-config/03-onchange.md b/docs/usage/03-config/03-onchange.md new file mode 100644 index 000000000..b7651817b --- /dev/null +++ b/docs/usage/03-config/03-onchange.md @@ -0,0 +1,32 @@ +# 配置 onchange 函数 + +配置`onchange`函数之后,用户操作(鼠标点击、键盘打字等)导致的内容变化之后,会自动触发`onchange`函数执行。 + +但是,**用户自己使用 JS 修改了`div1`的`innerHTML`,不会自动触发`onchange`函数**,此时你可以通过执行`editor.change()`来手动触发`onchange`函数的执行。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ +

手动触发 onchange 函数执行

+ + + + +``` + diff --git a/docs/usage/03-config/04-z-index.md b/docs/usage/03-config/04-z-index.md new file mode 100644 index 000000000..ab7863e72 --- /dev/null +++ b/docs/usage/03-config/04-z-index.md @@ -0,0 +1,19 @@ +# 配置编辑区域的 z-index + +编辑区域的`z-index`默认为`10000`,可自定义修改,代码配置如下: + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + + diff --git a/docs/usage/04-uploadimg/01-show-tab.md b/docs/usage/04-uploadimg/01-show-tab.md new file mode 100644 index 000000000..8261950cb --- /dev/null +++ b/docs/usage/04-uploadimg/01-show-tab.md @@ -0,0 +1,52 @@ +# 隐藏/显示 tab + +## 显示“上传图片”tab + +默认情况下,编辑器不会显示“上传图片”的tab,因为你还没有配置上传图片的信息。 + +![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204308039-691571074.png) + +参考一下示例显示“上传图片”tab + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +显示效果 + +![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204504524-830243744.png) + +## 隐藏“网络图片”tab + +默认情况下,“网络图片”tab是一直存在的。如果不需要,可以参考一下示例来隐藏它。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` diff --git a/docs/usage/04-uploadimg/02-base64.md b/docs/usage/04-uploadimg/02-base64.md new file mode 100644 index 000000000..3a2d71aa1 --- /dev/null +++ b/docs/usage/04-uploadimg/02-base64.md @@ -0,0 +1,23 @@ +# 使用 base64 保存图片 + +如果需要使用 base64 编码直接将图片插入到内容中,可参考一下示例配置 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +示例效果如下 + +![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204759258-1412289899.png) + + diff --git a/docs/usage/04-uploadimg/03-upload-config.md b/docs/usage/04-uploadimg/03-upload-config.md new file mode 100644 index 000000000..0e383651a --- /dev/null +++ b/docs/usage/04-uploadimg/03-upload-config.md @@ -0,0 +1,148 @@ +# 上传图片 & 配置 + +将图片上传到服务器上的配置方式 + +## 上传图片 + +参考如下代码 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +其中`/upload`是上传图片的服务器端接口,接口返回的**数据格式**如下 + +```json +{ + // errno 即错误代码,0 表示没有错误。 + // 如果有错误,errno != 0,可通过下文中的监听函数 fail 拿到该错误码进行自定义处理 + errno: 0, + + // data 是一个数组,返回若干图片的线上地址 + data: [ + '图片1地址', + '图片2地址', + '……' + ] +} +``` + +## 限制图片大小 + +默认限制图片大小是 5M + +```javascript +// 将图片大小限制为 3M +editor.customConfig.uploadImgMaxSize = 3 * 1024 * 1024 +``` + +## 限制一次最多能传几张图片 + +默认为 10000 张(即不限制),需要限制可自己配置 + +```javascript +// 限制一次最多上传 5 张图片 +editor.customConfig.uploadImgMaxLength = 5 +``` + +## 自定义上传参数 + +上传图片时可自定义传递一些参数,例如传递验证的`token`等。这些参数会拼接到 url 的参数中,也会被添加到`formdata`中。 + +```javascript +editor.customConfig.uploadImgParams = { + token: 'abcdef12345' // 属性值会自动进行 encode ,此处无需 encode +} +``` + +## 自定义 fileName + +上传图片时,可自定义`filename`,即在使用`formdata.append(name, file)`添加图片文件时,自定义第一个参数。 + +```javascript +editor.customConfig.uploadFileName = 'yourFileName' +``` + +## 自定义 header + +上传图片时刻自定义设置 header + +```javascript +editor.customConfig.uploadImgHeaders = { + 'Accept': 'text/x-json' +} +``` + +## withCredentials(跨域传递 cookie) + +跨域上传中如果需要传递 cookie 需设置 withCredentials + +```javascript +editor.customConfig.withCredentials = true +``` + +## 自定义 timeout 时间 + +默认的 timeout 时间是 5 秒钟 + +```javascript +// 将 timeout 时间改为 3s +editor.customConfig.uploadImgTimeout = 3000 +``` + +## 监听函数 + +可使用监听函数在上传图片的不同阶段做相应处理 + +```javascript +editor.customConfig.uploadImgHooks = { + before: function (xhr, editor, files) { + // 图片上传之前触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,files 是选择的图片文件 + }, + success: function (xhr, editor, result) { + // 图片上传并返回结果,图片插入成功之后触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果 + }, + fail: function (xhr, editor, result) { + // 图片上传并返回结果,但图片插入错误时触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果 + }, + error: function (xhr, editor) { + // 图片上传出错时触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象 + }, + timeout: function (xhr, editor) { + // 图片上传超时时触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象 + }, + + // 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置 + customInsert: function (insertImg, result, editor) { + // 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!) + // insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果 + + // 举例:假如上传图片成功后,服务器端返回的是 {url:'....'} 这种格式,即可这样插入图片: + var url = result.url + insertImg(url) + } +} +``` diff --git "a/docs/usage/05-other/01-\345\205\250\345\261\217-\351\242\204\350\247\210-\346\237\245\347\234\213\346\272\220\347\240\201.md" "b/docs/usage/05-other/01-\345\205\250\345\261\217-\351\242\204\350\247\210-\346\237\245\347\234\213\346\272\220\347\240\201.md" new file mode 100644 index 000000000..27588c87f --- /dev/null +++ "b/docs/usage/05-other/01-\345\205\250\345\261\217-\351\242\204\350\247\210-\346\237\245\347\234\213\346\272\220\347\240\201.md" @@ -0,0 +1,10 @@ +# 全屏 & 预览 & 查看源码 + +## 全屏 + +虽然 wangEditor 没有内置全屏功能,但是你可以通过简单的代码来搞定,作者已经做了一个demo来示范。通过运行 demo(文档一开始就介绍了)即可看到该示例页面,直接查看页面源代码即可。 + +## 预览 & 查看源码 + +如果需要预览和查看源码的功能,也需要跟全屏功能一样,自己定义按钮。点击按钮时通过`editor.txt.html()`获取编辑器内容,然后自定义实现预览和查看源码功能。 + diff --git "a/docs/usage/05-other/02-\344\270\212\344\274\240\351\231\204\344\273\266.md" "b/docs/usage/05-other/02-\344\270\212\344\274\240\351\231\204\344\273\266.md" new file mode 100644 index 000000000..1f3cc88d9 --- /dev/null +++ "b/docs/usage/05-other/02-\344\270\212\344\274\240\351\231\204\344\273\266.md" @@ -0,0 +1,24 @@ +# 关于上传附件 + +**有用户问到编辑器能否有上传附件的功能?我的建议是不要把附件做到内容中。** + +原因很简单,如果将附件上传之后再插入到富文本内容中,其实就是一个链接的形式。如下图: + +![](http://box.kancloud.cn/2016-02-19_56c718ec6f9bf.png) + +而用户在用编辑器编辑文本时,操作是非常随意多样的,他把这个链接删了,你服务器要想实时删除上传的附件文件,是难监控到的。 + +还有,用户如果要上传很多个附件,也是很难管理的,还是因为富文本的内容变化多样,用户可以随便在什么地方插入附件,而且形式和链接一样。 + +------- + +反过来,我们想一下平时用附件和编辑器最多的产品是什么——是邮箱。邮箱如何处理附件的,大家应该很清楚。它把文本内容和附件分开,这样附件就可以很轻松、明了的进行管理,绝对不会和编辑内容的链接产生混淆。 + +![](http://box.kancloud.cn/2016-02-19_56c718ec83f7e.png) + +你能看到的所有的邮箱产品,几乎都是这样设计的。 + +------- + +因此,在你提问编辑器能否上传附件这个问题的时候,可以想一下能否参照邮箱的实现来设计? + diff --git a/docs/usage/05-other/03-markdown.md b/docs/usage/05-other/03-markdown.md new file mode 100644 index 000000000..c7233471d --- /dev/null +++ b/docs/usage/05-other/03-markdown.md @@ -0,0 +1,12 @@ +# 关于 markdown + +**好多使用者问到,wangEditor编辑器能否集成markdown?——答案是:富文本编辑器无法和markdown集成到一起。** + +----- + + +你可以参考 [简书](http://www.jianshu.com/) 的实现方式,简书中编辑器也无法实现富文本和`markdown`的自由切换。要么使用富文本编写文章,要么使用`markdown`编写文章,不能公用。 + +本质上,富文本编辑器和`markdown`编辑器是两回事儿。 + + diff --git a/docs/usage/05-other/04-xss.md b/docs/usage/05-other/04-xss.md new file mode 100644 index 000000000..286337fcb --- /dev/null +++ b/docs/usage/05-other/04-xss.md @@ -0,0 +1,23 @@ +# 预防 XSS 攻击 + +> 术业有专攻 + +要想在前端预防 xss 攻击,还得依赖于其他工具,例如[xss.js](http://jsxss.com/zh/index.html)(如果打不开页面,就从百度搜一下) + +代码示例如下 + +```html + + + +``` + diff --git a/docs/usage/05-other/05-react.md b/docs/usage/05-other/05-react.md new file mode 100644 index 000000000..8dcc2d45b --- /dev/null +++ b/docs/usage/05-other/05-react.md @@ -0,0 +1,7 @@ +# 用于 React + +如果需要将 wangEditor 用于 React 中,可参见如下示例 + +- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git` +- 进入 React 示例目录 `cd wangEditor/example/demo/in-react/`,查看`src/App.js`即可 +- 也可以运行`npm install && npm start`查看在 React 中的效果(`http://localhost:3000/`) diff --git a/docs/usage/05-other/06-vue.md b/docs/usage/05-other/06-vue.md new file mode 100644 index 000000000..47e167a86 --- /dev/null +++ b/docs/usage/05-other/06-vue.md @@ -0,0 +1,7 @@ +# 用于 Vue + +如果需要将 wangEditor 用于 Vue 中,可参见如下示例 + +- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git` +- 进入 vue 示例目录 `cd wangEditor/example/demo/in-vue/`,查看`src/components/Editor.vue`即可 +- 也可以运行`npm install && npm run dev`查看在 vue 中的效果(`http://localhost:8080/`) diff --git a/docs/usage/README.md b/docs/usage/README.md new file mode 100644 index 000000000..4c801e6ea --- /dev/null +++ b/docs/usage/README.md @@ -0,0 +1,3 @@ +同步[../../README.md](../../README.md)的内容 + +将所有文档跟新到 www.kancloud.cn/wangfupeng/wangeditor3/332599 中 diff --git a/example/README.md b/example/README.md new file mode 100644 index 000000000..6e17ca09b --- /dev/null +++ b/example/README.md @@ -0,0 +1 @@ +wangEditor demo diff --git a/example/demo/in-react/package.json b/example/demo/in-react/package.json new file mode 100644 index 000000000..054d5cd6c --- /dev/null +++ b/example/demo/in-react/package.json @@ -0,0 +1,19 @@ +{ + "name": "wangeditor-in-react", + "version": "0.1.0", + "private": true, + "dependencies": { + "react": "^15.5.4", + "react-dom": "^15.5.4", + "wangeditor": ">=3.0.0" + }, + "devDependencies": { + "react-scripts": "1.0.7" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} diff --git a/example/demo/in-react/public/favicon.ico b/example/demo/in-react/public/favicon.ico new file mode 100644 index 000000000..5c125de5d Binary files /dev/null and b/example/demo/in-react/public/favicon.ico differ diff --git a/example/demo/in-react/public/index.html b/example/demo/in-react/public/index.html new file mode 100644 index 000000000..7bee02710 --- /dev/null +++ b/example/demo/in-react/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + React App + + + +
+ + + diff --git a/example/demo/in-react/public/manifest.json b/example/demo/in-react/public/manifest.json new file mode 100644 index 000000000..be607e417 --- /dev/null +++ b/example/demo/in-react/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "192x192", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/example/demo/in-react/src/App.css b/example/demo/in-react/src/App.css new file mode 100644 index 000000000..15adfdc71 --- /dev/null +++ b/example/demo/in-react/src/App.css @@ -0,0 +1,24 @@ +.App { + text-align: center; +} + +.App-logo { + animation: App-logo-spin infinite 20s linear; + height: 80px; +} + +.App-header { + background-color: #222; + height: 150px; + padding: 20px; + color: white; +} + +.App-intro { + font-size: large; +} + +@keyframes App-logo-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/example/demo/in-react/src/App.js b/example/demo/in-react/src/App.js new file mode 100644 index 000000000..95b21fba2 --- /dev/null +++ b/example/demo/in-react/src/App.js @@ -0,0 +1,48 @@ +import React, { Component } from 'react'; +import logo from './logo.svg'; +import './App.css'; +import E from 'wangeditor' + +class App extends Component { + constructor(props, context) { + super(props, context); + this.state = { + editorContent: '' + } + } + render() { + return ( +
+
+ logo +

Welcome to React

+
+

+ To get started, edit src/App.js and save to reload. +

+ + {/* 将生成编辑器 */} +
+
+ + +
+ ); + } + componentDidMount() { + const elem = this.refs.editorElem + const editor = new E(elem) + // 使用 onchange 函数监听内容的变化,并实时更新到 state 中 + editor.customConfig.onchange = html => { + this.setState({ + editorContent: html + }) + } + editor.create() + } + clickHandle() { + alert(this.state.editorContent) + } +} + +export default App; diff --git a/example/demo/in-react/src/App.test.js b/example/demo/in-react/src/App.test.js new file mode 100644 index 000000000..b84af98d7 --- /dev/null +++ b/example/demo/in-react/src/App.test.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); +}); diff --git a/example/demo/in-react/src/index.css b/example/demo/in-react/src/index.css new file mode 100644 index 000000000..b4cc7250b --- /dev/null +++ b/example/demo/in-react/src/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/example/demo/in-react/src/index.js b/example/demo/in-react/src/index.js new file mode 100644 index 000000000..53c768842 --- /dev/null +++ b/example/demo/in-react/src/index.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; +import registerServiceWorker from './registerServiceWorker'; +import './index.css'; + +ReactDOM.render(, document.getElementById('root')); +registerServiceWorker(); diff --git a/example/demo/in-react/src/logo.svg b/example/demo/in-react/src/logo.svg new file mode 100644 index 000000000..6b60c1042 --- /dev/null +++ b/example/demo/in-react/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/example/demo/in-react/src/registerServiceWorker.js b/example/demo/in-react/src/registerServiceWorker.js new file mode 100644 index 000000000..9966897dc --- /dev/null +++ b/example/demo/in-react/src/registerServiceWorker.js @@ -0,0 +1,51 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); + }); + } +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/example/demo/in-vue/.babelrc b/example/demo/in-vue/.babelrc new file mode 100644 index 000000000..13f0e4716 --- /dev/null +++ b/example/demo/in-vue/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + ["env", { "modules": false }], + "stage-2" + ], + "plugins": ["transform-runtime"], + "comments": false, + "env": { + "test": { + "presets": ["env", "stage-2"], + "plugins": [ "istanbul" ] + } + } +} diff --git a/example/demo/in-vue/.editorconfig b/example/demo/in-vue/.editorconfig new file mode 100644 index 000000000..9d08a1a82 --- /dev/null +++ b/example/demo/in-vue/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/example/demo/in-vue/.postcssrc.js b/example/demo/in-vue/.postcssrc.js new file mode 100644 index 000000000..ea9a5ab87 --- /dev/null +++ b/example/demo/in-vue/.postcssrc.js @@ -0,0 +1,8 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + "plugins": { + // to edit target browsers: use "browserlist" field in package.json + "autoprefixer": {} + } +} diff --git a/example/demo/in-vue/build/build.js b/example/demo/in-vue/build/build.js new file mode 100644 index 000000000..6b8add100 --- /dev/null +++ b/example/demo/in-vue/build/build.js @@ -0,0 +1,35 @@ +require('./check-versions')() + +process.env.NODE_ENV = 'production' + +var ora = require('ora') +var rm = require('rimraf') +var path = require('path') +var chalk = require('chalk') +var webpack = require('webpack') +var config = require('../config') +var webpackConfig = require('./webpack.prod.conf') + +var spinner = ora('building for production...') +spinner.start() + +rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { + if (err) throw err + webpack(webpackConfig, function (err, stats) { + spinner.stop() + if (err) throw err + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, + chunks: false, + chunkModules: false + }) + '\n\n') + + console.log(chalk.cyan(' Build complete.\n')) + console.log(chalk.yellow( + ' Tip: built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' + )) + }) +}) diff --git a/example/demo/in-vue/build/check-versions.js b/example/demo/in-vue/build/check-versions.js new file mode 100644 index 000000000..100f3a0fe --- /dev/null +++ b/example/demo/in-vue/build/check-versions.js @@ -0,0 +1,48 @@ +var chalk = require('chalk') +var semver = require('semver') +var packageConfig = require('../package.json') +var shell = require('shelljs') +function exec (cmd) { + return require('child_process').execSync(cmd).toString().trim() +} + +var versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node + }, +] + +if (shell.which('npm')) { + versionRequirements.push({ + name: 'npm', + currentVersion: exec('npm --version'), + versionRequirement: packageConfig.engines.npm + }) +} + +module.exports = function () { + var warnings = [] + for (var i = 0; i < versionRequirements.length; i++) { + var mod = versionRequirements[i] + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push(mod.name + ': ' + + chalk.red(mod.currentVersion) + ' should be ' + + chalk.green(mod.versionRequirement) + ) + } + } + + if (warnings.length) { + console.log('') + console.log(chalk.yellow('To use this template, you must update following to modules:')) + console.log() + for (var i = 0; i < warnings.length; i++) { + var warning = warnings[i] + console.log(' ' + warning) + } + console.log() + process.exit(1) + } +} diff --git a/example/demo/in-vue/build/dev-client.js b/example/demo/in-vue/build/dev-client.js new file mode 100644 index 000000000..18aa1e219 --- /dev/null +++ b/example/demo/in-vue/build/dev-client.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +require('eventsource-polyfill') +var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') + +hotClient.subscribe(function (event) { + if (event.action === 'reload') { + window.location.reload() + } +}) diff --git a/example/demo/in-vue/build/dev-server.js b/example/demo/in-vue/build/dev-server.js new file mode 100644 index 000000000..782dc6fc7 --- /dev/null +++ b/example/demo/in-vue/build/dev-server.js @@ -0,0 +1,89 @@ +require('./check-versions')() + +var config = require('../config') +if (!process.env.NODE_ENV) { + process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) +} + +var opn = require('opn') +var path = require('path') +var express = require('express') +var webpack = require('webpack') +var proxyMiddleware = require('http-proxy-middleware') +var webpackConfig = require('./webpack.dev.conf') + +// default port where dev server listens for incoming traffic +var port = process.env.PORT || config.dev.port +// automatically open browser, if not set will be false +var autoOpenBrowser = !!config.dev.autoOpenBrowser +// Define HTTP proxies to your custom API backend +// https://github.com/chimurai/http-proxy-middleware +var proxyTable = config.dev.proxyTable + +var app = express() +var compiler = webpack(webpackConfig) + +var devMiddleware = require('webpack-dev-middleware')(compiler, { + publicPath: webpackConfig.output.publicPath, + quiet: true +}) + +var hotMiddleware = require('webpack-hot-middleware')(compiler, { + log: () => {} +}) +// force page reload when html-webpack-plugin template changes +compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { + hotMiddleware.publish({ action: 'reload' }) + cb() + }) +}) + +// proxy api requests +Object.keys(proxyTable).forEach(function (context) { + var options = proxyTable[context] + if (typeof options === 'string') { + options = { target: options } + } + app.use(proxyMiddleware(options.filter || context, options)) +}) + +// handle fallback for HTML5 history API +app.use(require('connect-history-api-fallback')()) + +// serve webpack bundle output +app.use(devMiddleware) + +// enable hot-reload and state-preserving +// compilation error display +app.use(hotMiddleware) + +// serve pure static assets +var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) +app.use(staticPath, express.static('./static')) + +var uri = 'http://localhost:' + port + +var _resolve +var readyPromise = new Promise(resolve => { + _resolve = resolve +}) + +console.log('> Starting dev server...') +devMiddleware.waitUntilValid(() => { + console.log('> Listening at ' + uri + '\n') + // when env is testing, don't need open it + if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { + opn(uri) + } + _resolve() +}) + +var server = app.listen(port) + +module.exports = { + ready: readyPromise, + close: () => { + server.close() + } +} diff --git a/example/demo/in-vue/build/utils.js b/example/demo/in-vue/build/utils.js new file mode 100644 index 000000000..b1d54b4d6 --- /dev/null +++ b/example/demo/in-vue/build/utils.js @@ -0,0 +1,71 @@ +var path = require('path') +var config = require('../config') +var ExtractTextPlugin = require('extract-text-webpack-plugin') + +exports.assetsPath = function (_path) { + var assetsSubDirectory = process.env.NODE_ENV === 'production' + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory + return path.posix.join(assetsSubDirectory, _path) +} + +exports.cssLoaders = function (options) { + options = options || {} + + var cssLoader = { + loader: 'css-loader', + options: { + minimize: process.env.NODE_ENV === 'production', + sourceMap: options.sourceMap + } + } + + // generate loader string to be used with extract text plugin + function generateLoaders (loader, loaderOptions) { + var loaders = [cssLoader] + if (loader) { + loaders.push({ + loader: loader + '-loader', + options: Object.assign({}, loaderOptions, { + sourceMap: options.sourceMap + }) + }) + } + + // Extract CSS when that option is specified + // (which is the case during production build) + if (options.extract) { + return ExtractTextPlugin.extract({ + use: loaders, + fallback: 'vue-style-loader' + }) + } else { + return ['vue-style-loader'].concat(loaders) + } + } + + // https://vue-loader.vuejs.org/en/configurations/extract-css.html + return { + css: generateLoaders(), + postcss: generateLoaders(), + less: generateLoaders('less'), + sass: generateLoaders('sass', { indentedSyntax: true }), + scss: generateLoaders('sass'), + stylus: generateLoaders('stylus'), + styl: generateLoaders('stylus') + } +} + +// Generate loaders for standalone style files (outside of .vue) +exports.styleLoaders = function (options) { + var output = [] + var loaders = exports.cssLoaders(options) + for (var extension in loaders) { + var loader = loaders[extension] + output.push({ + test: new RegExp('\\.' + extension + '$'), + use: loader + }) + } + return output +} diff --git a/example/demo/in-vue/build/vue-loader.conf.js b/example/demo/in-vue/build/vue-loader.conf.js new file mode 100644 index 000000000..7aee79bae --- /dev/null +++ b/example/demo/in-vue/build/vue-loader.conf.js @@ -0,0 +1,12 @@ +var utils = require('./utils') +var config = require('../config') +var isProduction = process.env.NODE_ENV === 'production' + +module.exports = { + loaders: utils.cssLoaders({ + sourceMap: isProduction + ? config.build.productionSourceMap + : config.dev.cssSourceMap, + extract: isProduction + }) +} diff --git a/example/demo/in-vue/build/webpack.base.conf.js b/example/demo/in-vue/build/webpack.base.conf.js new file mode 100644 index 000000000..daa358983 --- /dev/null +++ b/example/demo/in-vue/build/webpack.base.conf.js @@ -0,0 +1,58 @@ +var path = require('path') +var utils = require('./utils') +var config = require('../config') +var vueLoaderConfig = require('./vue-loader.conf') + +function resolve (dir) { + return path.join(__dirname, '..', dir) +} + +module.exports = { + entry: { + app: './src/main.js' + }, + output: { + path: config.build.assetsRoot, + filename: '[name].js', + publicPath: process.env.NODE_ENV === 'production' + ? config.build.assetsPublicPath + : config.dev.assetsPublicPath + }, + resolve: { + extensions: ['.js', '.vue', '.json'], + alias: { + 'vue$': 'vue/dist/vue.esm.js', + '@': resolve('src') + } + }, + module: { + rules: [ + { + test: /\.vue$/, + loader: 'vue-loader', + options: vueLoaderConfig + }, + { + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('test')] + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } + } + ] + } +} diff --git a/example/demo/in-vue/build/webpack.dev.conf.js b/example/demo/in-vue/build/webpack.dev.conf.js new file mode 100644 index 000000000..5470402d0 --- /dev/null +++ b/example/demo/in-vue/build/webpack.dev.conf.js @@ -0,0 +1,35 @@ +var utils = require('./utils') +var webpack = require('webpack') +var config = require('../config') +var merge = require('webpack-merge') +var baseWebpackConfig = require('./webpack.base.conf') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') + +// add hot-reload related code to entry chunks +Object.keys(baseWebpackConfig.entry).forEach(function (name) { + baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) +}) + +module.exports = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) + }, + // cheap-module-eval-source-map is faster for development + devtool: '#cheap-module-eval-source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': config.dev.env + }), + // https://github.com/glenjamin/webpack-hot-middleware#installation--usage + new webpack.HotModuleReplacementPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true + }), + new FriendlyErrorsPlugin() + ] +}) diff --git a/example/demo/in-vue/build/webpack.prod.conf.js b/example/demo/in-vue/build/webpack.prod.conf.js new file mode 100644 index 000000000..da44b656c --- /dev/null +++ b/example/demo/in-vue/build/webpack.prod.conf.js @@ -0,0 +1,120 @@ +var path = require('path') +var utils = require('./utils') +var webpack = require('webpack') +var config = require('../config') +var merge = require('webpack-merge') +var baseWebpackConfig = require('./webpack.base.conf') +var CopyWebpackPlugin = require('copy-webpack-plugin') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var ExtractTextPlugin = require('extract-text-webpack-plugin') +var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') + +var env = config.build.env + +var webpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ + sourceMap: config.build.productionSourceMap, + extract: true + }) + }, + devtool: config.build.productionSourceMap ? '#source-map' : false, + output: { + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') + }, + plugins: [ + // http://vuejs.github.io/vue-loader/en/workflow/production.html + new webpack.DefinePlugin({ + 'process.env': env + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false + }, + sourceMap: true + }), + // extract css into its own file + new ExtractTextPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css') + }), + // Compress extracted CSS. We are using this plugin so that possible + // duplicated CSS from different components can be deduped. + new OptimizeCSSPlugin({ + cssProcessorOptions: { + safe: true + } + }), + // generate dist index.html with correct asset hash for caching. + // you can customize output by editing /index.html + // see https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: config.build.index, + template: 'index.html', + inject: true, + minify: { + removeComments: true, + collapseWhitespace: true, + removeAttributeQuotes: true + // more options: + // https://github.com/kangax/html-minifier#options-quick-reference + }, + // necessary to consistently work with multiple chunks via CommonsChunkPlugin + chunksSortMode: 'dependency' + }), + // split vendor js into its own file + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function (module, count) { + // any required modules inside node_modules are extracted to vendor + return ( + module.resource && + /\.js$/.test(module.resource) && + module.resource.indexOf( + path.join(__dirname, '../node_modules') + ) === 0 + ) + } + }), + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + chunks: ['vendor'] + }), + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.build.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +if (config.build.productionGzip) { + var CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +if (config.build.bundleAnalyzerReport) { + var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + webpackConfig.plugins.push(new BundleAnalyzerPlugin()) +} + +module.exports = webpackConfig diff --git a/example/demo/in-vue/config/dev.env.js b/example/demo/in-vue/config/dev.env.js new file mode 100644 index 000000000..efead7c84 --- /dev/null +++ b/example/demo/in-vue/config/dev.env.js @@ -0,0 +1,6 @@ +var merge = require('webpack-merge') +var prodEnv = require('./prod.env') + +module.exports = merge(prodEnv, { + NODE_ENV: '"development"' +}) diff --git a/example/demo/in-vue/config/index.js b/example/demo/in-vue/config/index.js new file mode 100644 index 000000000..196da1fa7 --- /dev/null +++ b/example/demo/in-vue/config/index.js @@ -0,0 +1,38 @@ +// see http://vuejs-templates.github.io/webpack for documentation. +var path = require('path') + +module.exports = { + build: { + env: require('./prod.env'), + index: path.resolve(__dirname, '../dist/index.html'), + assetsRoot: path.resolve(__dirname, '../dist'), + assetsSubDirectory: 'static', + assetsPublicPath: '/', + productionSourceMap: true, + // Gzip off by default as many popular static hosts such as + // Surge or Netlify already gzip all static assets for you. + // Before setting to `true`, make sure to: + // npm install --save-dev compression-webpack-plugin + productionGzip: false, + productionGzipExtensions: ['js', 'css'], + // Run the build command with an extra argument to + // View the bundle analyzer report after build finishes: + // `npm run build --report` + // Set to `true` or `false` to always turn it on or off + bundleAnalyzerReport: process.env.npm_config_report + }, + dev: { + env: require('./dev.env'), + port: 8080, + autoOpenBrowser: true, + assetsSubDirectory: 'static', + assetsPublicPath: '/', + proxyTable: {}, + // CSS Sourcemaps off by default because relative paths are "buggy" + // with this option, according to the CSS-Loader README + // (https://github.com/webpack/css-loader#sourcemaps) + // In our experience, they generally work as expected, + // just be aware of this issue when enabling this option. + cssSourceMap: false + } +} diff --git a/example/demo/in-vue/config/prod.env.js b/example/demo/in-vue/config/prod.env.js new file mode 100644 index 000000000..773d263d3 --- /dev/null +++ b/example/demo/in-vue/config/prod.env.js @@ -0,0 +1,3 @@ +module.exports = { + NODE_ENV: '"production"' +} diff --git a/example/demo/in-vue/index.html b/example/demo/in-vue/index.html new file mode 100644 index 000000000..47ae14a39 --- /dev/null +++ b/example/demo/in-vue/index.html @@ -0,0 +1,11 @@ + + + + + wangeditor-in-vue + + +
+ + + diff --git a/example/demo/in-vue/package.json b/example/demo/in-vue/package.json new file mode 100644 index 000000000..80cf68fd5 --- /dev/null +++ b/example/demo/in-vue/package.json @@ -0,0 +1,60 @@ +{ + "name": "wangeditor-in-vue", + "version": "1.0.0", + "description": "A Vue.js project", + "author": "git ", + "private": true, + "scripts": { + "dev": "node build/dev-server.js", + "start": "node build/dev-server.js", + "build": "node build/build.js" + }, + "dependencies": { + "vue": "^2.3.3", + "wangeditor": ">=3.0.0" + }, + "devDependencies": { + "autoprefixer": "^6.7.2", + "babel-core": "^6.22.1", + "babel-loader": "^6.2.10", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-2": "^6.22.0", + "babel-register": "^6.22.0", + "chalk": "^1.1.3", + "connect-history-api-fallback": "^1.3.0", + "copy-webpack-plugin": "^4.0.1", + "css-loader": "^0.28.0", + "eventsource-polyfill": "^0.9.6", + "express": "^4.14.1", + "extract-text-webpack-plugin": "^2.0.0", + "file-loader": "^0.11.1", + "friendly-errors-webpack-plugin": "^1.1.3", + "html-webpack-plugin": "^2.28.0", + "http-proxy-middleware": "^0.17.3", + "webpack-bundle-analyzer": "^2.2.1", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "opn": "^4.0.2", + "optimize-css-assets-webpack-plugin": "^1.3.0", + "ora": "^1.2.0", + "rimraf": "^2.6.0", + "url-loader": "^0.5.8", + "vue-loader": "^12.1.0", + "vue-style-loader": "^3.0.1", + "vue-template-compiler": "^2.3.3", + "webpack": "^2.6.1", + "webpack-dev-middleware": "^1.10.0", + "webpack-hot-middleware": "^2.18.0", + "webpack-merge": "^4.1.0" + }, + "engines": { + "node": ">= 4.0.0", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ] +} diff --git a/example/demo/in-vue/src/App.vue b/example/demo/in-vue/src/App.vue new file mode 100644 index 000000000..27d15ffa3 --- /dev/null +++ b/example/demo/in-vue/src/App.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/example/demo/in-vue/src/assets/logo.png b/example/demo/in-vue/src/assets/logo.png new file mode 100644 index 000000000..f3d2503fc Binary files /dev/null and b/example/demo/in-vue/src/assets/logo.png differ diff --git a/example/demo/in-vue/src/components/Editor.vue b/example/demo/in-vue/src/components/Editor.vue new file mode 100644 index 000000000..26dbcbdfa --- /dev/null +++ b/example/demo/in-vue/src/components/Editor.vue @@ -0,0 +1,34 @@ + + + + + \ No newline at end of file diff --git a/example/demo/in-vue/src/components/Hello.vue b/example/demo/in-vue/src/components/Hello.vue new file mode 100644 index 000000000..2d8053956 --- /dev/null +++ b/example/demo/in-vue/src/components/Hello.vue @@ -0,0 +1,53 @@ + + + + + + diff --git a/example/demo/in-vue/src/main.js b/example/demo/in-vue/src/main.js new file mode 100644 index 000000000..7b7fec763 --- /dev/null +++ b/example/demo/in-vue/src/main.js @@ -0,0 +1,13 @@ +// The Vue build version to load with the `import` command +// (runtime-only or standalone) has been set in webpack.base.conf with an alias. +import Vue from 'vue' +import App from './App' + +Vue.config.productionTip = false + +/* eslint-disable no-new */ +new Vue({ + el: '#app', + template: '', + components: { App } +}) diff --git a/packages/list-module/src/assets/index.less b/example/demo/in-vue/static/.gitkeep similarity index 100% rename from packages/list-module/src/assets/index.less rename to example/demo/in-vue/static/.gitkeep diff --git a/example/demo/test-amd-main.js b/example/demo/test-amd-main.js new file mode 100644 index 000000000..444b2da79 --- /dev/null +++ b/example/demo/test-amd-main.js @@ -0,0 +1,4 @@ +require(['/wangEditor.min.js'], function (E) { + var editor2 = new E('#div3') + editor2.create() +}) \ No newline at end of file diff --git a/example/demo/test-amd.html b/example/demo/test-amd.html new file mode 100644 index 000000000..b784b30cf --- /dev/null +++ b/example/demo/test-amd.html @@ -0,0 +1,15 @@ + + + + + wangEditor 使用 AMD 加载 + + +

wangEditor 使用 AMD 加载

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + \ No newline at end of file diff --git a/example/demo/test-fullscreen.html b/example/demo/test-fullscreen.html new file mode 100644 index 000000000..fbc747a51 --- /dev/null +++ b/example/demo/test-fullscreen.html @@ -0,0 +1,114 @@ + + + + + wangEditor 全屏 + + + +

wangEditor 全屏

+ + +
+ +
+
+
+ +
+
+ +
+

wangEditor 本身不包含“全屏”功能,不过可以很简单的开发出来

+

注意,全屏模式与max-height有冲突,尽量避免一起使用

+
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/example/demo/test-get-content.html b/example/demo/test-get-content.html new file mode 100644 index 000000000..c5f494e2a --- /dev/null +++ b/example/demo/test-get-content.html @@ -0,0 +1,34 @@ + + + + + wangEditor 获取内容 + + +

wangEditor 获取内容

+
+

欢迎使用 wangEditor 富文本编辑器

+

欢迎使用 wangEditor 富文本编辑器

+
+
+ + +
+ + + + + \ No newline at end of file diff --git a/example/demo/test-menus.html b/example/demo/test-menus.html new file mode 100644 index 000000000..7e921f611 --- /dev/null +++ b/example/demo/test-menus.html @@ -0,0 +1,26 @@ + + + + + wangEditor 菜单配置 + + +

wangEditor 自定义菜单配置

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/example/demo/test-mult.html b/example/demo/test-mult.html new file mode 100644 index 000000000..bd6f7e102 --- /dev/null +++ b/example/demo/test-mult.html @@ -0,0 +1,44 @@ + + + + + wangEditor 一个页面多个编辑器 + + + + +

第一个 demo(菜单和编辑器区域分开)

+
+
+
中间隔离带
+
+

请输入内容

+
+ +

第二个 demo(常规)

+
+

请输入内容

+
+ + + + + + \ No newline at end of file diff --git a/example/demo/test-set-content.html b/example/demo/test-set-content.html new file mode 100644 index 000000000..269029d87 --- /dev/null +++ b/example/demo/test-set-content.html @@ -0,0 +1,35 @@ + + + + + wangEditor 设置内容 + + +

wangEditor 设置内容

+
+

欢迎使用 wangEditor 富文本编辑器

+
+
+ + +
+ + + + + \ No newline at end of file diff --git a/example/demo/test-sperate.html b/example/demo/test-sperate.html new file mode 100644 index 000000000..19a7894ae --- /dev/null +++ b/example/demo/test-sperate.html @@ -0,0 +1,35 @@ + + + + + wangEditor 菜单和编辑器区域分离 + + + + +

wangEditor 菜单和编辑器区域分离

+
+
+
中间隔离带
+
+

请输入内容

+
+ + + + + \ No newline at end of file diff --git a/example/demo/test-uploadimg.html b/example/demo/test-uploadimg.html new file mode 100644 index 000000000..9c010364a --- /dev/null +++ b/example/demo/test-uploadimg.html @@ -0,0 +1,31 @@ + + + + + wangEditor 上传图片 + + +

wangEditor 上传图片到服务器

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ +

wangEditor 以base64保存图片文件

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/packages/editor/favicon.ico b/example/favicon.ico similarity index 100% rename from packages/editor/favicon.ico rename to example/favicon.ico diff --git a/example/icomoon/Read Me.txt b/example/icomoon/Read Me.txt new file mode 100644 index 000000000..8491652f8 --- /dev/null +++ b/example/icomoon/Read Me.txt @@ -0,0 +1,7 @@ +Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. + +To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts + +You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. + +You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. diff --git a/example/icomoon/demo-files/demo.css b/example/icomoon/demo-files/demo.css new file mode 100644 index 000000000..f9ab27c08 --- /dev/null +++ b/example/icomoon/demo-files/demo.css @@ -0,0 +1,155 @@ +body { + padding: 0; + margin: 0; + font-family: sans-serif; + font-size: 1em; + line-height: 1.5; + color: #555; + background: #fff; +} +h1 { + font-size: 1.5em; + font-weight: normal; +} +small { + font-size: .66666667em; +} +a { + color: #e74c3c; + text-decoration: none; +} +a:hover, a:focus { + box-shadow: 0 1px #e74c3c; +} +.bshadow0, input { + box-shadow: inset 0 -2px #e7e7e7; +} +input:hover { + box-shadow: inset 0 -2px #ccc; +} +input, fieldset { + font-family: sans-serif; + font-size: 1em; + margin: 0; + padding: 0; + border: 0; +} +input { + color: inherit; + line-height: 1.5; + height: 1.5em; + padding: .25em 0; +} +input:focus { + outline: none; + box-shadow: inset 0 -2px #449fdb; +} +.glyph { + font-size: 16px; + width: 15em; + padding-bottom: 1em; + margin-right: 4em; + margin-bottom: 1em; + float: left; + overflow: hidden; +} +.liga { + width: 80%; + width: calc(100% - 2.5em); +} +.talign-right { + text-align: right; +} +.talign-center { + text-align: center; +} +.bgc1 { + background: #f1f1f1; +} +.fgc1 { + color: #999; +} +.fgc0 { + color: #000; +} +p { + margin-top: 1em; + margin-bottom: 1em; +} +.mvm { + margin-top: .75em; + margin-bottom: .75em; +} +.mtn { + margin-top: 0; +} +.mtl, .mal { + margin-top: 1.5em; +} +.mbl, .mal { + margin-bottom: 1.5em; +} +.mal, .mhl { + margin-left: 1.5em; + margin-right: 1.5em; +} +.mhmm { + margin-left: 1em; + margin-right: 1em; +} +.mls { + margin-left: .25em; +} +.ptl { + padding-top: 1.5em; +} +.pbs, .pvs { + padding-bottom: .25em; +} +.pvs, .pts { + padding-top: .25em; +} +.unit { + float: left; +} +.unitRight { + float: right; +} +.size1of2 { + width: 50%; +} +.size1of1 { + width: 100%; +} +.clearfix:before, .clearfix:after { + content: " "; + display: table; +} +.clearfix:after { + clear: both; +} +.hidden-true { + display: none; +} +.textbox0 { + width: 3em; + background: #f1f1f1; + padding: .25em .5em; + line-height: 1.5; + height: 1.5em; +} +#testDrive { + display: block; + padding-top: 24px; + line-height: 1.5; +} +.fs0 { + font-size: 16px; +} +.fs1 { + font-size: 16px; +} +.fs2 { + font-size: 16px; +} + diff --git a/example/icomoon/demo-files/demo.js b/example/icomoon/demo-files/demo.js new file mode 100644 index 000000000..6f45f1c40 --- /dev/null +++ b/example/icomoon/demo-files/demo.js @@ -0,0 +1,30 @@ +if (!('boxShadow' in document.body.style)) { + document.body.setAttribute('class', 'noBoxShadow'); +} + +document.body.addEventListener("click", function(e) { + var target = e.target; + if (target.tagName === "INPUT" && + target.getAttribute('class').indexOf('liga') === -1) { + target.select(); + } +}); + +(function() { + var fontSize = document.getElementById('fontSize'), + testDrive = document.getElementById('testDrive'), + testText = document.getElementById('testText'); + function updateTest() { + testDrive.innerHTML = testText.value || String.fromCharCode(160); + if (window.icomoonLiga) { + window.icomoonLiga(testDrive); + } + } + function updateSize() { + testDrive.style.fontSize = fontSize.value + 'px'; + } + fontSize.addEventListener('change', updateSize, false); + testText.addEventListener('input', updateTest, false); + testText.addEventListener('change', updateTest, false); + updateSize(); +}()); diff --git a/example/icomoon/demo.html b/example/icomoon/demo.html new file mode 100644 index 000000000..a36d3ba0b --- /dev/null +++ b/example/icomoon/demo.html @@ -0,0 +1,505 @@ + + + + + IcoMoon Demo + + + + + +
+

Font Name: icomoon (Glyphs: 27)

+
+
+

Grid Size: 14

+
+
+ + + + icon-close +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-remove +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-times +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-trash-o +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-terminal +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-header +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paint-brush +
+
+ + +
+
+ liga: + +
+
+
+
+

Grid Size: 16

+
+
+ + + + icon-pencil2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-image +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-play +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-location +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-undo +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-redo +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-quotes-left +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-list-numbered +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-list2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-upload2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-link +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-happy +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-cancel-circle +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-bold +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-underline +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-italic +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-strikethrough +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-page-break +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-table2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paragraph-left +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paragraph-center +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paragraph-right +
+
+ + +
+
+ liga: + +
+
+
+ + +
+

Font Test Drive

+ + +
  +
+
+ +
+

Generated by IcoMoon

+
+ + + + diff --git a/example/icomoon/fonts/icomoon.eot b/example/icomoon/fonts/icomoon.eot new file mode 100644 index 000000000..0d144fdfa Binary files /dev/null and b/example/icomoon/fonts/icomoon.eot differ diff --git a/example/icomoon/fonts/icomoon.svg b/example/icomoon/fonts/icomoon.svg new file mode 100644 index 000000000..21be01686 --- /dev/null +++ b/example/icomoon/fonts/icomoon.svg @@ -0,0 +1,37 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/icomoon/fonts/icomoon.ttf b/example/icomoon/fonts/icomoon.ttf new file mode 100644 index 000000000..80be9ad27 Binary files /dev/null and b/example/icomoon/fonts/icomoon.ttf differ diff --git a/example/icomoon/fonts/icomoon.woff b/example/icomoon/fonts/icomoon.woff new file mode 100644 index 000000000..fa64c4d86 Binary files /dev/null and b/example/icomoon/fonts/icomoon.woff differ diff --git a/example/icomoon/selection.json b/example/icomoon/selection.json new file mode 100644 index 000000000..b4a875fb4 --- /dev/null +++ b/example/icomoon/selection.json @@ -0,0 +1,775 @@ +{ + "IcoMoonType": "selection", + "icons": [ + { + "icon": { + "paths": [ + "M741.714 755.429q0 22.857-16 38.857l-77.714 77.714q-16 16-38.857 16t-38.857-16l-168-168-168 168q-16 16-38.857 16t-38.857-16l-77.714-77.714q-16-16-16-38.857t16-38.857l168-168-168-168q-16-16-16-38.857t16-38.857l77.714-77.714q16-16 38.857-16t38.857 16l168 168 168-168q16-16 38.857-16t38.857 16l77.714 77.714q16 16 16 38.857t-16 38.857l-168 168 168 168q16 16 16 38.857z" + ], + "width": 804.5720062255859, + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "close", + "remove", + "times" + ], + "defaultCode": 61453, + "grid": 14 + }, + "attrs": [], + "properties": { + "name": "close, remove, times", + "id": 13, + "order": 60, + "prevSize": 16, + "code": 61453 + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 13 + }, + { + "icon": { + "paths": [ + "M292.571 420.571v329.143q0 8-5.143 13.143t-13.143 5.143h-36.571q-8 0-13.143-5.143t-5.143-13.143v-329.143q0-8 5.143-13.143t13.143-5.143h36.571q8 0 13.143 5.143t5.143 13.143zM438.857 420.571v329.143q0 8-5.143 13.143t-13.143 5.143h-36.571q-8 0-13.143-5.143t-5.143-13.143v-329.143q0-8 5.143-13.143t13.143-5.143h36.571q8 0 13.143 5.143t5.143 13.143zM585.143 420.571v329.143q0 8-5.143 13.143t-13.143 5.143h-36.571q-8 0-13.143-5.143t-5.143-13.143v-329.143q0-8 5.143-13.143t13.143-5.143h36.571q8 0 13.143 5.143t5.143 13.143zM658.286 834.286v-541.714h-512v541.714q0 12.571 4 23.143t8.286 15.429 6 4.857h475.429q1.714 0 6-4.857t8.286-15.429 4-23.143zM274.286 219.429h256l-27.429-66.857q-4-5.143-9.714-6.286h-181.143q-5.714 1.143-9.714 6.286zM804.571 237.714v36.571q0 8-5.143 13.143t-13.143 5.143h-54.857v541.714q0 47.429-26.857 82t-64.571 34.571h-475.429q-37.714 0-64.571-33.429t-26.857-80.857v-544h-54.857q-8 0-13.143-5.143t-5.143-13.143v-36.571q0-8 5.143-13.143t13.143-5.143h176.571l40-95.429q8.571-21.143 30.857-36t45.143-14.857h182.857q22.857 0 45.143 14.857t30.857 36l40 95.429h176.571q8 0 13.143 5.143t5.143 13.143z" + ], + "width": 804.5710134506226, + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "trash-o" + ], + "defaultCode": 61460, + "grid": 14 + }, + "attrs": [], + "properties": { + "name": "trash-o", + "id": 19, + "order": 53, + "prevSize": 16, + "code": 61460 + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 19 + }, + { + "icon": { + "paths": [ + "M334.286 561.714l-266.286 266.286q-5.714 5.714-13.143 5.714t-13.143-5.714l-28.571-28.571q-5.714-5.714-5.714-13.143t5.714-13.143l224.571-224.571-224.571-224.571q-5.714-5.714-5.714-13.143t5.714-13.143l28.571-28.571q5.714-5.714 13.143-5.714t13.143 5.714l266.286 266.286q5.714 5.714 5.714 13.143t-5.714 13.143zM950.857 822.857v36.571q0 8-5.143 13.143t-13.143 5.143h-548.571q-8 0-13.143-5.143t-5.143-13.143v-36.571q0-8 5.143-13.143t13.143-5.143h548.571q8 0 13.143 5.143t5.143 13.143z" + ], + "width": 958.2859897613525, + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "terminal" + ], + "defaultCode": 61728, + "grid": 14 + }, + "attrs": [], + "properties": { + "name": "terminal", + "id": 256, + "order": 55, + "prevSize": 16, + "code": 61728 + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 256 + }, + { + "icon": { + "paths": [ + "M961.143 950.857q-25.143 0-75.714-2t-76.286-2q-25.143 0-75.429 2t-75.429 2q-13.714 0-21.143-11.714t-7.429-26q0-17.714 9.714-26.286t22.286-9.714 29.143-4 25.714-8.571q18.857-12 18.857-80l-0.571-223.429q0-12-0.571-17.714-7.429-2.286-28.571-2.286h-385.714q-21.714 0-29.143 2.286-0.571 5.714-0.571 17.714l-0.571 212q0 81.143 21.143 93.714 9.143 5.714 27.429 7.429t32.571 2 25.714 8.571 11.429 26q0 14.857-7.143 27.429t-20.857 12.571q-26.857 0-79.714-2t-79.143-2q-24.571 0-73.143 2t-72.571 2q-13.143 0-20.286-12t-7.143-25.714q0-17.143 8.857-25.714t20.571-10 27.143-4.286 24-8.571q18.857-13.143 18.857-81.714l-0.571-32.571v-464.571q0-1.714 0.286-14.857t0-20.857-0.857-22-2-24-3.714-20.857-6.286-18-9.143-10.286q-8.571-5.714-25.714-6.857t-30.286-1.143-23.429-8-10.286-25.714q0-14.857 6.857-27.429t20.571-12.571q26.286 0 79.143 2t79.143 2q24 0 72.286-2t72.286-2q14.286 0 21.429 12.571t7.143 27.429q0 17.143-9.714 24.857t-22 8.286-28.286 2.286-24.571 7.429q-20 12-20 91.429l0.571 182.857q0 12 0.571 18.286 7.429 1.714 22.286 1.714h399.429q14.286 0 21.714-1.714 0.571-6.286 0.571-18.286l0.571-182.857q0-79.429-20-91.429-10.286-6.286-33.429-7.143t-37.714-7.429-14.571-28.286q0-14.857 7.143-27.429t21.429-12.571q25.143 0 75.429 2t75.429 2q24.571 0 73.714-2t73.714-2q14.286 0 21.429 12.571t7.143 27.429q0 17.143-10 25.143t-22.857 8.286-29.429 1.714-25.143 7.143q-20 13.143-20 92l0.571 538.857q0 68 19.429 80 9.143 5.714 26.286 7.714t30.571 2.571 23.714 8.857 10.286 25.429q0 14.857-6.857 27.429t-20.571 12.571z" + ], + "width": 1024.001937866211, + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "header" + ], + "defaultCode": 61916, + "grid": 14 + }, + "attrs": [], + "properties": { + "name": "header", + "id": 433, + "order": 49, + "prevSize": 16, + "code": 61916 + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 433 + }, + { + "icon": { + "paths": [ + "M922.857 0q40 0 70 26.571t30 66.571q0 36-25.714 86.286-189.714 359.429-265.714 429.714-55.429 52-124.571 52-72 0-123.714-52.857t-51.714-125.429q0-73.143 52.571-121.143l364.571-330.857q33.714-30.857 74.286-30.857zM403.429 590.857q22.286 43.429 60.857 74.286t86 43.429l0.571 40.571q2.286 121.714-74 198.286t-199.143 76.571q-70.286 0-124.571-26.571t-87.143-72.857-49.429-104.571-16.571-125.714q4 2.857 23.429 17.143t35.429 25.429 33.714 20.857 26.286 9.714q23.429 0 31.429-21.143 14.286-37.714 32.857-64.286t39.714-43.429 50.286-27.143 58.857-14.571 71.429-6z" + ], + "width": 1022.8569793701172, + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "paint-brush" + ], + "defaultCode": 61948, + "grid": 14 + }, + "attrs": [], + "properties": { + "name": "paint-brush", + "id": 463, + "order": 54, + "prevSize": 16, + "code": 61948 + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 463 + }, + { + "icon": { + "paths": [ + "M384 640l128-64 448-448-64-64-448 448-64 128zM289.3 867.098c-31.632-66.728-65.666-100.762-132.396-132.394l99.096-272.792 128-77.912 384-384h-192l-384 384-192 640 640-192 384-384v-192l-384 384-77.912 128z" + ], + "tags": [ + "pencil", + "write", + "edit" + ], + "defaultCode": 59654, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "pencil2, write2", + "name": "pencil2", + "order": 32, + "id": 7, + "prevSize": 16, + "code": 59654 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 6 + }, + { + "icon": { + "paths": [ + "M959.884 128c0.040 0.034 0.082 0.076 0.116 0.116v767.77c-0.034 0.040-0.076 0.082-0.116 0.116h-895.77c-0.040-0.034-0.082-0.076-0.114-0.116v-767.772c0.034-0.040 0.076-0.082 0.114-0.114h895.77zM960 64h-896c-35.2 0-64 28.8-64 64v768c0 35.2 28.8 64 64 64h896c35.2 0 64-28.8 64-64v-768c0-35.2-28.8-64-64-64v0z", + "M832 288c0 53.020-42.98 96-96 96s-96-42.98-96-96 42.98-96 96-96 96 42.98 96 96z", + "M896 832h-768v-128l224-384 256 320h64l224-192z" + ], + "tags": [ + "image", + "picture", + "photo", + "graphic" + ], + "defaultCode": 59661, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "image, picture", + "name": "image", + "order": 44, + "id": 14, + "prevSize": 16, + "code": 59661 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 13 + }, + { + "icon": { + "paths": [ + "M981.188 160.108c-143.632-20.65-302.332-32.108-469.186-32.108-166.86 0-325.556 11.458-469.194 32.108-27.53 107.726-42.808 226.75-42.808 351.892 0 125.14 15.278 244.166 42.808 351.89 143.638 20.652 302.336 32.11 469.194 32.11 166.854 0 325.552-11.458 469.186-32.11 27.532-107.724 42.812-226.75 42.812-351.89 0-125.142-15.28-244.166-42.812-351.892zM384.002 704v-384l320 192-320 192z" + ], + "tags": [ + "play", + "video", + "movie" + ], + "defaultCode": 59666, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "play, video", + "name": "play", + "order": 51, + "id": 19, + "prevSize": 16, + "code": 59666 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 18 + }, + { + "icon": { + "paths": [ + "M512 0c-176.732 0-320 143.268-320 320 0 320 320 704 320 704s320-384 320-704c0-176.732-143.27-320-320-320zM512 512c-106.040 0-192-85.96-192-192s85.96-192 192-192 192 85.96 192 192-85.96 192-192 192z" + ], + "tags": [ + "location", + "map-marker", + "pin" + ], + "defaultCode": 59719, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "location, map-marker", + "name": "location", + "order": 48, + "id": 72, + "prevSize": 16, + "code": 59719 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 71 + }, + { + "icon": { + "paths": [ + "M512 64c-141.384 0-269.376 57.32-362.032 149.978l-149.968-149.978v384h384l-143.532-143.522c69.496-69.492 165.492-112.478 271.532-112.478 212.068 0 384 171.924 384 384 0 114.696-50.292 217.636-130.018 288l84.666 96c106.302-93.816 173.352-231.076 173.352-384 0-282.77-229.23-512-512-512z" + ], + "tags": [ + "undo", + "ccw", + "arrow" + ], + "defaultCode": 59749, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "undo, ccw", + "name": "undo", + "order": 35, + "id": 102, + "prevSize": 16, + "code": 59749 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 101 + }, + { + "icon": { + "paths": [ + "M0 576c0 152.924 67.048 290.184 173.35 384l84.666-96c-79.726-70.364-130.016-173.304-130.016-288 0-212.076 171.93-384 384-384 106.042 0 202.038 42.986 271.53 112.478l-143.53 143.522h384v-384l-149.97 149.978c-92.654-92.658-220.644-149.978-362.030-149.978-282.77 0-512 229.23-512 512z" + ], + "tags": [ + "redo", + "cw", + "arrow" + ], + "defaultCode": 59750, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "redo, cw", + "name": "redo", + "order": 36, + "id": 103, + "prevSize": 16, + "code": 59750 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 102 + }, + { + "icon": { + "paths": [ + "M225 448c123.712 0 224 100.29 224 224 0 123.712-100.288 224-224 224s-224-100.288-224-224l-1-32c0-247.424 200.576-448 448-448v128c-85.474 0-165.834 33.286-226.274 93.726-11.634 11.636-22.252 24.016-31.83 37.020 11.438-1.8 23.16-2.746 35.104-2.746zM801 448c123.71 0 224 100.29 224 224 0 123.712-100.29 224-224 224s-224-100.288-224-224l-1-32c0-247.424 200.576-448 448-448v128c-85.474 0-165.834 33.286-226.274 93.726-11.636 11.636-22.254 24.016-31.832 37.020 11.44-1.8 23.16-2.746 35.106-2.746z" + ], + "tags": [ + "quotes-left", + "ldquo" + ], + "defaultCode": 59767, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "quotes-left, ldquo", + "name": "quotes-left", + "order": 34, + "id": 120, + "prevSize": 16, + "code": 59767 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 119 + }, + { + "icon": { + "paths": [ + "M384 832h640v128h-640zM384 448h640v128h-640zM384 64h640v128h-640zM192 0v256h-64v-192h-64v-64zM128 526v50h128v64h-192v-146l128-60v-50h-128v-64h192v146zM256 704v320h-192v-64h128v-64h-128v-64h128v-64h-128v-64z" + ], + "tags": [ + "list-numbered", + "options" + ], + "defaultCode": 59833, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "list-numbered, options", + "name": "list-numbered", + "order": 37, + "id": 186, + "prevSize": 16, + "code": 59833 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 185 + }, + { + "icon": { + "paths": [ + "M384 64h640v128h-640v-128zM384 448h640v128h-640v-128zM384 832h640v128h-640v-128zM0 128c0-70.692 57.308-128 128-128s128 57.308 128 128c0 70.692-57.308 128-128 128s-128-57.308-128-128zM0 512c0-70.692 57.308-128 128-128s128 57.308 128 128c0 70.692-57.308 128-128 128s-128-57.308-128-128zM0 896c0-70.692 57.308-128 128-128s128 57.308 128 128c0 70.692-57.308 128-128 128s-128-57.308-128-128z" + ], + "tags": [ + "list", + "todo", + "bullet", + "menu", + "options" + ], + "defaultCode": 59835, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "list2, todo2", + "name": "list2", + "order": 38, + "id": 188, + "prevSize": 16, + "code": 59835 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 187 + }, + { + "icon": { + "paths": [ + "M0 896h1024v64h-1024zM1024 768v64h-1024v-64l128-256h256v128h256v-128h256zM224 320l288-288 288 288h-224v256h-128v-256z" + ], + "tags": [ + "upload", + "load", + "open" + ], + "defaultCode": 59846, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "upload2, load2", + "name": "upload2", + "order": 52, + "id": 199, + "prevSize": 16, + "code": 59846 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 198 + }, + { + "icon": { + "paths": [ + "M440.236 635.766c-13.31 0-26.616-5.076-36.77-15.23-95.134-95.136-95.134-249.934 0-345.070l192-192c46.088-46.086 107.36-71.466 172.534-71.466s126.448 25.38 172.536 71.464c95.132 95.136 95.132 249.934 0 345.070l-87.766 87.766c-20.308 20.308-53.23 20.308-73.54 0-20.306-20.306-20.306-53.232 0-73.54l87.766-87.766c54.584-54.586 54.584-143.404 0-197.99-26.442-26.442-61.6-41.004-98.996-41.004s-72.552 14.562-98.996 41.006l-192 191.998c-54.586 54.586-54.586 143.406 0 197.992 20.308 20.306 20.306 53.232 0 73.54-10.15 10.152-23.462 15.23-36.768 15.23z", + "M256 1012c-65.176 0-126.45-25.38-172.534-71.464-95.134-95.136-95.134-249.934 0-345.070l87.764-87.764c20.308-20.306 53.234-20.306 73.54 0 20.308 20.306 20.308 53.232 0 73.54l-87.764 87.764c-54.586 54.586-54.586 143.406 0 197.992 26.44 26.44 61.598 41.002 98.994 41.002s72.552-14.562 98.998-41.006l192-191.998c54.584-54.586 54.584-143.406 0-197.992-20.308-20.308-20.306-53.232 0-73.54 20.306-20.306 53.232-20.306 73.54 0.002 95.132 95.134 95.132 249.932 0.002 345.068l-192.002 192c-46.090 46.088-107.364 71.466-172.538 71.466z" + ], + "tags": [ + "link", + "chain", + "url", + "uri", + "anchor" + ], + "defaultCode": 59851, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "link, chain", + "name": "link", + "order": 42, + "id": 204, + "prevSize": 16, + "code": 59851 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 203 + }, + { + "icon": { + "paths": [ + "M512 1024c282.77 0 512-229.23 512-512s-229.23-512-512-512-512 229.23-512 512 229.23 512 512 512zM512 96c229.75 0 416 186.25 416 416s-186.25 416-416 416-416-186.25-416-416 186.25-416 416-416zM512 598.76c115.95 0 226.23-30.806 320-84.92-14.574 178.438-153.128 318.16-320 318.16-166.868 0-305.422-139.872-320-318.304 93.77 54.112 204.050 85.064 320 85.064zM256 352c0-53.019 28.654-96 64-96s64 42.981 64 96c0 53.019-28.654 96-64 96s-64-42.981-64-96zM640 352c0-53.019 28.654-96 64-96s64 42.981 64 96c0 53.019-28.654 96-64 96s-64-42.981-64-96z" + ], + "tags": [ + "happy", + "emoticon", + "smiley", + "face" + ], + "defaultCode": 59871, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "happy, emoticon", + "name": "happy", + "order": 31, + "id": 224, + "prevSize": 16, + "code": 59871 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 223 + }, + { + "icon": { + "paths": [ + "M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416z", + "M672 256l-160 160-160-160-96 96 160 160-160 160 96 96 160-160 160 160 96-96-160-160 160-160z" + ], + "tags": [ + "cancel-circle", + "close", + "remove", + "delete" + ], + "defaultCode": 59917, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "cancel-circle, close", + "name": "cancel-circle", + "order": 59, + "id": 270, + "prevSize": 16, + "code": 59917 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 269 + }, + { + "icon": { + "paths": [ + "M707.88 484.652c37.498-44.542 60.12-102.008 60.12-164.652 0-141.16-114.842-256-256-256h-320v896h384c141.158 0 256-114.842 256-256 0-92.956-49.798-174.496-124.12-219.348zM384 192h101.5c55.968 0 101.5 57.42 101.5 128s-45.532 128-101.5 128h-101.5v-256zM543 832h-159v-256h159c58.45 0 106 57.42 106 128s-47.55 128-106 128z" + ], + "tags": [ + "bold", + "wysiwyg" + ], + "defaultCode": 60002, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "bold, wysiwyg4", + "name": "bold", + "order": 27, + "id": 355, + "prevSize": 16, + "code": 60002 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 354 + }, + { + "icon": { + "paths": [ + "M704 64h128v416c0 159.058-143.268 288-320 288-176.73 0-320-128.942-320-288v-416h128v416c0 40.166 18.238 78.704 51.354 108.506 36.896 33.204 86.846 51.494 140.646 51.494s103.75-18.29 140.646-51.494c33.116-29.802 51.354-68.34 51.354-108.506v-416zM192 832h640v128h-640z" + ], + "tags": [ + "underline", + "wysiwyg" + ], + "defaultCode": 60003, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "underline, wysiwyg5", + "name": "underline", + "order": 28, + "id": 356, + "prevSize": 16, + "code": 60003 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 355 + }, + { + "icon": { + "paths": [ + "M896 64v64h-128l-320 768h128v64h-448v-64h128l320-768h-128v-64z" + ], + "tags": [ + "italic", + "wysiwyg" + ], + "defaultCode": 60004, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "italic, wysiwyg6", + "name": "italic", + "order": 29, + "id": 357, + "prevSize": 16, + "code": 60004 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 356 + }, + { + "icon": { + "paths": [ + "M1024 512v64h-234.506c27.504 38.51 42.506 82.692 42.506 128 0 70.878-36.66 139.026-100.58 186.964-59.358 44.518-137.284 69.036-219.42 69.036-82.138 0-160.062-24.518-219.42-69.036-63.92-47.938-100.58-116.086-100.58-186.964h128c0 69.382 87.926 128 192 128s192-58.618 192-128c0-69.382-87.926-128-192-128h-512v-64h299.518c-2.338-1.654-4.656-3.324-6.938-5.036-63.92-47.94-100.58-116.086-100.58-186.964s36.66-139.024 100.58-186.964c59.358-44.518 137.282-69.036 219.42-69.036 82.136 0 160.062 24.518 219.42 69.036 63.92 47.94 100.58 116.086 100.58 186.964h-128c0-69.382-87.926-128-192-128s-192 58.618-192 128c0 69.382 87.926 128 192 128 78.978 0 154.054 22.678 212.482 64h299.518z" + ], + "tags": [ + "strikethrough", + "wysiwyg" + ], + "defaultCode": 60005, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "strikethrough, wysiwyg7", + "name": "strikethrough", + "order": 30, + "id": 358, + "prevSize": 16, + "code": 60005 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 357 + }, + { + "icon": { + "paths": [ + "M0 512h128v64h-128zM192 512h192v64h-192zM448 512h128v64h-128zM640 512h192v64h-192zM896 512h128v64h-128zM880 0l16 448h-768l16-448h32l16 384h640l16-384zM144 1024l-16-384h768l-16 384h-32l-16-320h-640l-16 320z" + ], + "tags": [ + "page-break", + "wysiwyg" + ], + "defaultCode": 60008, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "page-break, wysiwyg10", + "name": "page-break", + "order": 57, + "id": 361, + "prevSize": 16, + "code": 60008 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 360 + }, + { + "icon": { + "paths": [ + "M0 64v896h1024v-896h-1024zM384 640v-192h256v192h-256zM640 704v192h-256v-192h256zM640 192v192h-256v-192h256zM320 192v192h-256v-192h256zM64 448h256v192h-256v-192zM704 448h256v192h-256v-192zM704 384v-192h256v192h-256zM64 704h256v192h-256v-192zM704 896v-192h256v192h-256z" + ], + "tags": [ + "table", + "wysiwyg" + ], + "defaultCode": 60017, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "table2, wysiwyg19", + "name": "table2", + "order": 43, + "id": 370, + "prevSize": 16, + "code": 60017 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 369 + }, + { + "icon": { + "paths": [ + "M0 64h1024v128h-1024zM0 256h640v128h-640zM0 640h640v128h-640zM0 448h1024v128h-1024zM0 832h1024v128h-1024z" + ], + "tags": [ + "paragraph-left", + "wysiwyg", + "align-left", + "left" + ], + "defaultCode": 60023, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "paragraph-left, wysiwyg25", + "name": "paragraph-left", + "order": 39, + "id": 376, + "prevSize": 16, + "code": 60023 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 375 + }, + { + "icon": { + "paths": [ + "M0 64h1024v128h-1024zM192 256h640v128h-640zM192 640h640v128h-640zM0 448h1024v128h-1024zM0 832h1024v128h-1024z" + ], + "tags": [ + "paragraph-center", + "wysiwyg", + "align-center", + "center" + ], + "defaultCode": 60024, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "paragraph-center, wysiwyg26", + "name": "paragraph-center", + "order": 40, + "id": 377, + "prevSize": 16, + "code": 60024 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 376 + }, + { + "icon": { + "paths": [ + "M0 64h1024v128h-1024zM384 256h640v128h-640zM384 640h640v128h-640zM0 448h1024v128h-1024zM0 832h1024v128h-1024z" + ], + "tags": [ + "paragraph-right", + "wysiwyg", + "align-right", + "right" + ], + "defaultCode": 60025, + "grid": 16, + "attrs": [] + }, + "attrs": [], + "properties": { + "ligatures": "paragraph-right, wysiwyg27", + "name": "paragraph-right", + "order": 41, + "id": 378, + "prevSize": 16, + "code": 60025 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 377 + } + ], + "height": 1024, + "metadata": { + "name": "icomoon" + }, + "preferences": { + "showGlyphs": true, + "showQuickUse": true, + "showQuickUse2": true, + "showSVGs": true, + "fontPref": { + "prefix": "icon-", + "metadata": { + "fontFamily": "icomoon" + }, + "metrics": { + "emSize": 1024, + "baseline": 6.25, + "whitespace": 50 + }, + "embed": false + }, + "imagePref": { + "prefix": "icon-", + "png": true, + "useClassSelector": true, + "color": 0, + "bgColor": 16777215, + "classSelector": ".icon" + }, + "historySize": 100, + "showCodes": true, + "gridSize": 16 + } +} \ No newline at end of file diff --git a/example/icomoon/style.css b/example/icomoon/style.css new file mode 100644 index 000000000..e8caa2511 --- /dev/null +++ b/example/icomoon/style.css @@ -0,0 +1,113 @@ +@font-face { + font-family: 'icomoon'; + src: url('fonts/icomoon.eot?b1ngen'); + src: url('fonts/icomoon.eot?b1ngen#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?b1ngen') format('truetype'), + url('fonts/icomoon.woff?b1ngen') format('woff'), + url('fonts/icomoon.svg?b1ngen#icomoon') format('svg'); + font-weight: normal; + font-style: normal; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-close:before { + content: "\f00d"; +} +.icon-remove:before { + content: "\f00d"; +} +.icon-times:before { + content: "\f00d"; +} +.icon-trash-o:before { + content: "\f014"; +} +.icon-terminal:before { + content: "\f120"; +} +.icon-header:before { + content: "\f1dc"; +} +.icon-paint-brush:before { + content: "\f1fc"; +} +.icon-pencil2:before { + content: "\e906"; +} +.icon-image:before { + content: "\e90d"; +} +.icon-play:before { + content: "\e912"; +} +.icon-location:before { + content: "\e947"; +} +.icon-undo:before { + content: "\e965"; +} +.icon-redo:before { + content: "\e966"; +} +.icon-quotes-left:before { + content: "\e977"; +} +.icon-list-numbered:before { + content: "\e9b9"; +} +.icon-list2:before { + content: "\e9bb"; +} +.icon-upload2:before { + content: "\e9c6"; +} +.icon-link:before { + content: "\e9cb"; +} +.icon-happy:before { + content: "\e9df"; +} +.icon-cancel-circle:before { + content: "\ea0d"; +} +.icon-bold:before { + content: "\ea62"; +} +.icon-underline:before { + content: "\ea63"; +} +.icon-italic:before { + content: "\ea64"; +} +.icon-strikethrough:before { + content: "\ea65"; +} +.icon-page-break:before { + content: "\ea68"; +} +.icon-table2:before { + content: "\ea71"; +} +.icon-paragraph-left:before { + content: "\ea77"; +} +.icon-paragraph-center:before { + content: "\ea78"; +} +.icon-paragraph-right:before { + content: "\ea79"; +} diff --git a/example/index.html b/example/index.html new file mode 100644 index 000000000..703f53ef5 --- /dev/null +++ b/example/index.html @@ -0,0 +1,52 @@ + + + + + wangEditor demo list + + + +
+

可访问 wangEditor 官网 了解更多内容

+
+

欢迎使用 wangEditor 富文本编辑器

+

欢迎使用 wangEditor 富文本编辑器

+

欢迎使用 wangEditor 富文本编辑器

+
+ +

wangEditor demo list(demo页面直接查看网页源代码即可)

+ + +

其他链接

+ +
+ + + + + + \ No newline at end of file diff --git a/example/server/index.js b/example/server/index.js new file mode 100644 index 000000000..28d8a6026 --- /dev/null +++ b/example/server/index.js @@ -0,0 +1,88 @@ +const fs = require('fs') +const path = require('path') +const formidable = require('formidable') +const util = require('./util.js') + +const koa = require('koa') +const app = koa() + +// 捕获错误 +const onerror = require('koa-onerror') +onerror(app) + +// post body 解析 +const bodyParser = require('koa-bodyparser') +app.use(bodyParser()) + +// 静态文件服务,针对 html js css fonts 文件 +const staticCache = require('koa-static-cache') +function setStaticCache() { + const exampleDir = path.join(__dirname, '..', '..', 'example') + const releaseDir = path.join(__dirname, '..', '..', 'release') + app.use(staticCache(exampleDir)) + app.use(staticCache(releaseDir)) +} +setStaticCache() + +// 配置路由 +const router = require('koa-router')() + +// 保存上传的文件 +function saveFiles(req) { + return new Promise((resolve, reject) => { + const imgLinks = [] + const form = new formidable.IncomingForm() + form.parse(req, function (err, fields, files) { + if (err) { + reject('formidable, form.parse err', err.stack) + } + // 存储图片的文件夹 + const storePath = path.resolve(__dirname, '..', 'upload-files') + if (!fs.existsSync(storePath)) { + fs.mkdirSync(storePath) + } + + // 遍历所有上传来的图片 + util.objForEach(files, (name, file) => { + // 图片临时位置 + const tempFilePath = file.path + // 图片名称和路径 + const fileName = file.name + const fullFileName = path.join(storePath, fileName) + // 将临时文件保存为正式文件 + fs.renameSync(tempFilePath, fullFileName) + // 存储链接 + imgLinks.push('/upload-files/' + fileName) + }) + + // 重新设置静态文件缓存 + setStaticCache() + + // 返回结果 + resolve({ + errno: 0, + data: imgLinks + }) + }) + }) +} + +// 上传图片 +router.post('/upload-img', function* () { + const ctx = this + const req = ctx.req + const res = ctx.res + + // 获取数据 + const data = yield saveFiles(req) + + // 返回结果 + this.body = JSON.stringify(data) +}) +app.use(router.routes()).use(router.allowedMethods()); + +// 启动服务 +app.listen(3000) +console.log('listening on port %s', 3000) + +module.exports = app \ No newline at end of file diff --git a/example/server/util.js b/example/server/util.js new file mode 100644 index 000000000..62477f2e0 --- /dev/null +++ b/example/server/util.js @@ -0,0 +1,14 @@ +module.exports = { + // 遍历对象 + objForEach: function (obj, fn) { + let key, result + for (key in obj) { + if (obj.hasOwnProperty(key)) { + result = fn.call(obj, key, obj[key]) + if (result === false) { + break + } + } + } + } +} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 000000000..c988b7b40 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,122 @@ +const path = require('path') +const fs = require('fs') +const gulp = require('gulp') +const rollup = require('rollup') +const uglify = require('gulp-uglify') +const sourcemaps = require('gulp-sourcemaps') +const rename = require('gulp-rename') +const less = require('gulp-less') +const concat = require('gulp-concat') +const cssmin = require('gulp-cssmin') +const eslint = require('rollup-plugin-eslint') +const postcss = require('gulp-postcss') +const autoprefixer = require('autoprefixer') +const cssgrace = require('cssgrace') +const resolve = require('rollup-plugin-node-resolve') +const babel = require('rollup-plugin-babel') +const gulpReplace = require('gulp-replace') + +// 拷贝 fonts 文件 +gulp.task('copy-fonts', () => { + gulp.src('./src/fonts/*') + .pipe(gulp.dest('./release/fonts')) +}) + +// 处理 css +gulp.task('css', () => { + gulp.src('./src/less/**/*.less') + .pipe(less()) + // 产出的未压缩的文件名 + .pipe(concat('wangEditor.css')) + // 配置 postcss + .pipe(postcss([ + autoprefixer, + cssgrace + ])) + // 将 css 引用的字体文件转换为 base64 格式 + .pipe(gulpReplace( /'fonts\/icomoon\..+?'/gm, function (fontFile) { + // fontFile 例如 'fonts/icomoon.eot?paxlku' + fontFile = fontFile.slice(0, -1).slice(1) + fontFile = fontFile.split('?')[0] + var ext = fontFile.split('.')[1] + // 读取文件内容,转换为 base64 格式 + var filePath = path.resolve(__dirname, 'release', fontFile) + var content = fs.readFileSync(filePath) + var base64 = content.toString('base64') + // 返回 + return 'data:application/x-font-' + ext + ';charset=utf-8;base64,' + base64 + })) + // 产出文件的位置 + .pipe(gulp.dest('./release')) + // 产出的压缩后的文件名 + .pipe(rename('wangEditor.min.css')) + .pipe(cssmin()) + .pipe(gulp.dest('./release')) +}) + +// 处理 JS +gulp.task('script', () => { + // rollup 打包 js 模块 + return rollup.rollup({ + // 入口文件 + entry: './src/js/index.js', + plugins: [ + // 对原始文件启动 eslint 检查,配置参见 ./.eslintrc.json + eslint(), + resolve(), + babel({ + exclude: 'node_modules/**' // only transpile our source code + }) + ] + }).then(bundle => { + bundle.write({ + // 产出文件使用 umd 规范(即兼容 amd cjs 和 iife) + format: 'umd', + // iife 规范下的全局变量名称 + moduleName: 'wangEditor', + // 产出的未压缩的文件名 + dest: './release/wangEditor.js' + }).then(() => { + // 待 rollup 打包 js 完毕之后,再进行如下的处理: + gulp.src('./release/wangEditor.js') + // inline css + .pipe(gulpReplace(/__INLINE_CSS__/gm, function () { + // 读取 css 文件内容 + var filePath = path.resolve(__dirname, 'release', 'wangEditor.css') + var content = fs.readFileSync(filePath).toString('utf-8') + // 替换 \n \ ' 三个字符 + content = content.replace(/\n/g, '').replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + return content + })) + .pipe(gulp.dest('./release')) + .pipe(sourcemaps.init()) + // 压缩 + .pipe(uglify()) + // 产出的压缩的文件名 + .pipe(rename('wangEditor.min.js')) + // 生成 sourcemap + .pipe(sourcemaps.write('')) + .pipe(gulp.dest('./release')) + }) + }) +}) + + +// 默认任务配置 +gulp.task('default', () => { + gulp.run('copy-fonts', 'css', 'script') + + // 监听 js 原始文件的变化 + gulp.watch('./src/js/**/*.js', () => { + gulp.run('script') + }) + // 监听 css 原始文件的变化 + gulp.watch('./src/less/**/*.less', () => { + gulp.run('css', 'script') + }) + // 监听 icon.less 的变化,变化时重新拷贝 fonts 文件 + gulp.watch('./src/less/icon.less', () => { + gulp.run('copy-fonts') + }) +}) + diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 6a301f04f..000000000 --- a/jest.config.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - roots: ['/packages'], - testEnvironment: 'jsdom', - testMatch: ['**/(*.)+(spec|test).+(ts|js|tsx)'], - transform: { - '^.+\\.tsx?$': 'ts-jest', - '^.+\\.js$': 'ts-jest', - }, - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.json', - }, - }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - moduleNameMapper: { - '^.+\\.(css|less)$': '/tests/utils/stylesMock.js', - }, - transformIgnorePatterns: ['node_modules/(?!(html-void-elements)/)'], - setupFilesAfterEnv: ['/tests/setup/index.ts'], - collectCoverageFrom: ['/packages/**/src/**/*.(ts|tsx)'], - coveragePathIgnorePatterns: [ - 'dist', - 'locale', - 'index.ts', - 'config.ts', - 'browser-polyfill.ts', - 'node-polyfill.ts', - ], -} diff --git a/lerna.json b/lerna.json deleted file mode 100644 index aac950166..000000000 --- a/lerna.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "packages": [ - "packages/*" - ], - "version": "independent", - "npmClient": "yarn", - "useWorkspaces": true, - "command": { - "publish": { - "ignoreChanges": ["ignored-file", "*.md"], - "message": "chore(release): publish", - "conventionalCommits": true, - "registry": "https://npm.pkg.github.com" - }, - "version": { - "message": "chore(release): publish", - "allowBranch": "master" - } - }, - "changelog": { - "repo": "wangeditor-team/wangEditor", - "labels": { - "tag: new feature": ":rocket: New Feature", - "tag: breaking change": ":boom: Breaking Change", - "tag: bug fix": ":bug: Bug Fix", - "tag: enhancement": ":nail_care: Enhancement", - "tag: documentation": ":memo: Documentation", - "tag: internal": ":house: Internal" - }, - "cacheDir": ".changelog" - }, - "changelogPreset": "angular" -} diff --git a/package.json b/package.json index 98c80c838..20a659ef6 100644 --- a/package.json +++ b/package.json @@ -1,110 +1,60 @@ { - "name": "@wangeditor-team/wangeditor", - "private": true, - "scripts": { - "test": "cross-env NODE_OPTIONS=--unhandled-rejections=warn jest --detectOpenHandles --passWithNoTests", - "test-c": "cross-env NODE_OPTIONS=--unhandled-rejections=warn jest --coverage", - "dev": "sh build/build-all.sh dev", - "build": "sh build/build-all.sh", - "bootstrap": "lerna bootstrap --use-workspaces", - "release:version": "git pull origin master && lerna version --conventional-commits && node ./scripts/release-tag.js", - "release:publish:experimental": "lerna publish --dist-tag experimental", - "release:publish:canary": "lerna publish --canary", - "release:next": "yarn prerelease && lerna publish --dist-tag next", - "release:publish": "lerna publish from-git --yes", - "release:package": "lerna publish from-package --yes", - "prerelease": "yarn build", - "format": "yarn prettier --write", - "lint": "eslint \"packages/*/+(src|__tests__)/**/*.+(ts|tsx)\"", - "prettier": "prettier --ignore-path .gitignore \"packages/*/+(src|__tests__)/**/*.+(ts|tsx)\"", - "cypress:open": "cypress open", - "cypress:run": "cypress run", - "e2e:dev": "concurrently \"yarn example\" \"yarn run cypress:open\"", - "e2e": "concurrently \"yarn example\" \"yarn run cypress:run\"", - "example": "lerna exec --scope @wangeditor/editor -- yarn run example" + "name": "wangeditor", + "title": "wangEditor", + "version": "3.0.2", + "description": "wangEditor - 基于javascript和css开发的 web 富文本编辑器, 轻量、简洁、易用、开源免费", + "homepage": "http://wangeditor.github.io/", + "author": { + "name": "wangfupeng1988", + "url": "https://github.com/wangfupeng1988" }, - "workspaces": [ - "packages/*" + "keywords": [ + "wangEditor", + "web 富文本编辑器" ], - "lint-staged": { - "packages/**/*.{ts,tsx}": [ - "yarn lint", - "yarn format", - "git add ." - ] - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged", - "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" + "main": "release/wangEditor.js", + "maintainers": [ + { + "name": "wangfupeng1988", + "web": "http://www.cnblogs.com/wangfupeng1988/default.html?OnlyTitle=1", + "mail": "wangfupeng1988@163.com" } - }, - "config": { - "commitizen": { - "path": "node_modules/cz-customizable" + ], + "repositories": [ + { + "type": "git", + "url": "https://github.com/wangfupeng1988/wangEditor" } + ], + "scripts": { + "release": "gulp", + "win-example": "node ./example/server/index.js", + "example": "/bin/rm -rf ./example/upload-files && mkdir ./example/upload-files && npm run win-example" }, "devDependencies": { - "@babel/core": "^7.14.6", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.11.0", - "@babel/plugin-transform-runtime": "^7.14.5", - "@babel/preset-env": "^7.14.5", - "@babel/preset-typescript": "^7.14.5", - "@babel/runtime-corejs3": "^7.14.7", - "@rollup/plugin-babel": "^5.3.0", - "@rollup/plugin-commonjs": "^17.1.0", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^11.2.0", - "@rollup/plugin-replace": "^2.4.2", - "@testing-library/jest-dom": "^5.14.1", - "@types/jest": "^25.2.1", - "@typescript-eslint/eslint-plugin": "^2.31.0", - "@typescript-eslint/parser": "^4.4.1", - "autoprefixer": "^10.2.5", - "babel-core": "^7.0.0-bridge.0", - "babel-jest": "^27.0.6", - "babel-plugin-istanbul": "^6.0.0", - "commitlint": "^11.0.0", - "commitlint-config-cz": "^0.13.2", - "concurrently": "^6.2.0", - "conventional-changelog": "^3.1.24", - "cross-env": "^7.0.2", - "cssnano": "^5.0.3", - "cypress": "^8.6.0", - "cz-customizable": "^6.3.0", - "eslint": "^7.21.0", - "eslint-config-prettier": "^6.11.0", - "eslint-plugin-prettier": "^3.1.3", - "http-server": "^0.12.3", - "husky": "^4.2.5", - "jest": "^27.0.6", - "lerna": "^3.20.2", - "lerna-changelog": "^1.0.1", - "less": "^3.11.1", - "lint-staged": "^10.2.2", - "lodash": "^4.17.21", - "nock": "^13.2.4", - "nodemon": "^2.0.6", - "postcss": "^8.2.15", - "prettier": "^2.0.5", - "release-it": "^14.2.0", - "rollup": "^2.41.0", - "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-copy": "^3.4.0", - "rollup-plugin-delete": "^2.0.0", - "rollup-plugin-generate-html-template": "^1.7.0", - "rollup-plugin-peer-deps-external": "^2.2.4", - "rollup-plugin-postcss": "^4.0.0", - "rollup-plugin-serve": "^1.1.0", - "rollup-plugin-terser": "^7.0.2", - "rollup-plugin-typescript2": "^0.30.0", - "rollup-plugin-visualizer": "^5.5.0", - "ts-jest": "^27.0.4", - "tslib": "^2.3.0", - "typescript": "4.3.2" + "autoprefixer": "^6.7.7", + "babel-plugin-external-helpers": "^6.22.0", + "babel-preset-latest": "^6.24.0", + "cssgrace": "^3.0.0", + "formidable": "^1.1.1", + "gulp": "^3.9.1", + "gulp-concat": "^2.6.1", + "gulp-cssmin": "^0.1.7", + "gulp-less": "^3.3.0", + "gulp-postcss": "^6.4.0", + "gulp-rename": "^1.2.2", + "gulp-replace": "^0.5.4", + "gulp-sourcemaps": "^2.5.0", + "gulp-uglify": "^2.1.2", + "koa": "^1.2.4", + "koa-bodyparser": "^2.3.0", + "koa-onerror": "^3.1.0", + "koa-router": "^5.4.0", + "koa-static-cache": "^4.0.0", + "rollup": "^0.41.6", + "rollup-plugin-babel": "^2.7.1", + "rollup-plugin-eslint": "^3.0.0", + "rollup-plugin-node-resolve": "^3.0.0" }, - "dependencies": { - "@babel/runtime": "^7.14.6" - } + "dependencies": {} } diff --git a/packages/basic-modules/CHANGELOG.md b/packages/basic-modules/CHANGELOG.md deleted file mode 100644 index cd1535977..000000000 --- a/packages/basic-modules/CHANGELOG.md +++ /dev/null @@ -1,192 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.1.7](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.1.6...@wangeditor/basic-modules@1.1.7) (2022-11-14) - - -### Bug Fixes - -* **font family menu:** 处理 setHtml 的时候字体样式回显失败的问题 ([b941bab](https://github.com/wangeditor-team/wangEditor/commit/b941babbdc6bd5bf7da0cce826803a8fde011e07)) - - - - - -## [1.1.6](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.1.5...@wangeditor/basic-modules@1.1.6) (2022-09-27) - -**Note:** Version bump only for package @wangeditor/basic-modules - - - - - -## [1.1.5](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.1.4...@wangeditor/basic-modules@1.1.5) (2022-09-15) - - -### Bug Fixes - -* 图片 100% 有横向滚动条 ([d21322a](https://github.com/wangeditor-team/wangEditor/commit/d21322a1a9f2e3172a1bd5e175f5ebbb5f2ed074)) -* 插入表格会删掉去掉 issue 4711 ([d4fac4e](https://github.com/wangeditor-team/wangEditor/commit/d4fac4efd06480457a95c2b06e7472cf6204de58)) - - - - - -## [1.1.4](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.1.3...@wangeditor/basic-modules@1.1.4) (2022-09-14) - - -### Bug Fixes - -* font-size - 支持配置 name value ([206ebd9](https://github.com/wangeditor-team/wangEditor/commit/206ebd994d2635704d93ef9ebe0022d7d72ddea8)) - - - - - -## [1.1.3](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.1.2...@wangeditor/basic-modules@1.1.3) (2022-07-13) - - -### Bug Fixes - -* parse indent style 负数 ([c8d746e](https://github.com/wangeditor-team/wangEditor/commit/c8d746e0464bdda626313c17af4d015681ccc3e8)) -* 兼容 word 文字背景色 ([e820b26](https://github.com/wangeditor-team/wangEditor/commit/e820b26730d34480994a343ab262c043c30a4495)) - - - - - -## [1.1.2](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.1.1...@wangeditor/basic-modules@1.1.2) (2022-07-11) - - -### Bug Fixes - -* editor.focus() 参数语法错误 ([334fa21](https://github.com/wangeditor-team/wangEditor/commit/334fa217d43fdaa95454e7c85a53526b7b777fda)) -* 修复html回显时,部分字体回显问题 ([c83ffa7](https://github.com/wangeditor-team/wangEditor/commit/c83ffa70da655d03bfd639f2d1fd04986440ead2)) - - - - - -## [1.1.1](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.1.0...@wangeditor/basic-modules@1.1.1) (2022-06-02) - - -### Bug Fixes - -* issue 4308 - 自定义字号、字体无法回显 ([ad38b8c](https://github.com/wangeditor-team/wangEditor/commit/ad38b8ce6dbcff1d65785c8d6701238ad351f562)) -* 修复在空字符上插入 link 报错的问题 ([e838f06](https://github.com/wangeditor-team/wangEditor/commit/e838f069f556a5d3206e48a5ed76f8d1e0ae3d05)) - - - - - -# [1.1.0](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/basic-modules@1.0.1...@wangeditor/basic-modules@1.1.0) (2022-05-25) - - -### Bug Fixes - -* 粘贴 HTML 后 font-size font-family line-height 不显示 ([2281957](https://github.com/wangeditor-team/wangEditor/commit/2281957020a30de9cda1c5e9d5e20c6668b7f592)) - - -### Features - -* enter menu ([988fc31](https://github.com/wangeditor-team/wangEditor/commit/988fc31f31de3d37dffbf54abb784cceb8e6118d)) - - - - - -## 1.0.1 (2022-04-18) - - -### Bug Fixes - -* 部分菜单 disabled ([87f1233](https://github.com/wangeditor-team/wangEditor/commit/87f12332a087072406c1988dc5cef2eae8335375)) -* 插入图片的 < > 替换 ([5721560](https://github.com/wangeditor-team/wangEditor/commit/57215609ada8b9d15f5505d1ba52e49707b5b183)) -* 错别字 alwaysEnable ([82c5136](https://github.com/wangeditor-team/wangEditor/commit/82c5136f8496be420dfa26b0f30522e19924a907)) -* 分割线后无法输入内容 ([146fd05](https://github.com/wangeditor-team/wangEditor/commit/146fd05108592d50d036d0f37a2e29fcdd2a97be)) -* 更新各包之间依赖版本 ([75c552c](https://github.com/wangeditor-team/wangEditor/commit/75c552cc8ed54765bebb86a7ec5329a7fc79e85f)) -* 更新了 basic-module 依赖版本 ([20c9543](https://github.com/wangeditor-team/wangEditor/commit/20c9543dc9249af6fc7e3a9895ed7f64709ca6ee)) -* 禁用时 image 的样式 ([42c993a](https://github.com/wangeditor-team/wangEditor/commit/42c993a7668d90ce049b88a01df21b28912c679f)) -* 全选设置字体报错 ([cdb14d1](https://github.com/wangeditor-team/wangEditor/commit/cdb14d10330b5736534e7aaf3a070df2804a8be2)) -* 上下标文案显示 ([0e97da1](https://github.com/wangeditor-team/wangEditor/commit/0e97da18279cee6ea06c217972fee4faf9e4758f)) -* 添加链接,空格也会在链接中的问题 ([c656827](https://github.com/wangeditor-team/wangEditor/commit/c65682743bd49eba9ab64be847f1f9527fb6170b)) -* 修复 pnpm 安装 @wangeditor/editor 出现警告的问题 ([4087fbe](https://github.com/wangeditor-team/wangEditor/commit/4087fbee01c76bdd55e747a5e86c5e4a8d6a8353)) -* 修复多选文字且选择空白行无法修改文字样式 ([99a9150](https://github.com/wangeditor-team/wangEditor/commit/99a91509c6e12220bb105cc6d15a0f0a4b375cea)) -* 修复光标状态下设置文字样式,菜单不 active 的问题 ([b1b2dba](https://github.com/wangeditor-team/wangEditor/commit/b1b2dbaaae11f74bd36ec79ff50de336c252fef5)) -* 修复清除格式不完全的问题 ([1181a23](https://github.com/wangeditor-team/wangEditor/commit/1181a23e6de71162dc490d9b348379c9b2ef4251)) -* 修复设置文字颜色与背景色行为与预期不一致的问题 ([25d3381](https://github.com/wangeditor-team/wangEditor/commit/25d3381aa65ce8fe862617e7b1b03cfa5370715d)) -* 选中文字,创建链接(同时修改文字) ([5fdf6ae](https://github.com/wangeditor-team/wangEditor/commit/5fdf6ae33b1bebe9b7373e4b7ee8c568480a3c08)) -* 移除了每个包下的 publishConfig directory 配置 ([16559f0](https://github.com/wangeditor-team/wangEditor/commit/16559f052545c111318be760e64291a521bdcc65)) -* 优化 custom-types.d.ts 中类型声明,修复测试文件 ts 报错 ([3a6c455](https://github.com/wangeditor-team/wangEditor/commit/3a6c4553245bc734dae1e17d605af389971782a2)) -* 有内联元素时对齐失败 ([076c694](https://github.com/wangeditor-team/wangEditor/commit/076c694a4b3474080b89f52692595b84bf4d8207)) -* blockquote & header insertBreak ([06678c9](https://github.com/wangeditor-team/wangEditor/commit/06678c963e8c8421ecded448de7510b254117550)) -* code node 顶层 ([a927938](https://github.com/wangeditor-team/wangEditor/commit/a9279388f14212319505f6a5da300cd15e81c214)) -* code-block 换行 - 自动加入前面的空格 ([c214032](https://github.com/wangeditor-team/wangEditor/commit/c2140327842d803cd18a9acf47ec3225182bf940)) -* color toHtml ([2c9718c](https://github.com/wangeditor-team/wangEditor/commit/2c9718cb2feb4dd0a7bf39238598707fa6d2bb21)) -* delete divider ([f04cbd6](https://github.com/wangeditor-team/wangEditor/commit/f04cbd6009099629e3cd41be19d20b6788fe7f28)) -* disabled - img 和 todo 可编辑 ([cf6a3f2](https://github.com/wangeditor-team/wangEditor/commit/cf6a3f2a1e05b6231f46aa74c422561e4147f7ae)) -* divider - 键盘删除 ([31db059](https://github.com/wangeditor-team/wangEditor/commit/31db0593dbc77fba9b4a719bc0f48f1223afd680)) -* emptyP toHtml 增加 br ([c347c29](https://github.com/wangeditor-team/wangEditor/commit/c347c2916416edc96a99d1bf53c0e18cd22d80f9)) -* getHtml API ([c0b60cf](https://github.com/wangeditor-team/wangEditor/commit/c0b60cf47d8eaae4292265906fbe07875e1564c9)) -* header 不禁用 bold ([f4cd3d0](https://github.com/wangeditor-team/wangEditor/commit/f4cd3d0b85725701c3ec650e4d6ae7d8831f5105)) -* hoverbar 被点击多次隐藏 ([bf4fc19](https://github.com/wangeditor-team/wangEditor/commit/bf4fc193847e8caba3a67c8dd152eae4f1950c4f)) -* hoverbar modal 重复创建 ([70d2b61](https://github.com/wangeditor-team/wangEditor/commit/70d2b618a0662c88cd5e6691f513009726ce1b9b)) -* hoverbar show/hide ([c96bc83](https://github.com/wangeditor-team/wangEditor/commit/c96bc8378939fecd78807fea4f2b7e1eec2a9ea0)) -* image 拖拽,设置最小值 ([0205023](https://github.com/wangeditor-team/wangEditor/commit/0205023d8c1ec3fafcba3a950afcaef9f5f5170f)) -* insert link ([a104682](https://github.com/wangeditor-team/wangEditor/commit/a10468279f730c9a4216474cf3d44d41f124cb6b)) -* insertHtml - 空行 ([53a8fbb](https://github.com/wangeditor-team/wangEditor/commit/53a8fbb5cf665ef0d6f7fd1c2fee73dba0d98e32)) -* insertHtml - 空行 ([c61f415](https://github.com/wangeditor-team/wangEditor/commit/c61f415c41d393f203ae5e5c17d9167ec60a1824)) -* justify - disable ([3a4b24e](https://github.com/wangeditor-team/wangEditor/commit/3a4b24e8e628024de248f0b52bb4066f626e7480)) -* justify indent ([5a81e52](https://github.com/wangeditor-team/wangEditor/commit/5a81e527a45e7a92eb36a2aefa50d93e20c4cec2)) -* justify menu disabled ([19e2f80](https://github.com/wangeditor-team/wangEditor/commit/19e2f8008a435101c6ecd4d4a7eadd423cb1070f)) -* link 无文本 ([af4fb32](https://github.com/wangeditor-team/wangEditor/commit/af4fb3218bd4651763f66c804fec2b872e99e8f3)) -* link, text hoverbar 选区问题 ([e0b7438](https://github.com/wangeditor-team/wangEditor/commit/e0b7438c89a347f1b0b940d9c11150b72d595529)) -* lodash.throttle 引用 bug ([50aeff9](https://github.com/wangeditor-team/wangEditor/commit/50aeff94859bf328346cb9cfe89d0abd57c3b641)) -* maxLength - 拼音 + 粘贴 ([3ac4db6](https://github.com/wangeditor-team/wangEditor/commit/3ac4db6d78cbe7a8d1fe19747deb0a17edd9b552)) -* menu active ([10829e2](https://github.com/wangeditor-team/wangEditor/commit/10829e2e9e1d864d4900821ee3d5fa516b8cca2a)) -* parse html - 有些 elem children 需要过滤 ([63cbb80](https://github.com/wangeditor-team/wangEditor/commit/63cbb804c8c7a778a4ee1f4ba8717a11b4b6b5a3)) -* parse-html - sub sup ([2c15a5f](https://github.com/wangeditor-team/wangEditor/commit/2c15a5f9c9c2de8b34770a6bebfe765d203a03f6)) -* parse-html pre/code ([d9bd773](https://github.com/wangeditor-team/wangEditor/commit/d9bd773f9a40f9531d9163700253d0b5f717afb8)) -* rename es module filename ([1821d4e](https://github.com/wangeditor-team/wangEditor/commit/1821d4eef49e64efcb41b848849ca7a5e6472044)) -* shadow dom 中 modal 输入框异常 ([ef3b199](https://github.com/wangeditor-team/wangEditor/commit/ef3b199a3e74c6b8ba61ed781e1aa13a1c5acfde)) -* style-to-html - 输入 a 会删除外部的 标签 ([af1f523](https://github.com/wangeditor-team/wangEditor/commit/af1f523983f2bc4b7eaf9726d4b8a35227ab27dc)) -* table - elemToHtml ([e36e609](https://github.com/wangeditor-team/wangEditor/commit/e36e6092ef721723169afc8bf0560a47ac9f4dfc)) -* tableCell 中 br 报错 ([8604db7](https://github.com/wangeditor-team/wangEditor/commit/8604db751b622c01fa5391af59328236cf13effc)) - - -### Features - -* 两端对齐 ([e5080d3](https://github.com/wangeditor-team/wangEditor/commit/e5080d3dd102f7a951d8e1f370db834778ecbdfa)) -* 上标 下标 ([40dab08](https://github.com/wangeditor-team/wangEditor/commit/40dab085a061ea3e838f0cfa86260c6c6f894c69)) -* 增加 enable disable API(删除 setConfig setMenuConfig API) ([984fc50](https://github.com/wangeditor-team/wangEditor/commit/984fc50520061fc34ea08f4136bdeb93dee46564)) -* 增加 header4 header5 ([cc48734](https://github.com/wangeditor-team/wangEditor/commit/cc4873412ce3f4de1ecc1dbf4c313094dceb5a77)) -* 支持 nodejs 环境 ([484f18c](https://github.com/wangeditor-team/wangEditor/commit/484f18c3abc70d19e51c556f48491c18d390b1e1)) -* basic text paste ([f0a5b98](https://github.com/wangeditor-team/wangEditor/commit/f0a5b980c95fa1e2fc59a898c6e0d0723c276c28)) -* clearStyle menu ([8002f70](https://github.com/wangeditor-team/wangEditor/commit/8002f707ed04b914180ec36fdca0edf48c815e01)) -* drag resize image ([cd72028](https://github.com/wangeditor-team/wangEditor/commit/cd72028f1786e2e53079ad5cbef1b8569731ca79)) -* editor 生命周期,自定义事件 ([00e9bc2](https://github.com/wangeditor-team/wangEditor/commit/00e9bc2cfcb8b622764db1c76394491d72ffd93e)) -* focus支持focus到文档末尾 ([628830e](https://github.com/wangeditor-team/wangEditor/commit/628830ef06ff85b3e67001ce30dd9e0557b0aa28)) -* fullScreen ([e7ccd88](https://github.com/wangeditor-team/wangEditor/commit/e7ccd88a7dd58f64b7bd484de428e3a76cc994f7)) -* getHeaders & editor.srcollToElem ([2bfb813](https://github.com/wangeditor-team/wangEditor/commit/2bfb813e4957f080c6676ec38f8f051275cdf44a)) -* groupButton disabled ([8ffd44c](https://github.com/wangeditor-team/wangEditor/commit/8ffd44c9a44758e951ca7bd02dd46746fcac1c03)) -* header button menu ([6413135](https://github.com/wangeditor-team/wangEditor/commit/64131354d54705e11fd6992fcf5a4389371c3560)) -* i18n ([c11b244](https://github.com/wangeditor-team/wangEditor/commit/c11b2440f91b99d40bca18b675c66a22b6e160c9)) -* image menu - width 50% 100% ([f9b4c68](https://github.com/wangeditor-team/wangEditor/commit/f9b4c68dff3232b50491b07949c20eb4c18baa6b)) -* image menu config ([bb18774](https://github.com/wangeditor-team/wangEditor/commit/bb187740e9703b4a76cde4f5e4d32ac714aa793a)) -* insertHtml - text style ([6f303c5](https://github.com/wangeditor-team/wangEditor/commit/6f303c5be81dc8763a28bc982928e5bc9f2936d9)) -* link menu config ([fe6b6db](https://github.com/wangeditor-team/wangEditor/commit/fe6b6db62086a5014c25ea96aa9308c2028a5b60)) -* parse html ([2a5eace](https://github.com/wangeditor-team/wangEditor/commit/2a5eace00f33cded50b68e8164748ec2480213fd)) -* parse src (link image video) ([715a841](https://github.com/wangeditor-team/wangEditor/commit/715a841fc6c730ee2b448a1799a07ce778128aad)) -* placeholder ([a3e4cdc](https://github.com/wangeditor-team/wangEditor/commit/a3e4cdcd474063e4f436327aaf4074bb2126d941)) -* react 组件 ([448fc83](https://github.com/wangeditor-team/wangEditor/commit/448fc838d64dbef52cbcddde0e98eb021d8a9122)) -* todo ([9608fef](https://github.com/wangeditor-team/wangEditor/commit/9608fef2ff86368cdcbb950a74af1246a58709de)) -* toHtml 机制 ([1c4d872](https://github.com/wangeditor-team/wangEditor/commit/1c4d8729f84aaab6a448f23064b34a20596305e9)) -* upload video ([ac8e6f8](https://github.com/wangeditor-team/wangEditor/commit/ac8e6f8b5258e593714676a6f6be359ba525833c)) -* video menu config ([7fa3783](https://github.com/wangeditor-team/wangEditor/commit/7fa3783c42aa83f7d53c8be34be3c8b7c8a64754)) - - -### Performance Improvements - -* 优化较大的svg图片 ([2c360e7](https://github.com/wangeditor-team/wangEditor/commit/2c360e7628eb655e8df67cc7b764f4981b283a2f)) diff --git a/packages/basic-modules/README.md b/packages/basic-modules/README.md deleted file mode 100644 index 25062ec51..000000000 --- a/packages/basic-modules/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# wangEditor basic-modules - -Basic modules built in [wangEditor](https://www.wangeditor.com/) by default. diff --git a/packages/basic-modules/__tests__/blockquote/blockquote-menu.test.ts b/packages/basic-modules/__tests__/blockquote/blockquote-menu.test.ts deleted file mode 100644 index 7b894127c..000000000 --- a/packages/basic-modules/__tests__/blockquote/blockquote-menu.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @description blockquote menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import BlockquoteMenu from '../../src/modules/blockquote/menu/BlockquoteMenu' - -describe('blockquote menu', () => { - let editor: any - let startLocation: any - const menu = new BlockquoteMenu() - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // getValue 无逻辑,不用测试 - - it('is disabled', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - Transforms.setNodes(editor, { type: 'blockquote' }) - expect(menu.isDisabled(editor)).toBeFalsy() - - Transforms.setNodes(editor, { type: 'header1' }) - expect(menu.isDisabled(editor)).toBeTruthy() // 非 p blockquote ,则禁用 - }) - - it('exec and isActive', () => { - editor.select(startLocation) - - menu.exec(editor, '') // 转换为 blockquote - const blockquotes1 = editor.getElemsByTypePrefix('blockquote') - expect(blockquotes1.length).toBe(1) - expect(menu.isActive(editor)).toBeTruthy() - - menu.exec(editor, '') // 取消 blockquote - const blockquotes2 = editor.getElemsByTypePrefix('blockquote') - expect(blockquotes2.length).toBe(0) - expect(menu.isActive(editor)).toBeFalsy() - }) -}) diff --git a/packages/basic-modules/__tests__/blockquote/elem-to-html.test.ts b/packages/basic-modules/__tests__/blockquote/elem-to-html.test.ts deleted file mode 100644 index db49ca18e..000000000 --- a/packages/basic-modules/__tests__/blockquote/elem-to-html.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @description blockquote - elem to html test - * @author wangfupeng - */ - -import { quoteToHtmlConf } from '../../src/modules/blockquote/elem-to-html' - -describe('blockquote elem to html', () => { - it('blockquote to html', () => { - expect(quoteToHtmlConf.type).toBe('blockquote') - - const elem = { type: 'blockquote', children: [] } - const html = quoteToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('
hello
') - }) -}) diff --git a/packages/basic-modules/__tests__/blockquote/parse-html.test.ts b/packages/basic-modules/__tests__/blockquote/parse-html.test.ts deleted file mode 100644 index a85bf80d3..000000000 --- a/packages/basic-modules/__tests__/blockquote/parse-html.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import { BaseElement } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import { parseHtmlConf } from '../../src/modules/blockquote/parse-elem-html' - -describe('blockquote - parse html', () => { - const editor = createEditor() - - it('without children', () => { - const $elem = $('
hello world
') - - // match selector - expect($elem[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseHtmlConf.parseElemHtml($elem[0], [], editor) - expect(res).toEqual({ - type: 'blockquote', - children: [{ text: 'hello world' }], - }) - }) - - it('with children', () => { - const $elem = $('
') - const children = [{ text: 'hello ' }, { text: 'world', bold: true }] - - // parse - const res = parseHtmlConf.parseElemHtml($elem[0], children, editor) - expect(res).toEqual({ - type: 'blockquote', - children: [{ text: 'hello ' }, { text: 'world', bold: true }], - }) - }) - - it('with inline children', () => { - const $elem = $('
') - const children: any[] = [ - { text: 'hello ' }, - { type: 'link', url: 'http://wangeditor.com' }, - { type: 'paragraph', children: [] }, - ] - - const isInline = editor.isInline - editor.isInline = (element: any) => { - if (element.type === 'link') return true - return isInline(element) - } - - // parse - const res = parseHtmlConf.parseElemHtml($elem[0], children, editor) - expect(res).toEqual({ - type: 'blockquote', - children: [{ text: 'hello ' }, { type: 'link', url: 'http://wangeditor.com' }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/blockquote/plugin.test.ts b/packages/basic-modules/__tests__/blockquote/plugin.test.ts deleted file mode 100644 index 56c98ffba..000000000 --- a/packages/basic-modules/__tests__/blockquote/plugin.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @description blockquote plugin test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import withBlockquote from '../../src/modules/blockquote/plugin' - -describe('blockquote plugin', () => { - let editor = withBlockquote(createEditor()) - let startLocation = Editor.start(editor, []) - - beforeEach(() => { - editor = withBlockquote(createEditor()) - startLocation = Editor.start(editor, []) - }) - - it('insert break', () => { - expect(1).toBeTruthy() - - // TODO 该测试一直报错(找不到 blockquote path),待定处理 - // editor.select(startLocation) - - // // @ts-ignore - // Transforms.setNodes(editor, { type: 'blockquote' }) // 设置 blockquote - - // const pList1 = editor.getElemsByType('paragraph') - // expect(pList1.length).toBe(0) - - // editor.insertText('hello') - // console.log(11, JSON.stringify(editor.children)) - // console.log(22, JSON.stringify(editor.selection)) - // editor.insertBreak() // 第一次换行,内部插入 \n - - // const pList2 = editor.getElemsByType('paragraph') - // expect(pList2.length).toBe(0) - - // editor.insertBreak() // 再一次换行,生成 p - // const pList3 = editor.getElemsByType('paragraph') - // expect(pList3.length).toBe(1) - }) -}) diff --git a/packages/basic-modules/__tests__/blockquote/render-elem.test.ts b/packages/basic-modules/__tests__/blockquote/render-elem.test.ts deleted file mode 100644 index 2b169dc2b..000000000 --- a/packages/basic-modules/__tests__/blockquote/render-elem.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @description blockquote render elem test - * @author wangfupeng - */ - -import createEditor from '../../../../tests/utils/create-editor' -import { renderBlockQuoteConf } from '../../src/modules/blockquote/render-elem' - -describe('blockquote - render elem', () => { - const editor = createEditor() - - it('render blockquote elem', () => { - expect(renderBlockQuoteConf.type).toBe('blockquote') - - const elem = { type: 'blockquote', children: [] } - const vnode = renderBlockQuoteConf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('blockquote') - }) -}) diff --git a/packages/basic-modules/__tests__/code-block/code-block-menu.test.ts b/packages/basic-modules/__tests__/code-block/code-block-menu.test.ts deleted file mode 100644 index ffb33e79c..000000000 --- a/packages/basic-modules/__tests__/code-block/code-block-menu.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @description code-block menu test - * @author wangfupeng - */ - -import { Editor, Transforms, Element } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import CodeBlockMenu from '../../src/modules/code-block/menu/CodeBlockMenu' - -describe('code-block menu', () => { - const menu = new CodeBlockMenu() - let editor: any - let startLocation: any - - const codeElem = { - type: 'code', - language: 'javascript', - children: [{ text: 'var' }], - } - const preElem = { - type: 'pre', - children: [codeElem], - } - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('getValue and isActive', done => { - editor.select(startLocation) - expect(menu.isActive(editor)).toBeFalsy() - expect(menu.getValue(editor)).toBe('') - - editor.insertNode(preElem) // 插入 code node - editor.select({ - path: [1, 0, 0], // 选中 code node - offset: 3, - }) - setTimeout(() => { - expect(menu.isActive(editor)).toBeTruthy() - expect(menu.getValue(editor)).toBe('javascript') - done() - }) - }) - - it('is disabled', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - Transforms.setNodes(editor, { type: 'header1' } as Partial) - expect(menu.isDisabled(editor)).toBeTruthy() // 非 p pre ,则禁用 - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(menu.isDisabled(editor)).toBeFalsy() - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - it('exec - to code-block', () => { - editor.select(startLocation) - - menu.exec(editor, 'javascript') // 生成 code-block - const preList = editor.getElemsByTypePrefix('pre') - expect(preList.length).toBe(1) - const codeLis = editor.getElemsByTypePrefix('code') - expect(codeLis.length).toBe(1) - }) - - it('exec - to paragraph', () => { - editor.select(startLocation) - editor.insertNode(preElem) // 插入 code node - editor.select({ - path: [1, 0, 0], // 选中 code node - offset: 3, - }) - - menu.exec(editor, '') // 取消 code-block - const preList = editor.getElemsByTypePrefix('pre') - expect(preList.length).toBe(0) - const codeLis = editor.getElemsByTypePrefix('code') - expect(codeLis.length).toBe(0) - }) -}) diff --git a/packages/basic-modules/__tests__/code-block/elem-to-html.test.ts b/packages/basic-modules/__tests__/code-block/elem-to-html.test.ts deleted file mode 100644 index 64cec9299..000000000 --- a/packages/basic-modules/__tests__/code-block/elem-to-html.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @description code-block elem to html test - * @author wangfupeng - */ - -import { codeToHtmlConf, preToHtmlConf } from '../../src/modules/code-block/elem-to-html' - -describe('code-block - elem to html', () => { - it('code to html', () => { - expect(codeToHtmlConf.type).toBe('code') - const elem = { type: 'code', children: [] } - const html = codeToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('hello') - }) - - it('pre to html', () => { - expect(preToHtmlConf.type).toBe('pre') - const elem = { type: 'pre', children: [] } - const html = preToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('
hello
') - }) -}) diff --git a/packages/basic-modules/__tests__/code-block/parse-html.test.ts b/packages/basic-modules/__tests__/code-block/parse-html.test.ts deleted file mode 100644 index 18eb4f445..000000000 --- a/packages/basic-modules/__tests__/code-block/parse-html.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @description parse elem html - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseCodeHtmlConf, parsePreHtmlConf } from '../../src/modules/code-block/parse-elem-html' -import { preParseHtmlConf } from '../../src/modules/code-block/pre-parse-html' - -describe('code block - pre parse html', () => { - it('pre parse html', () => { - const $pre = $('
')
-    const $code = $('var a = 100;')
-    $pre.append($code)
-
-    // match selector
-    expect($code[0].matches(preParseHtmlConf.selector)).toBeTruthy()
-
-    // pre parse
-    const res = preParseHtmlConf.preParseHtml($code[0])
-    expect(res.innerHTML).toBe('var a = 100;')
-  })
-})
-
-describe('code block - parse html', () => {
-  const editor = createEditor()
-
-  it('parse code html', () => {
-    const $pre = $('
')
-    const $code = $('var a = 100;')
-    $pre.append($code)
-
-    // match selector
-    expect($code[0].matches(parseCodeHtmlConf.selector)).toBeTruthy()
-
-    // parse
-    const res = parseCodeHtmlConf.parseElemHtml($code[0], [], editor)
-    expect(res).toEqual({
-      type: 'code',
-      language: '',
-      children: [{ text: 'var a = 100;' }],
-    })
-  })
-  it('parse pre html', () => {
-    const $pre = $('
')
-    const children = [
-      {
-        type: 'code',
-        language: '',
-        children: [{ text: 'var a = 100;' }],
-      },
-    ]
-
-    // match selector
-    expect($pre[0].matches(parsePreHtmlConf.selector)).toBeTruthy()
-
-    // parse
-    const res = parsePreHtmlConf.parseElemHtml($pre[0], children, editor)
-    expect(res).toEqual({
-      type: 'pre',
-      children: [
-        {
-          type: 'code',
-          language: '',
-          children: [{ text: 'var a = 100;' }],
-        },
-      ],
-    })
-  })
-})
diff --git a/packages/basic-modules/__tests__/code-block/plugin.test.ts b/packages/basic-modules/__tests__/code-block/plugin.test.ts
deleted file mode 100644
index 15ab9b0f1..000000000
--- a/packages/basic-modules/__tests__/code-block/plugin.test.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * @description code-block plugin test
- * @author wangfupeng
- */
-
-import { Editor, Transforms } from 'slate'
-import createEditor from '../../../../tests/utils/create-editor'
-import withCodeBlock from '../../src/modules/code-block/plugin'
-
-// 模拟 DataTransfer
-class MyDataTransfer {
-  private values: object = {}
-  setData(type: string, value: string) {
-    this.values[type] = value
-  }
-  getData(type: string): string {
-    return this.values[type]
-  }
-}
-
-describe('code-block plugin', () => {
-  let editor: any
-  let startLocation: any
-
-  const codeElem = {
-    type: 'code',
-    children: [{ text: 'var' }],
-  }
-  const preElem = {
-    type: 'pre',
-    children: [codeElem],
-  }
-
-  beforeEach(() => {
-    editor = withCodeBlock(createEditor())
-    startLocation = Editor.start(editor, [])
-  })
-
-  afterEach(() => {
-    editor = null
-    startLocation = null
-  })
-
-  it('insert break', () => {
-    editor.select(startLocation)
-    editor.insertNode(preElem) // 插入 code-block
-
-    // code-block 前后会自动生成两个 p
-    const pList1 = editor.getElemsByTypePrefix('paragraph')
-    expect(pList1.length).toBe(2)
-
-    editor.select({
-      path: [1, 0, 0], // 选中 code-block
-      offset: 3,
-    })
-
-    // 换行都在 code-block 内部
-    editor.insertBreak()
-    editor.insertBreak()
-    editor.insertBreak()
-    expect(editor.getText()).toBe('\nvar\n\n\n\n')
-
-    // 不会再生成新的 p
-    const pList2 = editor.getElemsByTypePrefix('paragraph')
-    expect(pList2.length).toBe(2)
-  })
-
-  it('insert data', () => {
-    editor.select(startLocation)
-    editor.insertNode(preElem) // 插入 code node
-    editor.select({
-      path: [1, 0, 0], // 选中 code node
-      offset: 3,
-    })
-
-    const data = new MyDataTransfer()
-    data.setData('text/plain', ' hello')
-
-    editor.insertData(data)
-    expect(editor.getText()).toBe('\nvar hello\n')
-  })
-
-  it('normalizeNode - code node 不能是顶级元素,否则替换为 p', () => {
-    editor.select(startLocation)
-    editor.insertNode(codeElem)
-
-    const pList = editor.getElemsByTypePrefix('paragraph')
-    expect(pList.length).toBe(2)
-  })
-
-  it('normalizeNode - pre node 不能是第一个节点,否则前面插入 p', () => {
-    editor.select(startLocation)
-    editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] })
-
-    const pList = editor.getElemsByTypePrefix('paragraph')
-    expect(pList.length).toBe(2)
-
-    const preList = editor.getElemsByTypePrefix('pre')
-    expect(preList.length).toBe(1)
-  })
-})
diff --git a/packages/basic-modules/__tests__/code-block/render-elem.test.ts b/packages/basic-modules/__tests__/code-block/render-elem.test.ts
deleted file mode 100644
index 2f2b7e21b..000000000
--- a/packages/basic-modules/__tests__/code-block/render-elem.test.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * @description code-block render elem test
- * @author wangfupeng
- */
-
-import createEditor from '../../../../tests/utils/create-editor'
-import { renderPreConf, renderCodeConf } from '../../src/modules/code-block/render-elem'
-
-describe('code-block render elem', () => {
-  const editor = createEditor()
-
-  it('render code elem', () => {
-    expect(renderCodeConf.type).toBe('code')
-
-    const elem = { type: 'code', children: [] }
-    const vnode = renderCodeConf.renderElem(elem, null, editor)
-    expect(vnode.sel).toBe('code')
-  })
-
-  it('render pre elem', () => {
-    expect(renderPreConf.type).toBe('pre')
-
-    const elem = { type: 'pre', children: [] }
-    const vnode = renderPreConf.renderElem(elem, null, editor)
-    expect(vnode.sel).toBe('pre')
-  })
-})
diff --git a/packages/basic-modules/__tests__/color/color-menus.test.ts b/packages/basic-modules/__tests__/color/color-menus.test.ts
deleted file mode 100644
index 0841fc170..000000000
--- a/packages/basic-modules/__tests__/color/color-menus.test.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * @description color menus test
- * @author wangfupeng
- */
-
-import { Editor, Transforms } from 'slate'
-import createEditor from '../../../../tests/utils/create-editor'
-import ColorMenu from '../../src/modules/color/menu/ColorMenu'
-import BgColorMenu from '../../src/modules/color/menu/BgColorMenu'
-
-describe('color menus', () => {
-  let editor: any
-  let startLocation: any
-
-  const menus = [
-    {
-      mark: 'color',
-      menu: new ColorMenu(),
-    },
-    {
-      mark: 'bgColor',
-      menu: new BgColorMenu(),
-    },
-  ]
-
-  beforeEach(() => {
-    editor = createEditor()
-    startLocation = Editor.start(editor, [])
-  })
-
-  afterEach(() => {
-    editor = null
-    startLocation = null
-  })
-
-  // exec 无代码,不用测试
-
-  it('getValue and isActive', () => {
-    editor.select(startLocation)
-
-    menus.forEach(({ menu }) => {
-      expect(menu.getValue(editor)).toBe('')
-      expect(menu.isActive(editor)).toBeFalsy()
-    })
-
-    editor.insertText('hello') // 插入文字
-    editor.select([]) // 全选
-    menus.forEach(({ mark, menu }) => {
-      editor.addMark(mark, 'rgb(51, 51, 51)') // 添加 color bgColor
-      expect(menu.getValue(editor)).toBe('rgb(51, 51, 51)')
-      expect(menu.isActive(editor)).toBeTruthy()
-    })
-  })
-
-  it('is disabled', () => {
-    editor.select(startLocation)
-    menus.forEach(({ menu }) => {
-      expect(menu.isDisabled(editor)).toBeFalsy()
-    })
-
-    editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] })
-    menus.forEach(({ menu }) => {
-      expect(menu.isDisabled(editor)).toBeTruthy()
-    })
-    // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code
-  })
-
-  it('get panel content elem', () => {
-    menus.forEach(({ menu }) => {
-      const elem = menu.getPanelContentElem(editor)
-      expect(elem instanceof HTMLElement).toBeTruthy()
-    })
-  })
-})
diff --git a/packages/basic-modules/__tests__/color/parse-html.test.ts b/packages/basic-modules/__tests__/color/parse-html.test.ts
deleted file mode 100644
index 3f8e20832..000000000
--- a/packages/basic-modules/__tests__/color/parse-html.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * @description parse html test
- * @author wangfupeng
- */
-
-import { $ } from 'dom7'
-import createEditor from '../../../../tests/utils/create-editor'
-import { parseStyleHtml } from '../../src/modules/color/parse-style-html'
-import { preParseHtmlConf } from '../../src/modules/color/pre-parse-html'
-
-describe('color - pre parse html', () => {
-  it('pre parse html', () => {
-    const $font = $('hello')
-
-    // match selector
-    expect($font[0].matches(preParseHtmlConf.selector)).toBeTruthy()
-
-    // pre parse
-    const res = preParseHtmlConf.preParseHtml($font[0])
-    expect(res.outerHTML).toBe('hello')
-  })
-})
-
-describe('color - parse style html', () => {
-  const editor = createEditor()
-
-  it('parse style html', () => {
-    const $span = $(
-      ''
-    )
-    const textNode = { text: 'hello' }
-
-    // parse style
-    const res = parseStyleHtml($span[0], textNode, editor)
-    expect(res).toEqual({
-      text: 'hello',
-      color: 'rgb(235, 144, 58)',
-      bgColor: 'rgb(231, 246, 213)',
-    })
-  })
-})
diff --git a/packages/basic-modules/__tests__/color/render-text-style.test.tsx b/packages/basic-modules/__tests__/color/render-text-style.test.tsx
deleted file mode 100644
index a85a39cbd..000000000
--- a/packages/basic-modules/__tests__/color/render-text-style.test.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @description color - render text style test
- * @author wangfupeng
- */
-
-import { jsx } from 'snabbdom'
-import { renderStyle } from '../../src/modules/color/render-style'
-
-describe('color - render text style', () => {
-  it('render color style', () => {
-    const color = 'rgb(51, 51, 51)'
-    const bgColor = 'rgb(204, 204, 204)'
-    const textNode = { text: 'hello', color, bgColor }
-    const vnode = hello
-
-    // @ts-ignore
-    const newVnode = renderStyle(textNode, vnode) as any
-    expect(newVnode.sel).toBe('span')
-    expect(newVnode.data.style.color).toBe(color)
-    expect(newVnode.data.style.backgroundColor).toBe(bgColor)
-  })
-})
diff --git a/packages/basic-modules/__tests__/color/text-style-to-html.test.ts b/packages/basic-modules/__tests__/color/text-style-to-html.test.ts
deleted file mode 100644
index a9d4e1936..000000000
--- a/packages/basic-modules/__tests__/color/text-style-to-html.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description color - text style to html test
- * @author wangfupeng
- */
-
-import { styleToHtml } from '../../src/modules/color/style-to-html'
-
-describe('color - text style to html', () => {
-  it('color to html', () => {
-    const color = 'rgb(51, 51, 51)'
-    const bgColor = 'rgb(204, 204, 204)'
-    const textNode = { text: '', color, bgColor }
-
-    const html = styleToHtml(textNode, 'hello')
-    expect(html).toBe(`hello`)
-  })
-})
diff --git a/packages/basic-modules/__tests__/divider/elem-to-html.test.ts b/packages/basic-modules/__tests__/divider/elem-to-html.test.ts
deleted file mode 100644
index c27b52212..000000000
--- a/packages/basic-modules/__tests__/divider/elem-to-html.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * @description divider - elem to html test
- * @author wangfupeng
- */
-
-import { dividerToHtmlConf } from '../../src/modules/divider/elem-to-html'
-
-describe('divider - elem to html', () => {
-  it('divider to html', () => {
-    expect(dividerToHtmlConf.type).toBe('divider')
-
-    const elem = { type: 'divider', children: [{ text: '' }] }
-    const html = dividerToHtmlConf.elemToHtml(elem, '')
-    expect(html).toBe('
') - }) -}) diff --git a/packages/basic-modules/__tests__/divider/insert-divider-menu.test.ts b/packages/basic-modules/__tests__/divider/insert-divider-menu.test.ts deleted file mode 100644 index 3414c051b..000000000 --- a/packages/basic-modules/__tests__/divider/insert-divider-menu.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @description insert divider menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import InsertDividerMenu from '../../src/modules/divider/menu/InsertDividerMenu' - -describe('divider plugin', () => { - const menu = new InsertDividerMenu() - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // getValue isActive 无逻辑,不用测试 - - it('is disabled', () => { - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - const elem = { type: 'divider', children: [{ text: '' }] } - editor.insertNode(elem) // 插入 divider - editor.select({ - path: [1, 0], // 选中 divider - offset: 0, - }) - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('exec', () => { - editor.select(startLocation) - menu.exec(editor, '') - - const dividers = editor.getElemsByTypePrefix('divider') - expect(dividers.length).toBe(1) - }) -}) diff --git a/packages/basic-modules/__tests__/divider/parse-html.test.ts b/packages/basic-modules/__tests__/divider/parse-html.test.ts deleted file mode 100644 index 2e0bfb28f..000000000 --- a/packages/basic-modules/__tests__/divider/parse-html.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseHtmlConf } from '../../src/modules/divider/parse-elem-html' - -describe('divider - parse html', () => { - const editor = createEditor() - - it('parse html', () => { - const $hr = $('
') - - // match selector - expect($hr[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseHtmlConf.parseElemHtml($hr[0], [], editor) - expect(res).toEqual({ - type: 'divider', - children: [{ text: '' }], // void node 有一个空白 text - }) - }) -}) diff --git a/packages/basic-modules/__tests__/divider/plugin.test.ts b/packages/basic-modules/__tests__/divider/plugin.test.ts deleted file mode 100644 index 6d0c3e127..000000000 --- a/packages/basic-modules/__tests__/divider/plugin.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @description divider plugin test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import withDivider from '../../src/modules/divider/plugin' - -describe('divider plugin', () => { - let editor: any - let startLocation: any - - beforeEach(() => { - editor = withDivider(createEditor()) - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('divider is void node', () => { - const elem = { type: 'divider', children: [{ text: '' }] } - expect(editor.isVoid(elem)).toBeTruthy() - }) - - it('normalizeNode - divider 不能是最后一个元素,否则后面追加 p', () => { - const elem = { type: 'divider', children: [{ text: '' }] } - editor.select(startLocation) - editor.insertNode(elem) // 插入 divider - - const length = editor.children.length - expect(length).toBe(3) // 3 个顶级节点:p, divider, p - - const divider = editor.children[1] // 第 2 个节点应该是 divider - expect(divider.type).toBe('divider') - const p = editor.children[2] // 第 3 个节点应该是 p - expect(p.type).toBe('paragraph') - }) -}) diff --git a/packages/basic-modules/__tests__/divider/render-elem.test.ts b/packages/basic-modules/__tests__/divider/render-elem.test.ts deleted file mode 100644 index fe8d1887b..000000000 --- a/packages/basic-modules/__tests__/divider/render-elem.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @description divider - render elem test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import { renderDividerConf } from '../../src/modules/divider/render-elem' - -describe('divider - render elem test', () => { - const editor = createEditor() - const startLocation = Editor.start(editor, []) - - it('render divider elem', () => { - expect(renderDividerConf.type).toBe('divider') - - const elem = { type: 'divider', children: [{ text: '' }] } - const vnode1 = renderDividerConf.renderElem(elem, null, editor) as any - expect(vnode1.sel).toBe('div') - expect(vnode1.data.props.className).toBe('w-e-textarea-divider') - expect(vnode1.data.dataset.selected).toBe('') // 未选中 - expect(vnode1.children[0].sel).toBe('hr') - - editor.select(startLocation) - editor.insertNode(elem) // 插入 divider - editor.select({ - path: [1, 0], // 选中 divider - offset: 0, - }) - const vnode2 = renderDividerConf.renderElem(elem, null, editor) as any - expect(vnode2.data.dataset.selected).toBe('true') // 选中 - }) -}) diff --git a/packages/basic-modules/__tests__/emotion/emotion-menu.test.ts b/packages/basic-modules/__tests__/emotion/emotion-menu.test.ts deleted file mode 100644 index d19873d69..000000000 --- a/packages/basic-modules/__tests__/emotion/emotion-menu.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @description emotion menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import EmotionMenu from '../../src/modules/emotion/menu/EmotionMenu' - -describe('font family menu', () => { - const menu = new EmotionMenu() - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // exec getValue isActive 无代码逻辑,不用测试 - - it('is disabled', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(menu.isDisabled(editor)).toBeTruthy() - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - it('get panel content elem', () => { - const elem = menu.getPanelContentElem(editor) - expect(elem instanceof HTMLElement).toBeTruthy() - }) -}) diff --git a/packages/basic-modules/__tests__/font-size-family/menu/font-family-menu.test.ts b/packages/basic-modules/__tests__/font-size-family/menu/font-family-menu.test.ts deleted file mode 100644 index 759451768..000000000 --- a/packages/basic-modules/__tests__/font-size-family/menu/font-family-menu.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @description font family menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import FontFamilyMenu from '../../../src/modules/font-size-family/menu/FontFamilyMenu' - -describe('font family menu', () => { - const menu = new FontFamilyMenu() - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get options', () => { - editor.select(startLocation) - const options1 = menu.getOptions(editor) - const selectedDefault = options1.some(opt => opt.selected && opt.value === '') - expect(selectedDefault).toBeTruthy() // 空白 p ,选中“默认” - - editor.insertText('hello') - editor.select([]) // 全选 - editor.addMark('fontFamily', '黑体') // 设置字体 - const options2 = menu.getOptions(editor) - const selectedHeiti = options2.some(opt => opt.selected && opt.value === '黑体') - expect(selectedHeiti).toBeTruthy() - }) - - // isActive 无代码逻辑,不用测试 - - it('is disabled', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(menu.isDisabled(editor)).toBeTruthy() - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - it('exec and getValue', () => { - editor.select(startLocation) - expect(menu.getValue(editor)).toBe('') - - editor.insertText('hello') - editor.select([]) // 全选 - menu.exec(editor, '黑体') // 设置字体 - expect(menu.getValue(editor)).toBe('黑体') - - menu.exec(editor, '') // 取消字体 - expect(menu.getValue(editor)).toBe('') - }) -}) diff --git a/packages/basic-modules/__tests__/font-size-family/menu/font-size-menu.test.ts b/packages/basic-modules/__tests__/font-size-family/menu/font-size-menu.test.ts deleted file mode 100644 index 4c2e67a75..000000000 --- a/packages/basic-modules/__tests__/font-size-family/menu/font-size-menu.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @description font size menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import FontSizeMenu from '../../../src/modules/font-size-family/menu/FontSizeMenu' - -describe('font family menu', () => { - const menu = new FontSizeMenu() - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get options', () => { - editor.select(startLocation) - const options1 = menu.getOptions(editor) - const selectedDefault = options1.some(opt => opt.selected && opt.value === '') - expect(selectedDefault).toBeTruthy() // 空白 p ,选中“默认” - - editor.insertText('hello') - editor.select([]) // 全选 - editor.addMark('fontSize', '40px') // 设置字号 - const options2 = menu.getOptions(editor) - const selected = options2.some(opt => opt.selected && opt.value === '40px') - expect(selected).toBeTruthy() - }) - - // isActive 无代码逻辑,不用测试 - - it('is disabled', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(menu.isDisabled(editor)).toBeTruthy() - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - it('exec and getValue', () => { - editor.select(startLocation) - expect(menu.getValue(editor)).toBe('') - - editor.insertText('hello') - editor.select([]) // 全选 - menu.exec(editor, '40px') // 设置字号 - expect(menu.getValue(editor)).toBe('40px') - - menu.exec(editor, '') // 取消字号 - expect(menu.getValue(editor)).toBe('') - }) -}) diff --git a/packages/basic-modules/__tests__/font-size-family/parse-html.test.ts b/packages/basic-modules/__tests__/font-size-family/parse-html.test.ts deleted file mode 100644 index 6007f15b0..000000000 --- a/packages/basic-modules/__tests__/font-size-family/parse-html.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseStyleHtml } from '../../src/modules/font-size-family/parse-style-html' -import { preParseHtmlConf } from '../../src/modules/font-size-family/pre-parse-html' - -describe('font size family - pre parse html', () => { - it('pre parse html', () => { - const $font = $('hello') - - // match selector - expect($font[0].matches(preParseHtmlConf.selector)).toBeTruthy() - - // pre parse - const res = preParseHtmlConf.preParseHtml($font[0]) - expect(res.outerHTML).toBe('hello') - }) -}) - -describe('font size family - parse style html', () => { - const editor = createEditor() - - it('parse style html', () => { - const $span = $('') - const textNode = { text: 'hello' } - - // parse style - const res = parseStyleHtml($span[0], textNode, editor) - expect(res).toEqual({ - text: 'hello', - fontSize: '12px', - fontFamily: '黑体', - }) - }) -}) diff --git a/packages/basic-modules/__tests__/font-size-family/render-text-style.test.tsx b/packages/basic-modules/__tests__/font-size-family/render-text-style.test.tsx deleted file mode 100644 index 69ff804ed..000000000 --- a/packages/basic-modules/__tests__/font-size-family/render-text-style.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @description font size and family - render text style test - * @author wangfupeng - */ - -import { jsx } from 'snabbdom' -import { renderStyle } from '../../src/modules/font-size-family/render-style' - -describe('font size and family - render text style', () => { - it('render text style', () => { - const fontSize = '20px' - const fontFamily = '黑体' - const textNode = { text: 'hello', fontSize, fontFamily } - const vnode = hello - - // @ts-ignore 忽略 vnode 格式检查 - const newVnode = renderStyle(textNode, vnode) as any - expect(newVnode.data.style.fontSize).toBe(fontSize) - expect(newVnode.data.style.fontFamily).toBe(fontFamily) - }) -}) diff --git a/packages/basic-modules/__tests__/font-size-family/text-style-to-html.test.ts b/packages/basic-modules/__tests__/font-size-family/text-style-to-html.test.ts deleted file mode 100644 index ac68c04fd..000000000 --- a/packages/basic-modules/__tests__/font-size-family/text-style-to-html.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @description font size and family - text style to html test - * @author wangfupeng - */ - -import { styleToHtml } from '../../src/modules/font-size-family/style-to-html' - -describe('font size and family - text style to html', () => { - it('text style to html', () => { - const fontSize = '20px' - const fontFamily = '黑体' - const textNode = { text: '', fontSize, fontFamily } - - const html = styleToHtml(textNode, 'hello') - expect(html).toBe( - `hello` - ) - }) -}) diff --git a/packages/basic-modules/__tests__/full-screen/full-screen-menu.test.ts b/packages/basic-modules/__tests__/full-screen/full-screen-menu.test.ts deleted file mode 100644 index 377feaae8..000000000 --- a/packages/basic-modules/__tests__/full-screen/full-screen-menu.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @description full screen menu test - * @author wangfupeng - */ - -import createEditor from '../../../../tests/utils/create-editor' -import FullScreen from '../../src/modules/full-screen/menu/FullScreen' - -describe('full screen menu', () => { - const editor = createEditor() - const menu = new FullScreen() - - it('full screen menu', done => { - menu.exec(editor, '') // 设置全屏 - expect(menu.isActive(editor)).toBeTruthy() - - menu.exec(editor, '') // 取消全屏(有延迟) - setTimeout(() => { - expect(menu.isActive(editor)).toBeFalsy() - done() - }, 500) - }) -}) diff --git a/packages/basic-modules/__tests__/header/elem-to-html.test.ts b/packages/basic-modules/__tests__/header/elem-to-html.test.ts deleted file mode 100644 index bf2e6a959..000000000 --- a/packages/basic-modules/__tests__/header/elem-to-html.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @description header - elem to html test - * @author wangfupeng - */ - -import { - header1ToHtmlConf, - header2ToHtmlConf, - header3ToHtmlConf, - header4ToHtmlConf, - header5ToHtmlConf, -} from '../../src/modules/header/elem-to-html' - -describe('header - elem to html', () => { - const elem = { type: 'header1', children: [{ text: '' }] } - it('header1 to html', () => { - expect(header1ToHtmlConf.type).toBe('header1') - const html = header1ToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('

hello

') - }) - - it('header2 to html', () => { - expect(header2ToHtmlConf.type).toBe('header2') - const html = header2ToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('

hello

') - }) - - it('header3 to html', () => { - expect(header3ToHtmlConf.type).toBe('header3') - const html = header3ToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('

hello

') - }) - - it('header4 to html', () => { - expect(header4ToHtmlConf.type).toBe('header4') - const html = header4ToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('

hello

') - }) - - it('header5 to html', () => { - expect(header5ToHtmlConf.type).toBe('header5') - const html = header5ToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('
hello
') - }) -}) diff --git a/packages/basic-modules/__tests__/header/helper.test.ts b/packages/basic-modules/__tests__/header/helper.test.ts deleted file mode 100644 index f0e8d7fac..000000000 --- a/packages/basic-modules/__tests__/header/helper.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @description header helper test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import { getHeaderType, isMenuDisabled, setHeaderType } from '../../src/modules/header/helper' - -describe('header helper', () => { - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get header type', () => { - editor.select(startLocation) - expect(getHeaderType(editor)).toBe('paragraph') - - Transforms.setNodes(editor, { type: 'header1' }) - expect(getHeaderType(editor)).toBe('header1') - }) - - it('is menu disabled', () => { - editor.select(startLocation) - expect(isMenuDisabled(editor)).toBeFalsy() - - Transforms.setNodes(editor, { type: 'header1' }) - expect(isMenuDisabled(editor)).toBeFalsy() - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(isMenuDisabled(editor)).toBeTruthy() // 只能用于 p header - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - it('set header type', () => { - editor.select(startLocation) - setHeaderType(editor, 'header1') - - const headers = editor.getElemsByTypePrefix('header1') - expect(headers.length).toBe(1) - }) -}) diff --git a/packages/basic-modules/__tests__/header/menu/header-select-menu.test.ts b/packages/basic-modules/__tests__/header/menu/header-select-menu.test.ts deleted file mode 100644 index ce5010f45..000000000 --- a/packages/basic-modules/__tests__/header/menu/header-select-menu.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @description header select menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import HeaderSelectMenu from '../../../src/modules/header/menu/HeaderSelectMenu' - -describe('header select menu', () => { - const editor = createEditor() - const startLocation = Editor.start(editor, []) - const menu = new HeaderSelectMenu() - - it('get options', () => { - editor.select(startLocation) - const options1 = menu.getOptions(editor) - const selectedP = options1.some(opt => opt.selected && opt.value === 'paragraph') // 选中“文本” - expect(selectedP).toBeTruthy() - - Transforms.setNodes(editor, { type: 'header1' }) - const options2 = menu.getOptions(editor) - const selectedHeader = options2.some(opt => opt.selected && opt.value === 'header1') // 选中“h1” - expect(selectedHeader).toBeTruthy() - }) - - // isActive 无逻辑,不用测试 - - // getValue isDisabled exec 已经在 helper.test.ts 中测试过了 -}) diff --git a/packages/basic-modules/__tests__/header/menu/header1-menu.test.ts b/packages/basic-modules/__tests__/header/menu/header1-menu.test.ts deleted file mode 100644 index 180f4611f..000000000 --- a/packages/basic-modules/__tests__/header/menu/header1-menu.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @description header1 menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import Header1ButtonMenu from '../../../src/modules/header/menu/Header1ButtonMenu' -import Header2ButtonMenu from '../../../src/modules/header/menu/Header2ButtonMenu' -import Header3ButtonMenu from '../../../src/modules/header/menu/Header3ButtonMenu' -import Header4ButtonMenu from '../../../src/modules/header/menu/Header4ButtonMenu' -import Header5ButtonMenu from '../../../src/modules/header/menu/Header5ButtonMenu' - -describe('header menu', () => { - const editor = createEditor() - const startLocation = Editor.start(editor, []) - - describe('header1 menu', () => { - const menu = new Header1ButtonMenu() - - it('exec', () => { - editor.select(startLocation) - - menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) - const headers1 = editor.getElemsByTypePrefix('header1') - expect(headers1.length).toBe(1) - - menu.exec(editor, 'header1') // 取消 header( header1 是当前选中的 node type ) - const headers2 = editor.getElemsByTypePrefix('header1') - expect(headers2.length).toBe(0) - }) - }) - - describe('header2 menu', () => { - const menu = new Header2ButtonMenu() - - it('exec', () => { - editor.select(startLocation) - - menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) - const headers1 = editor.getElemsByTypePrefix('header2') - expect(headers1.length).toBe(1) - - menu.exec(editor, 'header2') // 取消 header( header2 是当前选中的 node type ) - const headers2 = editor.getElemsByTypePrefix('header2') - expect(headers2.length).toBe(0) - }) - }) - - describe('header3 menu', () => { - const menu = new Header3ButtonMenu() - - it('exec', () => { - editor.select(startLocation) - - menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) - const headers1 = editor.getElemsByTypePrefix('header3') - expect(headers1.length).toBe(1) - - menu.exec(editor, 'header3') // 取消 header( header3 是当前选中的 node type ) - const headers2 = editor.getElemsByTypePrefix('header3') - expect(headers2.length).toBe(0) - }) - }) - - describe('header4 menu', () => { - const menu = new Header4ButtonMenu() - - it('exec', () => { - editor.select(startLocation) - - menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) - const headers1 = editor.getElemsByTypePrefix('header4') - expect(headers1.length).toBe(1) - - menu.exec(editor, 'header4') // 取消 header( header4 是当前选中的 node type ) - const headers2 = editor.getElemsByTypePrefix('header4') - expect(headers2.length).toBe(0) - }) - }) - - describe('header5 menu', () => { - const menu = new Header5ButtonMenu() - - it('exec', () => { - editor.select(startLocation) - - menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) - const headers1 = editor.getElemsByTypePrefix('header5') - expect(headers1.length).toBe(1) - - menu.exec(editor, 'header5') // 取消 header( header5 是当前选中的 node type ) - const headers2 = editor.getElemsByTypePrefix('header5') - expect(headers2.length).toBe(0) - }) - }) -}) diff --git a/packages/basic-modules/__tests__/header/parse-html.test.ts b/packages/basic-modules/__tests__/header/parse-html.test.ts deleted file mode 100644 index 28f6fc93e..000000000 --- a/packages/basic-modules/__tests__/header/parse-html.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseHeader1HtmlConf } from '../../src/modules/header/parse-elem-html' - -describe('header - parse html', () => { - const editor = createEditor() - - it('with children', () => { - const $h1 = $(`

`) - const children = [{ text: 'hello ' }, { text: 'world', bold: true }] - - // match selector - expect($h1[0].matches(parseHeader1HtmlConf.selector)).toBeTruthy() - - // parse html - const res = parseHeader1HtmlConf.parseElemHtml($h1[0], children, editor) - expect(res).toEqual({ - type: `header1`, - children: [{ text: 'hello ' }, { text: 'world', bold: true }], - }) - }) - - it('without children', () => { - const $h1 = $(`

hello world

`) - - // match selector - expect($h1[0].matches(parseHeader1HtmlConf.selector)).toBeTruthy() - - // parse html - const res = parseHeader1HtmlConf.parseElemHtml($h1[0], [], editor) - expect(res).toEqual({ - type: `header1`, - children: [{ text: 'hello world' }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/header/plugin.test.ts b/packages/basic-modules/__tests__/header/plugin.test.ts deleted file mode 100644 index cf7ea6f90..000000000 --- a/packages/basic-modules/__tests__/header/plugin.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @description header plugin test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import withHeader from '../../src/modules/header/plugin' - -describe('header plugin', () => { - const editor = withHeader(createEditor()) - const startLocation = Editor.start(editor, []) - - it('header break', () => { - editor.select(startLocation) - - Transforms.setNodes(editor, { type: 'header1' }) - editor.insertBreak() // 在 header 换行,会生成 p - - const paragraphs = editor.getElemsByTypePrefix('paragraph') - expect(paragraphs.length).toBe(1) - }) -}) diff --git a/packages/basic-modules/__tests__/header/render-elem.test.ts b/packages/basic-modules/__tests__/header/render-elem.test.ts deleted file mode 100644 index f3482bd1d..000000000 --- a/packages/basic-modules/__tests__/header/render-elem.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @description header - render elem test - * @author wangfupeng - */ - -import createEditor from '../../../../tests/utils/create-editor' -import { - renderHeader1Conf, - renderHeader2Conf, - renderHeader3Conf, - renderHeader4Conf, - renderHeader5Conf, -} from '../../src/modules/header/render-elem' - -describe('render header elem', () => { - const editor = createEditor() - - it('render h1', () => { - expect(renderHeader1Conf.type).toBe('header1') - - const elem = { type: 'header1', children: [] } - const vnode = renderHeader1Conf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('h1') - }) - - it('render h2', () => { - expect(renderHeader2Conf.type).toBe('header2') - - const elem = { type: 'header2', children: [] } - const vnode = renderHeader2Conf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('h2') - }) - - it('render h3', () => { - expect(renderHeader3Conf.type).toBe('header3') - - const elem = { type: 'header3', children: [] } - const vnode = renderHeader3Conf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('h3') - }) - - it('render h4', () => { - expect(renderHeader4Conf.type).toBe('header4') - - const elem = { type: 'header4', children: [] } - const vnode = renderHeader4Conf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('h4') - }) - - it('render h5', () => { - expect(renderHeader5Conf.type).toBe('header5') - - const elem = { type: 'header5', children: [] } - const vnode = renderHeader5Conf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('h5') - }) -}) diff --git a/packages/basic-modules/__tests__/image/elem-to-html.test.ts b/packages/basic-modules/__tests__/image/elem-to-html.test.ts deleted file mode 100644 index 4f736335e..000000000 --- a/packages/basic-modules/__tests__/image/elem-to-html.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @description image - elem to html test - * @author wangfupeng - */ - -import { imageToHtmlConf } from '../../src/modules/image/elem-to-html' - -describe('image to html', () => { - it('to html', () => { - expect(imageToHtmlConf.type).toBe('image') - - const src = 'https://www.wangeditor.com/imgs/logo.png' - const href = 'https://www.wangeditor.com/' - const elem = { - type: 'image', - src, - alt: 'logo', - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - const html = imageToHtmlConf.elemToHtml(elem, '') - - expect(html).toBe( - `logo` - ) - }) -}) diff --git a/packages/basic-modules/__tests__/image/helper.test.ts b/packages/basic-modules/__tests__/image/helper.test.ts deleted file mode 100644 index 395970d6e..000000000 --- a/packages/basic-modules/__tests__/image/helper.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @description image helper test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import { DomEditor } from '@wangeditor/core' -import createEditor from '../../../../tests/utils/create-editor' -import { - insertImageNode, - updateImageNode, - isInsertImageMenuDisabled, -} from '../../src/modules/image/helper' - -describe('image helper', () => { - let editor: any - let startLocation: any - - const src = 'https://www.wangeditor.com/imgs/logo.png' - const alt = 'logo' - const href = 'https://www.wangeditor.com/' - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('insert image node', async () => { - editor.select(startLocation) - await insertImageNode(editor, src, alt, href) - const images = editor.getElemsByTypePrefix('image') - expect(images.length).toBe(1) - }) - - it('update image node', async () => { - editor.select(startLocation) - - const elem = { - type: 'image', - src, - alt, - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - - const newSrc = 'https://www.baidu.com/logo.png' - const newAlt = 'baidu' - const newHref = 'https://www.baidu.com/' - await updateImageNode(editor, newSrc, newAlt, newHref, {}) // 更新图片信息 - - const imageNode = DomEditor.getSelectedNodeByType(editor, 'image') - expect(imageNode).not.toBeNull() - }) - - it('is menu disable', async () => { - editor.deselect() - expect(isInsertImageMenuDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(isInsertImageMenuDisabled(editor)).toBeFalsy() - - editor.insertText('hello') - editor.select([]) - expect(isInsertImageMenuDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - Transforms.setNodes(editor, { type: 'header1' }) - expect(isInsertImageMenuDisabled(editor)).toBeTruthy() - }) -}) diff --git a/packages/basic-modules/__tests__/image/menu/del-image.test.ts b/packages/basic-modules/__tests__/image/menu/del-image.test.ts deleted file mode 100644 index 822858a33..000000000 --- a/packages/basic-modules/__tests__/image/menu/del-image.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @description delete image menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import DeleteImage from '../../../src/modules/image/menu/DeleteImage' - -describe('delete image menu', () => { - const menu = new DeleteImage() - let editor: any - let startLocation: any - - const src = 'https://www.wangeditor.com/imgs/logo.png' - const alt = 'logo' - const href = 'https://www.wangeditor.com/' - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // getValue isActive 无逻辑,不用测试 - - it('is disabled', () => { - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeTruthy() - - const elem = { - type: 'image', - src, - alt, - href, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - expect(menu.isDisabled(editor)).toBeFalsy() - }) - - it('exec', () => { - editor.select(startLocation) - const elem = { - type: 'image', - src, - alt, - href, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - - menu.exec(editor, '') - const images = editor.getElemsByTypePrefix('image') - expect(images.length).toBe(0) - }) -}) diff --git a/packages/basic-modules/__tests__/image/menu/edit-image.test.ts b/packages/basic-modules/__tests__/image/menu/edit-image.test.ts deleted file mode 100644 index e4232cc91..000000000 --- a/packages/basic-modules/__tests__/image/menu/edit-image.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @description edit image menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import EditImage from '../../../src/modules/image/menu/EditImage' - -describe('edit image menu', () => { - const menu = new EditImage() - let editor: any - let startLocation: any - - const src = 'https://www.wangeditor.com/imgs/logo.png' - const alt = 'logo' - const href = 'https://www.wangeditor.com/' - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // getValue isActive exec 无逻辑,不用测试 - - it('is disabled', () => { - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeTruthy() - - const elem = { - type: 'image', - src, - alt, - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - expect(menu.isDisabled(editor)).toBeFalsy() - }) - - it('get modal position node', () => { - editor.select(startLocation) - expect(menu.getModalPositionNode(editor)).toBeNull() - - const elem = { - type: 'image', - src, - alt, - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - const imageNode = menu.getModalPositionNode(editor) - expect((imageNode as any).src).toBe(src) - }) - - it('get modal content elem', () => { - editor.select(startLocation) - const imageElem = { - type: 'image', - src, - alt, - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(imageElem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - - const elem = menu.getModalContentElem(editor) - expect(elem.tagName).toBe('DIV') - - // updateImage 在 helper.test.ts 中测试 - }) -}) diff --git a/packages/basic-modules/__tests__/image/menu/insert-image.test.ts b/packages/basic-modules/__tests__/image/menu/insert-image.test.ts deleted file mode 100644 index 7f1f061d4..000000000 --- a/packages/basic-modules/__tests__/image/menu/insert-image.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @description insert image menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import InsertImage from '../../../src/modules/image/menu/InsertImage' - -describe('insert image menu', () => { - const menu = new InsertImage() - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // getValue isActive exec 无逻辑,不用测试 - - it('is disabled', () => { - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.insertText('xxx') - editor.select([]) // 全选文字 - expect(menu.isDisabled(editor)).toBeTruthy() // 非折叠选区,则不可用 - - editor.select(startLocation) - Transforms.setNodes(editor, { type: 'header1' }) - expect(menu.isDisabled(editor)).toBeTruthy() // header 中不可用 - - Transforms.setNodes(editor, { type: 'blockquote' }) - expect(menu.isDisabled(editor)).toBeTruthy() // blockquote 中不可用 - }) - - // getModalPositionNode 无逻辑,不用测试 - - it('get modal content elem', () => { - const elem = menu.getModalContentElem(editor) - expect(elem.tagName).toBe('DIV') - - // insertImage 在 helper.test.ts 中测试 - }) -}) diff --git a/packages/basic-modules/__tests__/image/menu/view-image-link.test.ts b/packages/basic-modules/__tests__/image/menu/view-image-link.test.ts deleted file mode 100644 index fa624ff17..000000000 --- a/packages/basic-modules/__tests__/image/menu/view-image-link.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @description view image link menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import ViewImageLink from '../../../src/modules/image/menu/ViewImageLink' - -describe('view image link menu', () => { - const menu = new ViewImageLink() - let editor: any - let startLocation: any - - const src = 'https://www.wangeditor.com/imgs/logo.png' - const alt = 'logo' - const href = 'https://www.wangeditor.com/' - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('getValue and isDisabled', () => { - editor.select(startLocation) - expect(menu.getValue(editor)).toBe('') - expect(menu.isDisabled(editor)).toBeTruthy() - - const elem = { - type: 'image', - src, - alt, - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - expect(menu.getValue(editor)).toBe(href) - expect(menu.isDisabled(editor)).toBeFalsy() - }) - - // isActive 无逻辑,不用测试 - - // exec 逻辑简单,不用测试 -}) diff --git a/packages/basic-modules/__tests__/image/menu/width-menus.test.ts b/packages/basic-modules/__tests__/image/menu/width-menus.test.ts deleted file mode 100644 index 115ca83bc..000000000 --- a/packages/basic-modules/__tests__/image/menu/width-menus.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @description image width menus test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import Width30 from '../../../src/modules/image/menu/Width30' -import Width50 from '../../../src/modules/image/menu/Width50' -import Width100 from '../../../src/modules/image/menu/Width100' - -describe('image width menus', () => { - const width30Menu = new Width30() - const width50Menu = new Width50() - const width100Menu = new Width100() - - let editor: any - let startLocation: any - - const src = 'https://www.wangeditor.com/imgs/logo.png' - const alt = 'logo' - const href = 'https://www.wangeditor.com/' - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // getValue isActive 无逻辑,不用测试 - - it('is disabled', () => { - editor.deselect() - expect(width30Menu.isDisabled(editor)).toBeTruthy() - expect(width50Menu.isDisabled(editor)).toBeTruthy() - expect(width100Menu.isDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(width30Menu.isDisabled(editor)).toBeTruthy() - expect(width50Menu.isDisabled(editor)).toBeTruthy() - expect(width100Menu.isDisabled(editor)).toBeTruthy() - - const elem = { - type: 'image', - src, - alt, - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - expect(width30Menu.isDisabled(editor)).toBeFalsy() - expect(width50Menu.isDisabled(editor)).toBeFalsy() - expect(width100Menu.isDisabled(editor)).toBeFalsy() - }) - - it('exec', () => { - editor.select(startLocation) - const elem = { - type: 'image', - src, - alt, - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - - width30Menu.exec(editor, '') - const image1 = editor.getElemsByTypePrefix('image')[0] - expect(image1.style.width).toBe('30%') - expect(image1.style.height).toBe('') - - width50Menu.exec(editor, '') - const image2 = editor.getElemsByTypePrefix('image')[0] - expect(image2.style.width).toBe('50%') - expect(image2.style.height).toBe('') - - width100Menu.exec(editor, '') - const image3 = editor.getElemsByTypePrefix('image')[0] - expect(image3.style.width).toBe('100%') - expect(image3.style.height).toBe('') - }) -}) diff --git a/packages/basic-modules/__tests__/image/parse-html.test.ts b/packages/basic-modules/__tests__/image/parse-html.test.ts deleted file mode 100644 index 4a2436156..000000000 --- a/packages/basic-modules/__tests__/image/parse-html.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseHtmlConf } from '../../src/modules/image/parse-elem-html' - -describe('image - parse html', () => { - const editor = createEditor() - - it('parse html', () => { - const $img = $( - 'hello' - ) - - // match selector - expect($img[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseHtmlConf.parseElemHtml($img[0], [], editor) - expect(res).toEqual({ - type: 'image', - src: 'hello.png', - alt: 'hello', - href: 'http://localhost/', - style: { - width: '10px', - height: '5px', - }, - children: [{ text: '' }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/image/plugin.test.ts b/packages/basic-modules/__tests__/image/plugin.test.ts deleted file mode 100644 index 4cae95157..000000000 --- a/packages/basic-modules/__tests__/image/plugin.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @description image plugin test - * @author wangfupeng - */ - -import createEditor from '../../../../tests/utils/create-editor' -import withImage from '../../src/modules/image/plugin' - -describe('image plugin', () => { - const editor = withImage(createEditor()) - const elem = { type: 'image', children: [{ text: '' }] } - - it('image is inline', () => { - expect(editor.isInline(elem)).toBeTruthy() - }) - - it('image is void', () => { - expect(editor.isVoid(elem)).toBeTruthy() - }) -}) diff --git a/packages/basic-modules/__tests__/image/render-elem.test.ts b/packages/basic-modules/__tests__/image/render-elem.test.ts deleted file mode 100644 index ae015d06a..000000000 --- a/packages/basic-modules/__tests__/image/render-elem.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @description image - render elem test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import { renderImageConf } from '../../src/modules/image/render-elem' -import createEditor from '../../../../tests/utils/create-editor' - -describe('image render elem', () => { - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor.clear() - editor.destroy() - editor = null - startLocation = null - }) - - it('render image - unselected image', () => { - expect(renderImageConf.type).toBe('image') - - const src = 'https://www.wangeditor.com/imgs/logo.png' - const href = 'https://www.wangeditor.com/' - const elem = { - type: 'image', - src, - alt: 'logo', - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - - const containerVnode = renderImageConf.renderElem(elem, null, editor) as any - expect(containerVnode.sel).toBe('div') - expect(containerVnode.data.className).toBe('w-e-image-container') - expect(containerVnode.data.style.width).toBe('100') - expect(containerVnode.data.style.height).toBe('80') - - const imageVnode = containerVnode.children[0] as any - expect(imageVnode.sel).toBe('img') - expect(imageVnode.data.src).toBe(src) - expect(imageVnode.data['data-href']).toBe(href) - }) - - it('render image - selected image', () => { - const src = 'https://www.wangeditor.com/imgs/logo.png' - const href = 'https://www.wangeditor.com/' - const elem = { - type: 'image', - src, - alt: 'logo', - href, - style: { width: '100', height: '80' }, - children: [{ text: '' }], // void node 必须包含一个空 text - } - - editor.select(startLocation) - editor.insertNode(elem) // 插入图片 - editor.select({ - path: [0, 1, 0], // 选中图片 - offset: 0, - }) - - const containerVnode = renderImageConf.renderElem(elem, null, editor) as any - expect(containerVnode.sel).toBe('div') - expect(containerVnode.data.className.indexOf('w-e-selected-image-container')).toBeGreaterThan(0) - expect(containerVnode.children.length).toBe(5) // image + 4 个拖拽触手 - }) -}) diff --git a/packages/basic-modules/__tests__/indent/menu/decrease-indent-menu.test.ts b/packages/basic-modules/__tests__/indent/menu/decrease-indent-menu.test.ts deleted file mode 100644 index d5fc7cbf1..000000000 --- a/packages/basic-modules/__tests__/indent/menu/decrease-indent-menu.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @description decrease indent menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import DecreaseIndentMenu from '../../../src/modules/indent/menu/DecreaseIndentMenu' - -describe('decrease indent menu', () => { - let editor: any - let startLocation: any - - const menu = new DecreaseIndentMenu() - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('is disabled', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeTruthy() // 没有 indent 则 disabled - - Transforms.setNodes(editor, { type: 'header1', children: [] }) - expect(menu.isDisabled(editor)).toBeTruthy() // 没有 indent 则 disabled - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(menu.isDisabled(editor)).toBeTruthy() // 除了 p header 之外,其他 type 不可用 indent - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - // isActive 不用测试 - - // getValue 在 increase menu 已测试过 - - it('exec', () => { - editor.select(startLocation) - Transforms.setNodes(editor, { type: 'paragraph', indent: '2em', children: [] }) - - expect(menu.isDisabled(editor)).toBeFalsy() // 有 indent 则取消 disabled - - menu.exec(editor, '') - expect(menu.getValue(editor)).toBe('') - }) -}) diff --git a/packages/basic-modules/__tests__/indent/menu/increase-indent-menu.test.ts b/packages/basic-modules/__tests__/indent/menu/increase-indent-menu.test.ts deleted file mode 100644 index cccb46d8e..000000000 --- a/packages/basic-modules/__tests__/indent/menu/increase-indent-menu.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @description increase indent menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import IncreaseIndentMenu from '../../../src/modules/indent/menu/IncreaseIndentMenu' - -describe('increase indent menu', () => { - let editor: any - let startLocation: any - - const menu = new IncreaseIndentMenu() - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('is disabled', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - Transforms.setNodes(editor, { type: 'header1', children: [] }) - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(menu.isDisabled(editor)).toBeTruthy() // 除了 p header 之外,其他 type 不可用 indent - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - // isActive 不用测试 - - it('exec and getValue', () => { - editor.select(startLocation) - expect(menu.getValue(editor)).toBe('') - - menu.exec(editor, '') - expect(menu.getValue(editor)).toBe('2em') - }) - - it('indent value', () => { - editor.insertNode({ - type: 'paragraph', - children: [{ fontSize: '18px', text: 'text1' } as any], - }) - - menu.exec(editor, '') - - expect(menu.getValue(editor)).toBe('36px') - }) -}) diff --git a/packages/basic-modules/__tests__/indent/parse-html.test.ts b/packages/basic-modules/__tests__/indent/parse-html.test.ts deleted file mode 100644 index fe1354a30..000000000 --- a/packages/basic-modules/__tests__/indent/parse-html.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseStyleHtml } from '../../src/modules/indent/parse-style-html' -import { preParseHtmlConf } from '../../src/modules/indent/pre-parse-html' - -describe('indent - parse style', () => { - const editor = createEditor() - - it('parse style', () => { - const $p = $('

') - const paragraph = { type: 'paragraph', children: [{ text: 'hello' }] } - - // parse - const res = parseStyleHtml($p[0], paragraph, editor) - expect(res).toEqual({ - type: 'paragraph', - indent: '2em', - children: [{ text: 'hello' }], - }) - }) -}) - -describe('indent - pre parse html', () => { - it('pre parse', () => { - expect(preParseHtmlConf.selector).toBe('p,h1,h2,h3,h4,h5') - - const $p = $('

') - - // parse - const res = preParseHtmlConf.preParseHtml($p[0]) - expect((res as HTMLParagraphElement).style.textIndent).toBe('2em') - }) - - it('pre parse with px unit', () => { - expect(preParseHtmlConf.selector).toBe('p,h1,h2,h3,h4,h5') - - const $p = $('

') - - // parse - const res = preParseHtmlConf.preParseHtml($p[0]) - expect((res as HTMLParagraphElement).style.textIndent).toBe('2em') - }) -}) diff --git a/packages/basic-modules/__tests__/indent/render-text-style.test.tsx b/packages/basic-modules/__tests__/indent/render-text-style.test.tsx deleted file mode 100644 index 910ff58f3..000000000 --- a/packages/basic-modules/__tests__/indent/render-text-style.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @description indent - render text style - * @author wangfupeng - */ - -import { jsx } from 'snabbdom' -import { renderStyle } from '../../src/modules/indent/render-style' - -describe('indent - render text style', () => { - it('render text style', () => { - const indent = '2em' - const elem = { type: 'paragraph', indent, children: [] } - const vnode =

hello

- - // @ts-ignore - const newVnode = renderStyle(elem, vnode) - // @ts-ignore - expect(newVnode.data.style.textIndent).toBe(indent) - }) -}) diff --git a/packages/basic-modules/__tests__/indent/text-style-to-html.test.ts b/packages/basic-modules/__tests__/indent/text-style-to-html.test.ts deleted file mode 100644 index 2c40a0e17..000000000 --- a/packages/basic-modules/__tests__/indent/text-style-to-html.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @description indent - text style to html test - * @author wangfupeng - */ - -import { styleToHtml } from '../../src/modules/indent/style-to-html' - -describe('indent - text style to html', () => { - it('text style to html', () => { - const indent = '2em' - const elem = { type: 'paragraph', indent, children: [] } - const html = styleToHtml(elem, '

hello

') - expect(html).toBe(`

hello

`) - }) -}) diff --git a/packages/basic-modules/__tests__/justify/menus.test.ts b/packages/basic-modules/__tests__/justify/menus.test.ts deleted file mode 100644 index 8101df1fc..000000000 --- a/packages/basic-modules/__tests__/justify/menus.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @description justify menus test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import JustifyCenterMenu from '../../src/modules/justify/menu/JustifyCenterMenu' -import JustifyJustifyMenu from '../../src/modules/justify/menu/JustifyJustifyMenu' -import JustifyLeftMenu from '../../src/modules/justify/menu/JustifyLeftMenu' -import JustifyRightMenu from '../../src/modules/justify/menu/JustifyRightMenu' - -describe('justify menus', () => { - let editor: any - let startLocation: any - - const centerMenu = new JustifyCenterMenu() - const justifyMenu = new JustifyJustifyMenu() - const leftMenu = new JustifyLeftMenu() - const rightMenu = new JustifyRightMenu() - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - // getValue getActive 不需要测试 - - it('is disabled', () => { - editor.deselect() - expect(centerMenu.isDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(centerMenu.isDisabled(editor)).toBeFalsy() - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(centerMenu.isDisabled(editor)).toBeTruthy() - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - it('exec', () => { - editor.select(startLocation) - - centerMenu.exec(editor, '') - const p1 = editor.getElemsByTypePrefix('paragraph')[0] - expect(p1.textAlign).toBe('center') - - justifyMenu.exec(editor, '') - const p2 = editor.getElemsByTypePrefix('paragraph')[0] - expect(p2.textAlign).toBe('justify') - - leftMenu.exec(editor, '') - const p3 = editor.getElemsByTypePrefix('paragraph')[0] - expect(p3.textAlign).toBe('left') - - rightMenu.exec(editor, '') - const p4 = editor.getElemsByTypePrefix('paragraph')[0] - expect(p4.textAlign).toBe('right') - }) -}) diff --git a/packages/basic-modules/__tests__/justify/parse-html.test.ts b/packages/basic-modules/__tests__/justify/parse-html.test.ts deleted file mode 100644 index 56f398983..000000000 --- a/packages/basic-modules/__tests__/justify/parse-html.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseStyleHtml } from '../../src/modules/justify/parse-style-html' - -describe('text align - parse style', () => { - const editor = createEditor() - - it('parse style', () => { - const $p = $('

') - const paragraph = { type: 'paragraph', children: [{ text: 'hello' }] } - - // parse - const res = parseStyleHtml($p[0], paragraph, editor) - expect(res).toEqual({ - type: 'paragraph', - textAlign: 'center', - children: [{ text: 'hello' }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/justify/render-text-style.test.tsx b/packages/basic-modules/__tests__/justify/render-text-style.test.tsx deleted file mode 100644 index b9e03b994..000000000 --- a/packages/basic-modules/__tests__/justify/render-text-style.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @description justify - render text style test - * @author wangfupeng - */ - -import { jsx } from 'snabbdom' -import { renderStyle } from '../../src/modules/justify/render-style' - -describe('justify - render text style', () => { - it('render text style', () => { - const elem = { type: 'paragraph', textAlign: 'center', children: [] } - const vnode = hello - // @ts-ignore 忽略 vnode 格式 - const newVnode = renderStyle(elem, vnode) - // @ts-ignore 忽略 vnode 格式 - expect(newVnode.data.style?.textAlign).toBe('center') - }) -}) diff --git a/packages/basic-modules/__tests__/justify/text-style-to-html.test.ts b/packages/basic-modules/__tests__/justify/text-style-to-html.test.ts deleted file mode 100644 index 53037f411..000000000 --- a/packages/basic-modules/__tests__/justify/text-style-to-html.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @description justify - text style to html test - * @author wangfupeng - */ - -import { styleToHtml } from '../../src/modules/justify/style-to-html' - -describe('justify text-style-to-html', () => { - it('text style to html', () => { - const elem = { type: 'paragraph', textAlign: 'center', children: [] } - const html = styleToHtml(elem, 'hello') - expect(html).toBe('hello') - }) -}) diff --git a/packages/basic-modules/__tests__/line-height/line-height-menu.test.ts b/packages/basic-modules/__tests__/line-height/line-height-menu.test.ts deleted file mode 100644 index 823e93ff6..000000000 --- a/packages/basic-modules/__tests__/line-height/line-height-menu.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @description line-height menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import LineHeightMenu from '../../src/modules/line-height/menu/LineHeightMenu' - -describe('line-height menu', () => { - let editor: any - let startLocation: any - const menu = new LineHeightMenu() - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get options', () => { - editor.select(startLocation) - - const options = menu.getOptions(editor) - expect(options.length).toBeGreaterThan(0) - - // 默认选中 空 - const selectedEmptyOne = options.some(opt => opt.value === '' && opt.selected) - expect(selectedEmptyOne).toBe(true) - }) - - // isActive 返回 false ,不用测试 - - it('get value', () => { - editor.select(startLocation) - expect(menu.getValue(editor)).toBe('') - - // 设置 lineHeight - Transforms.setNodes(editor, { lineHeight: '1.5' }, { mode: 'highest' }) - expect(menu.getValue(editor)).toBe('1.5') - }) - - it('is disable', () => { - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - Transforms.setNodes(editor, { type: 'header1' }) - expect(menu.isDisabled(editor)).toBeFalsy() - Transforms.setNodes(editor, { type: 'blockquote' }) - expect(menu.isDisabled(editor)).toBeFalsy() - Transforms.setNodes(editor, { type: 'list-item' }) - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] }) - expect(menu.isDisabled(editor)).toBeTruthy() - // Transforms.removeNodes(editor, { mode: 'highest' }) // 移除 pre/code - }) - - it('exec', () => { - editor.select(startLocation) - menu.exec(editor, '1.5') - expect(menu.getValue(editor)).toBe('1.5') - }) -}) diff --git a/packages/basic-modules/__tests__/line-height/parse-html.test.ts b/packages/basic-modules/__tests__/line-height/parse-html.test.ts deleted file mode 100644 index 70c1f4c7f..000000000 --- a/packages/basic-modules/__tests__/line-height/parse-html.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseStyleHtml } from '../../src/modules/line-height/parse-style-html' - -describe('line height - parse style', () => { - const editor = createEditor() - - it('parse style', () => { - const $p = $('

') - const paragraph = { type: 'paragraph', children: [{ text: 'hello' }] } - - // parse - const res = parseStyleHtml($p[0], paragraph, editor) - expect(res).toEqual({ - type: 'paragraph', - lineHeight: '2.5', - children: [{ text: 'hello' }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/line-height/render-text-style.test.tsx b/packages/basic-modules/__tests__/line-height/render-text-style.test.tsx deleted file mode 100644 index cf11d1acb..000000000 --- a/packages/basic-modules/__tests__/line-height/render-text-style.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @description line-height render text style test - * @author wangfupeng - */ - -import { jsx } from 'snabbdom' -import { renderStyle } from '../../src/modules/line-height/render-style' - -describe('line-height render-text-style', () => { - it('render text style', () => { - const elem = { type: 'paragraph', lineHeight: '1.5', children: [] } - const vnode = hello - // @ts-ignore 忽略 vnode 格式检查 - const newVnode = renderStyle(elem, vnode) - // @ts-ignore 忽略 vnode 格式检查 - expect(newVnode.data.style.lineHeight).toBe('1.5') - }) -}) diff --git a/packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts b/packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts deleted file mode 100644 index dee81e0b8..000000000 --- a/packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @description line-height text-style-to-html test - * @author wangfupeng - */ - -import { styleToHtml } from '../../src/modules/line-height/style-to-html' - -describe('line-height text-style-to-html', () => { - it('text style to html', () => { - const elem = { type: 'paragraph', lineHeight: '1.5', children: [] } - const html = styleToHtml(elem, 'hello') - expect(html).toBe('hello') - }) -}) diff --git a/packages/basic-modules/__tests__/link/elem-to-html.test.ts b/packages/basic-modules/__tests__/link/elem-to-html.test.ts deleted file mode 100644 index 9574de78c..000000000 --- a/packages/basic-modules/__tests__/link/elem-to-html.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @description link - elem to html test - * @author wangfupeng - */ - -import { linkToHtmlConf } from '../../src/modules/link/elem-to-html' - -describe('link elem to html', () => { - it('link to html', () => { - expect(linkToHtmlConf.type).toBe('link') - - const url = 'https://www.wangeditor.com/' - const target = '_blank' - const elem = { type: 'link', url, target, children: [] } - - const html = linkToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe(`
hello`) - }) -}) diff --git a/packages/basic-modules/__tests__/link/helper.test.ts b/packages/basic-modules/__tests__/link/helper.test.ts deleted file mode 100644 index b42203775..000000000 --- a/packages/basic-modules/__tests__/link/helper.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @description link module helper test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import { isMenuDisabled, insertLink, updateLink } from '../../src/modules/link/helper' - -describe('link module helper', () => { - let editor: any - let startLocation: any - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('menu disable', () => { - editor.deselect() - expect(isMenuDisabled(editor)).toBeTruthy() - - editor.select(startLocation) - expect(isMenuDisabled(editor)).toBeFalsy() - - editor.insertNode({ - type: 'link', - url: 'https://www.wangeditor.com/', - children: [{ text: 'xxx' }], - }) - expect(isMenuDisabled(editor)).toBeTruthy() // 选中 link ,则禁用 - - editor.clear() - editor.insertNode({ - type: 'pre', - children: [ - { - type: 'code', - children: [{ text: 'var' }], - }, - ], - }) - expect(isMenuDisabled(editor)).toBeTruthy() // 选中 code-block ,则禁用 - }) - - it('insert link with collapsed selection', async () => { - editor.select(startLocation) - - const url = 'https://www.wangeditor.com/' - await insertLink(editor, 'hello', url) - - const links = editor.getElemsByTypePrefix('link') - expect(links.length).toBe(1) - const linkElem = links[0] - expect(linkElem.url).toBe(url) - }) - - it('insert link with expand selection', async () => { - editor.select(startLocation) - editor.insertText('hello') - Transforms.move(editor, { - distance: 3, // 选中 3 个字母 - unit: 'character', - }) - editor.select([]) // 全选 - - const url = 'https://www.wangeditor.com/' - await insertLink(editor, 'hello', url) - - const links = editor.getElemsByTypePrefix('link') - expect(links.length).toBe(1) - const linkElem = links[0] - expect(linkElem.url).toBe(url) - }) - - it('update link', async () => { - editor.select(startLocation) - - const url = 'https://www.wangeditor.com/' - await insertLink(editor, 'hello', url) - - // 选区移动到 link 内部 - editor.select({ - path: [0, 1, 0], - offset: 3, - }) - - // 更新链接 - const newUrl = 'https://www.wangeditor.com/123' - await updateLink(editor, '', newUrl) - - const links = editor.getElemsByTypePrefix('link') - expect(links.length).toBe(1) - const linkElem = links[0] - expect(linkElem.url).toBe(newUrl) - }) -}) diff --git a/packages/basic-modules/__tests__/link/menu/edit-link-menu.test.ts b/packages/basic-modules/__tests__/link/menu/edit-link-menu.test.ts deleted file mode 100644 index d12ecbe24..000000000 --- a/packages/basic-modules/__tests__/link/menu/edit-link-menu.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @description edit link menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import EditLink from '../../../src/modules/link/menu/EditLink' - -describe('edit link menu', () => { - let editor: any - let startLocation: any - const menu = new EditLink() - - const linkNode = { - type: 'link', - url: 'https://www.wangeditor.com/', - children: [{ text: 'xxx' }], - } - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get value', () => { - editor.select(startLocation) - expect(menu.getValue(editor)).toBe('') - - editor.insertNode(linkNode) - editor.select({ - path: [0, 1, 0], // 选区定位到 link 内部 - offset: 1, - }) - expect(menu.getValue(editor)).toBe(linkNode.url) - }) - - it('is active', () => { - expect(menu.isActive(editor)).toBeFalsy() - }) - - it('is disable', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.insertNode(linkNode) - editor.select({ - path: [0, 1, 0], // 选区定位到 link 内部 - offset: 1, - }) - expect(menu.isDisabled(editor)).toBeFalsy() - }) - - it('get modal position node', () => { - editor.select(startLocation) - expect(menu.getModalPositionNode(editor)).toBeNull() - - editor.insertNode(linkNode) - editor.select({ - path: [0, 1, 0], // 选区定位到 link 内部 - offset: 1, - }) - const node = menu.getModalPositionNode(editor) as any - expect(node.type).toBe('link') - expect(node.url).toBe(linkNode.url) - }) - - it('get modal content elem', () => { - editor.select(startLocation) - const elem = menu.getModalContentElem(editor) - expect(elem.tagName).toBe('DIV') - }) -}) diff --git a/packages/basic-modules/__tests__/link/menu/insert-link-menu.test.ts b/packages/basic-modules/__tests__/link/menu/insert-link-menu.test.ts deleted file mode 100644 index b7ad64b5e..000000000 --- a/packages/basic-modules/__tests__/link/menu/insert-link-menu.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @description insert link menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import InsertLinkMenu from '../../../src/modules/link/menu/InsertLink' - -describe('insert link menu', () => { - const editor = createEditor() - const menu = new InsertLinkMenu() - const startLocation = Editor.start(editor, []) - - afterEach(() => { - editor.select(startLocation) - editor.clear() - editor.deselect() - }) - - it('get value', () => { - expect(menu.getValue(editor)).toBe('') - }) - - it('is active', () => { - expect(menu.isActive(editor)).toBeFalsy() - }) - - it('get modal position node', () => { - expect(menu.getModalPositionNode(editor)).toBeNull() - }) - - it('is disable', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - }) - - it('get modal content elem', () => { - const elem = menu.getModalContentElem(editor) - expect(elem.tagName).toBe('DIV') - }) -}) diff --git a/packages/basic-modules/__tests__/link/menu/unlink-menu.test.ts b/packages/basic-modules/__tests__/link/menu/unlink-menu.test.ts deleted file mode 100644 index 82ecc5a3c..000000000 --- a/packages/basic-modules/__tests__/link/menu/unlink-menu.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @description unlink menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import UnLink from '../../../src/modules/link/menu/UnLink' - -describe('unlink menu test', () => { - let editor: any - let startLocation: any - const menu = new UnLink() - - const linkNode = { - type: 'link', - url: 'https://www.wangeditor.com/', - children: [{ text: 'xxx' }], - } - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get value', () => { - expect(menu.getValue(editor)).toBe('') - }) - - it('is active', () => { - expect(menu.isActive(editor)).toBe(false) - }) - - it('is disable', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.insertNode(linkNode) - editor.select({ - path: [0, 1, 0], // 选区定位到 link 内部 - offset: 1, - }) - expect(menu.isDisabled(editor)).toBeFalsy() - }) - - it('exec', () => { - editor.select(startLocation) - editor.insertNode(linkNode) - editor.select({ - path: [0, 1, 0], // 选区定位到 link 内部 - offset: 1, - }) - - menu.exec(editor, '') - const links = editor.getElemsByTypePrefix('link') - expect(links.length).toBe(0) - }) -}) diff --git a/packages/basic-modules/__tests__/link/menu/view-link-menu.test.ts b/packages/basic-modules/__tests__/link/menu/view-link-menu.test.ts deleted file mode 100644 index 623c1f342..000000000 --- a/packages/basic-modules/__tests__/link/menu/view-link-menu.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @description view link menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import ViewLink from '../../../src/modules/link/menu/ViewLink' - -describe('view link menu', () => { - let editor: any - let startLocation: any - const menu = new ViewLink() - - const linkNode = { - type: 'link', - url: 'https://www.wangeditor.com/', - children: [{ text: 'xxx' }], - } - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get value', () => { - editor.select(startLocation) - expect(menu.getValue(editor)).toBe('') - - editor.insertNode(linkNode) - editor.select({ - path: [0, 1, 0], // 选区定位到 link 内部 - offset: 1, - }) - expect(menu.getValue(editor)).toBe(linkNode.url) - }) - - it('is active', () => { - expect(menu.isActive(editor)).toBe(false) - }) - - it('is disable', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.insertNode(linkNode) - editor.select({ - path: [0, 1, 0], // 选区定位到 link 内部 - offset: 1, - }) - expect(menu.isDisabled(editor)).toBeFalsy() - }) -}) diff --git a/packages/basic-modules/__tests__/link/parse-html.test.ts b/packages/basic-modules/__tests__/link/parse-html.test.ts deleted file mode 100644 index 20143712e..000000000 --- a/packages/basic-modules/__tests__/link/parse-html.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseHtmlConf } from '../../src/modules/link/parse-elem-html' - -describe('link - parse html', () => { - const editor = createEditor() - - it('without children', () => { - const $link = $('hello world') - - // match selector - expect($link[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseHtmlConf.parseElemHtml($link[0], [], editor) - expect(res).toEqual({ - type: 'link', - url: 'http://localhost/', - target: '_blank', - children: [{ text: 'hello world' }], - }) - }) - - it('with children', () => { - const $link = $('') - const children = [{ text: 'hello ' }, { text: 'world', bold: true }] - - // match selector - expect($link[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseHtmlConf.parseElemHtml($link[0], children, editor) - expect(res).toEqual({ - type: 'link', - url: 'http://localhost/', - target: '_blank', - children: [{ text: 'hello ' }, { text: 'world', bold: true }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/link/plugin.test.ts b/packages/basic-modules/__tests__/link/plugin.test.ts deleted file mode 100644 index 1c4cd44b2..000000000 --- a/packages/basic-modules/__tests__/link/plugin.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @description link plugin test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import withLink from '../../src/modules/link/plugin' -import createEditor from '../../../../tests/utils/create-editor' - -// 模拟 DataTransfer -class MyDataTransfer { - private values: object = {} - setData(type: string, value: string) { - this.values[type] = value - } - getData(type: string): string { - return this.values[type] - } -} - -describe('link plugin', () => { - const editor = withLink(createEditor()) - const startLocation = Editor.start(editor, []) - - it('link is inline elem', () => { - const elem = { type: 'link', children: [] } - expect(editor.isInline(elem)).toBeTruthy() - }) - - it('link insert data', done => { - const url = 'https://www.wangeditor.com/' - - const data = new MyDataTransfer() - data.setData('text/plain', url) - - editor.select(startLocation) - // @ts-ignore - editor.insertData(data) - - setTimeout(() => { - const links = editor.getElemsByTypePrefix('link') - expect(links.length).toBe(1) - const linkElem = links[0] as any - expect(linkElem.url).toBe(url) - done() - }) - }) -}) diff --git a/packages/basic-modules/__tests__/link/render-elem.test.ts b/packages/basic-modules/__tests__/link/render-elem.test.ts deleted file mode 100644 index d9e0e8082..000000000 --- a/packages/basic-modules/__tests__/link/render-elem.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @description link - render elem test - * @author wangfupeng - */ - -import createEditor from '../../../../tests/utils/create-editor' -import { renderLinkConf } from '../../src/modules/link/render-elem' - -describe('link render elem', () => { - const editor = createEditor() - - it('render elem', () => { - expect(renderLinkConf.type).toBe('link') - - const url = 'https://www.wangeditor.com/' - const target = '_blank' - const elem = { type: 'link', url, target, children: [] } - - const vnode = renderLinkConf.renderElem(elem, null, editor) as any - expect(vnode.sel).toBe('a') - expect(vnode.data.href).toBe(url) - expect(vnode.data.target).toBe(target) - }) -}) diff --git a/packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts b/packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts deleted file mode 100644 index dff797c1f..000000000 --- a/packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { html } from 'dom7' -/** - * @description paragraph - elem to html test - * @author wangfupeng - */ - -import { pToHtmlConf } from '../../src/modules/paragraph/elem-to-html' - -describe('paragraph - elem to html', () => { - it('paragraph to html', () => { - expect(pToHtmlConf.type).toBe('paragraph') - - const elem = { type: 'paragraph', children: [] } - const html = pToHtmlConf.elemToHtml(elem, 'hello') - expect(html).toBe('

hello

') - }) - - it('paragraph to html with empty children', () => { - expect(pToHtmlConf.type).toBe('paragraph') - - const elem = { type: 'paragraph', children: [] } - const html = pToHtmlConf.elemToHtml(elem, '') - expect(html).toBe('


') - }) -}) diff --git a/packages/basic-modules/__tests__/paragraph/parse-html.test.ts b/packages/basic-modules/__tests__/paragraph/parse-html.test.ts deleted file mode 100644 index af8e6d08d..000000000 --- a/packages/basic-modules/__tests__/paragraph/parse-html.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseParagraphHtmlConf } from '../../src/modules/paragraph/parse-elem-html' - -describe('paragraph - parse html', () => { - const editor = createEditor() - - it('without children', () => { - const $elem = $('

hello world

') - - // match selector - expect($elem[0].matches(parseParagraphHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseParagraphHtmlConf.parseElemHtml($elem[0], [], editor) - expect(res).toEqual({ - type: 'paragraph', - children: [{ text: 'hello world' }], - }) - }) - - it('with children', () => { - const $elem = $('

') - const children = [{ text: 'hello ' }, { text: 'world', bold: true }] - - // parse - const res = parseParagraphHtmlConf.parseElemHtml($elem[0], children, editor) - expect(res).toEqual({ - type: 'paragraph', - children: [{ text: 'hello ' }, { text: 'world', bold: true }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/paragraph/plugin.test.ts b/packages/basic-modules/__tests__/paragraph/plugin.test.ts deleted file mode 100644 index f5688d9d2..000000000 --- a/packages/basic-modules/__tests__/paragraph/plugin.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @description paragraph plugin test - * @author wangfupeng - */ - -import { Editor, Transforms, Point } from 'slate' -import { DomEditor, IDomEditor } from '@wangeditor/core' -import createEditor from '../../../../tests/utils/create-editor' -import withParagraph from '../../src/modules/paragraph/plugin' - -let editor: IDomEditor -let startLocation: Point -describe('paragraph plugin', () => { - beforeEach(() => { - editor = withParagraph(createEditor()) - startLocation = Editor.start(editor, []) - }) - - it('delete to clear text', () => { - editor.select(startLocation) - Transforms.setNodes(editor, { type: 'header1' }) // 设置 header - editor.deleteBackward('character') // 向后删除 - const selectedParagraph1 = DomEditor.getSelectedNodeByType(editor, 'paragraph') - expect(selectedParagraph1).not.toBeNull() // 执行删除后,header 变为 paragraph - - Transforms.setNodes(editor, { type: 'blockquote' }) // 设置 blockquote - editor.deleteForward('character') // 向前删除 - const selectedParagraph2 = DomEditor.getSelectedNodeByType(editor, 'paragraph') - expect(selectedParagraph2).not.toBeNull() // 执行删除后,header 变为 paragraph - }) -}) diff --git a/packages/basic-modules/__tests__/paragraph/render-elem.test.ts b/packages/basic-modules/__tests__/paragraph/render-elem.test.ts deleted file mode 100644 index e0ca77fc9..000000000 --- a/packages/basic-modules/__tests__/paragraph/render-elem.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @description paragraph render elem test - * @author wangfupeng - */ - -import createEditor from '../../../../tests/utils/create-editor' -import { renderParagraphConf } from '../../src/modules/paragraph/render-elem' - -describe('paragraph - render elem', () => { - const editor = createEditor() - - it('render paragraph', () => { - expect(renderParagraphConf.type).toBe('paragraph') - - const elem = { type: 'paragraph', children: [] } - const vnode = renderParagraphConf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('p') - }) -}) diff --git a/packages/basic-modules/__tests__/text-style/menu/clear-style-menu.test.ts b/packages/basic-modules/__tests__/text-style/menu/clear-style-menu.test.ts deleted file mode 100644 index d72ae0840..000000000 --- a/packages/basic-modules/__tests__/text-style/menu/clear-style-menu.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @description clear style menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import ClearStyleMenu from '../../../src/modules/text-style/menu/ClearStyleMenu' - -describe('clear style menu', () => { - let editor = createEditor() - const startLocation = Editor.start(editor, []) - const menu = new ClearStyleMenu() - - afterEach(() => { - editor.select(startLocation) - editor.clear() - }) - - it('exec', () => { - editor.select(startLocation) - editor.insertText('hello') - - editor.select([]) - editor.addMark('bold', true) - editor.addMark('italic', true) - - menu.exec(editor, '') // 清空样式 - - const marks = Editor.marks(editor) as any - expect(marks.bold).toBeUndefined() - expect(marks.italic).toBeUndefined() - }) -}) diff --git a/packages/basic-modules/__tests__/text-style/menu/menus.test.ts b/packages/basic-modules/__tests__/text-style/menu/menus.test.ts deleted file mode 100644 index 9a3cf4801..000000000 --- a/packages/basic-modules/__tests__/text-style/menu/menus.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @description style menus test - * @author wangfupeng - */ - -import { Editor, Transforms, Element } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import BoldMenu from '../../../src/modules/text-style/menu/BoldMenu' -import CodeMenu from '../../../src/modules/text-style/menu/CodeMenu' -import ItalicMenu from '../../../src/modules/text-style/menu/ItalicMenu' -import SubMenu from '../../../src/modules/text-style/menu/SubMenu' -import SupMenu from '../../../src/modules/text-style/menu/SupMenu' -import ThroughMenu from '../../../src/modules/text-style/menu/ThroughMenu' -import UnderlineMenu from '../../../src/modules/text-style/menu/UnderlineMenu' - -const MENU_INFO_LIST = [ - { mark: 'bold', menu: new BoldMenu() }, - { mark: 'code', menu: new CodeMenu() }, - { mark: 'italic', menu: new ItalicMenu() }, - { mark: 'sub', menu: new SubMenu() }, - { mark: 'sup', menu: new SupMenu() }, - { mark: 'through', menu: new ThroughMenu() }, - { mark: 'underline', menu: new UnderlineMenu() }, -] - -describe('text style menus', () => { - let editor = createEditor() - const startLocation = Editor.start(editor, []) - - afterEach(() => { - editor.select(startLocation) - editor.clear() - }) - - // getValue 已经被 isActive 覆盖 - - it('is active', () => { - MENU_INFO_LIST.forEach(info => { - const { mark, menu } = info - - editor.select(startLocation) - editor.clear() - editor.insertText('hello') - expect(menu.isActive(editor)).toBeFalsy() - - editor.select([]) - editor.addMark(mark, true) - expect(menu.isActive(editor)).toBeTruthy() - }) - }) - - it('is disable', () => { - MENU_INFO_LIST.forEach(info => { - const { mark, menu } = info - - editor.select(startLocation) - editor.clear() - editor.insertText('hello') - expect(menu.isDisabled(editor)).toBeFalsy() // 正常文字,不禁用 - - editor.insertNode({ - type: 'pre', - children: [ - { - type: 'code', - children: [{ text: 'var' }], - } as Element, - ], - } as Element) - expect(menu.isDisabled(editor)).toBeTruthy() // 选中代码块,禁用各个 menu - }) - }) - - it('exec', () => { - MENU_INFO_LIST.forEach(info => { - const { mark, menu } = info - - editor.select(startLocation) - editor.clear() - editor.insertText('hello') - editor.select([]) - - // 增加 mark - menu.exec(editor, false) - const marks1 = Editor.marks(editor) as any - expect(marks1[mark]).toBeTruthy() - - // 取消 mark - editor.select([]) - menu.exec(editor, true) - const marks2 = Editor.marks(editor) as any - expect(marks2[mark]).toBeUndefined() - }) - }) -}) diff --git a/packages/basic-modules/__tests__/text-style/parse-html.test.ts b/packages/basic-modules/__tests__/text-style/parse-html.test.ts deleted file mode 100644 index a828a26b1..000000000 --- a/packages/basic-modules/__tests__/text-style/parse-html.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -// import { $ } from 'dom7' -// import { parseStyleHtml } from '../../../../packages/basic-modules/src/modules/text-style/parse-style-html' - -describe('text style - parse style html', () => { - it('占位', () => { - expect(1 + 1).toBe(2) - }) - // TODO 执行以下代码会有 Dom7 一个怪异的 bug ,先暂且注释,后面再解决 wangfupeng 2022.01.17 - - // it('bold', () => { - // const $text = $('') - // const textNode = { text: 'hello' } - - // // parse style - // const res = parseStyleHtml($text[0], textNode) - // expect(res).toEqual({ - // text: 'hello', - // bold: true, - // }) - // }) - - // // italic underline... 等 -}) diff --git a/packages/basic-modules/__tests__/text-style/parse-style-html.test.ts b/packages/basic-modules/__tests__/text-style/parse-style-html.test.ts deleted file mode 100644 index c2516405c..000000000 --- a/packages/basic-modules/__tests__/text-style/parse-style-html.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { parseStyleHtml } from '../../src/modules/text-style/parse-style-html' -import $ from '../../src/utils/dom' -import createEditor from '../../../../tests/utils/create-editor' - -describe('parse style html', () => { - const editor = createEditor() - - it('it should return directly if give node that type is not text', () => { - const element = $('

') - const node = { type: 'paragraph', children: [] } - expect(parseStyleHtml(element[0], node, editor)).toEqual(node) - }) - - it('it should do nothing if give not exist element', () => { - const element = $('#text') - const node = { type: 'paragraph', children: [] } - expect(parseStyleHtml(element[0], node, editor)).toEqual(node) - }) - - it('it should set bold property for node if give strong element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, bold: true }) - }) - - it('it should set bold property for node if give b element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, bold: true }) - }) - - it('it should set italic property for node if give i element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, italic: true }) - }) - - it('it should set italic property for node if give em element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, italic: true }) - }) - - it('it should set underline property for node if give u element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, underline: true }) - }) - - it('it should set through property for node if give s element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, through: true }) - }) - - it('it should set through property for node if give strike element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, through: true }) - }) - - it('it should set sub property for node if give sub element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, sub: true }) - }) - - it('it should set sup property for node if give sup element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, sup: true }) - }) - - it('it should set code property for node if give code element', () => { - const element = $('') - const node = { text: 'text' } - expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, code: true }) - }) -}) diff --git a/packages/basic-modules/__tests__/text-style/text-style.test.tsx b/packages/basic-modules/__tests__/text-style/text-style.test.tsx deleted file mode 100644 index d8bf265fd..000000000 --- a/packages/basic-modules/__tests__/text-style/text-style.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @description text style test - * @author wangfupeng - */ - -import { jsx } from 'snabbdom' -import { renderStyle } from '../../src/modules/text-style/render-style' -import { StyledText } from '../../src/modules/text-style/custom-types' - -describe('text style - render text style', () => { - it('render text style', () => { - const vnode = hello - let newVnode - - const textNode: StyledText = { text: '' } - - textNode.bold = true - // @ts-ignore 忽略 vnode 格式 - newVnode = renderStyle(textNode, vnode) - expect(newVnode.sel).toBe('strong') - - textNode.code = true - // @ts-ignore 忽略 vnode 格式 - newVnode = renderStyle(textNode, vnode) - expect(newVnode.sel).toBe('code') - - textNode.italic = true - // @ts-ignore 忽略 vnode 格式 - newVnode = renderStyle(textNode, vnode) - expect(newVnode.sel).toBe('em') - - textNode.underline = true - // @ts-ignore 忽略 vnode 格式 - newVnode = renderStyle(textNode, vnode) - expect(newVnode.sel).toBe('u') - - textNode.through = true - // @ts-ignore 忽略 vnode 格式 - newVnode = renderStyle(textNode, vnode) - expect(newVnode.sel).toBe('s') - - textNode.sub = true - // @ts-ignore 忽略 vnode 格式 - newVnode = renderStyle(textNode, vnode) - expect(newVnode.sel).toBe('sub') - - textNode.sup = true - // @ts-ignore 忽略 vnode 格式 - newVnode = renderStyle(textNode, vnode) - expect(newVnode.sel).toBe('sup') - }) -}) diff --git a/packages/basic-modules/__tests__/text-style/text-to-html.test.ts b/packages/basic-modules/__tests__/text-style/text-to-html.test.ts deleted file mode 100644 index a1127f631..000000000 --- a/packages/basic-modules/__tests__/text-style/text-to-html.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @description text to html test - * @author wangfupeng - */ - -import { styleToHtml } from '../../src/modules/text-style/style-to-html' - -describe('text style - text to html', () => { - it('text to html', () => { - const textNode = { - text: '', - bold: true, - italic: true, - underline: true, - code: true, - through: true, - sub: true, - sup: true, - } - - const html1 = styleToHtml(textNode, 'hello') - expect(html1).toBe( - 'hello' - ) - - const html2 = styleToHtml(textNode, 'world') - expect(html2).toBe( - 'world' - ) - }) -}) diff --git a/packages/basic-modules/__tests__/todo/elem-to-html.test.ts b/packages/basic-modules/__tests__/todo/elem-to-html.test.ts deleted file mode 100644 index a26252f2a..000000000 --- a/packages/basic-modules/__tests__/todo/elem-to-html.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @description todo elem to html test - * @author wangfupeng - */ - -import { todoToHtmlConf } from '../../src/modules/todo/elem-to-html' - -describe('todo - elem to html', () => { - it('todo elem to html', () => { - expect(todoToHtmlConf.type).toBe('todo') - - const todoNode1 = { - type: 'todo', - checked: true, - children: [{ text: '' }], - } - const html1 = todoToHtmlConf.elemToHtml(todoNode1, 'hello') - expect(html1).toBe( - `
hello
` - ) - - const todoNode2 = { - type: 'todo', - checked: false, - children: [{ text: '' }], - } - const html2 = todoToHtmlConf.elemToHtml(todoNode2, 'hello') - expect(html2).toBe(`
hello
`) - }) -}) diff --git a/packages/basic-modules/__tests__/todo/menu/todo-menu.test.ts b/packages/basic-modules/__tests__/todo/menu/todo-menu.test.ts deleted file mode 100644 index ddbd82d1b..000000000 --- a/packages/basic-modules/__tests__/todo/menu/todo-menu.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * @description todo-menu test - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import createEditor from '../../../../../tests/utils/create-editor' -import TodoMenu from '../../../src/modules/todo/menu/Todo' - -describe('todo-menu', () => { - let editor: any - let startLocation: any - const menu = new TodoMenu() - - beforeEach(() => { - editor = createEditor() - startLocation = Editor.start(editor, []) - }) - - afterEach(() => { - editor = null - startLocation = null - }) - - it('get value', () => { - expect(menu.getValue(editor)).toBe('') - }) - - it('is active', () => { - editor.select(startLocation) - expect(menu.isActive(editor)).toBeFalsy() - - // @ts-ignore - Transforms.setNodes(editor, { type: 'todo' }) - expect(menu.isActive(editor)).toBeTruthy() - }) - - it('is disable - paragraph and todo', () => { - editor.select(startLocation) - expect(menu.isDisabled(editor)).toBeFalsy() - - // @ts-ignore - Transforms.setNodes(editor, { type: 'todo' }) - expect(menu.isDisabled(editor)).toBeFalsy() - }) - - it('is disable - list', () => { - editor.select(startLocation) - editor.insertNode({ - type: 'bulleted-list', - children: [ - { - type: 'list-item', - children: [{ text: 'hello' }], - }, - ], - }) - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('is disable - table', () => { - editor.select(startLocation) - editor.insertNode({ - type: 'table', - children: [ - { - type: 'table-row', - children: [ - { - type: 'table-cell', - children: [{ text: 'hello' }], - }, - ], - }, - ], - }) - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('is disable - pre/code', () => { - editor.select(startLocation) - editor.insertNode({ - type: 'pre', - children: [ - { - type: 'code', - children: [{ text: 'hello' }], - }, - ], - }) - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('exec - paragraph to todo', () => { - editor.select(startLocation) - menu.exec(editor, '') - - const todoElems = editor.getElemsByType('todo') - expect(todoElems.length).toBe(1) - }) - - it('exec - todo to paragraph', () => { - editor.select(startLocation) - // @ts-ignore - Transforms.setNodes(editor, { type: 'todo' }) - menu.exec(editor, '') - - const todoElems = editor.getElemsByType('todo') - expect(todoElems.length).toBe(0) - }) -}) diff --git a/packages/basic-modules/__tests__/todo/parse-html.test.ts b/packages/basic-modules/__tests__/todo/parse-html.test.ts deleted file mode 100644 index ecf078dc2..000000000 --- a/packages/basic-modules/__tests__/todo/parse-html.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @description todo parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../../tests/utils/create-editor' -import { parseHtmlConf } from '../../src/modules/todo/parse-elem-html' - -describe('todo - parse html', () => { - const editor = createEditor() - - it('with children, checked', () => { - const $todo = $('
hello
') - - // match selector - expect($todo[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseHtmlConf.parseElemHtml($todo[0], [], editor) - expect(res).toEqual({ - type: 'todo', - checked: true, - children: [{ text: 'hello' }], - }) - }) - - it('without children, unchecked', () => { - const $todo = $('
') - const children = [{ text: 'hello ' }, { text: 'world', bold: true }] - - // match selector - expect($todo[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - const res = parseHtmlConf.parseElemHtml($todo[0], children, editor) - expect(res).toEqual({ - type: 'todo', - checked: false, - children: [{ text: 'hello ' }, { text: 'world', bold: true }], - }) - }) -}) diff --git a/packages/basic-modules/__tests__/todo/plugin.test.ts b/packages/basic-modules/__tests__/todo/plugin.test.ts deleted file mode 100644 index 56aba6277..000000000 --- a/packages/basic-modules/__tests__/todo/plugin.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @description todo plugin test - * @author wangfupeng - */ - -import withTodo from '../../src/modules/todo/plugin' -import createEditor from '../../../../tests/utils/create-editor' - -describe('todo - plugin', () => { - it('delete backward', () => { - const editor = withTodo( - createEditor({ - content: [{ type: 'todo', children: [{ text: '' }] }], - }) - ) - editor.select({ - path: [0, 0], - offset: 0, - }) - - const todoElems1 = editor.getElemsByType('todo') - expect(todoElems1.length).toBe(1) - - editor.deleteBackward('character') - - const todoElems2 = editor.getElemsByType('todo') - expect(todoElems2.length).toBe(0) - }) -}) diff --git a/packages/basic-modules/__tests__/todo/pre-parse-html.test.ts b/packages/basic-modules/__tests__/todo/pre-parse-html.test.ts deleted file mode 100644 index a83d2701f..000000000 --- a/packages/basic-modules/__tests__/todo/pre-parse-html.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @description todo pre-parse html - * @author wangfupeng - */ - -import { $ } from 'dom7' -import { preParseHtmlConf } from '../../src/modules/todo/pre-parse-html' - -describe('todo - pre-parse html', () => { - it('pre-parse html', () => { - // v4 todo html 格式 - const $ul = $( - '
  • hello world
' - ) - - // match selector - expect($ul[0].matches(preParseHtmlConf.selector)).toBeTruthy() - - // parse - const res = preParseHtmlConf.preParseHtml($ul[0]) - expect(res.outerHTML).toBe( - '
hello world
' - ) - }) -}) diff --git a/packages/basic-modules/__tests__/todo/render-elem.test.ts b/packages/basic-modules/__tests__/todo/render-elem.test.ts deleted file mode 100644 index b63a69375..000000000 --- a/packages/basic-modules/__tests__/todo/render-elem.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @description todo render elem - * @author wangfupeng - */ - -import createEditor from '../../../../tests/utils/create-editor' -import { renderTodoConf } from '../../src/modules/todo/render-elem' - -describe('todo - render elem', () => { - const editor = createEditor() - - it('render elem', () => { - expect(renderTodoConf.type).toBe('todo') - - const todo = { type: 'todo', checked: true, children: [{ text: '' }] } - const vnode = renderTodoConf.renderElem(todo, null, editor) as any - expect(vnode.sel).toBe('div') - expect(vnode.children.length).toBe(2) - - const spanForInput = vnode.children[0] - expect(spanForInput.sel).toBe('span') - expect(spanForInput.data.contentEditable).toBe(false) - - const input = spanForInput.children[0] - expect(input.sel).toBe('input') - expect(input.data.type).toBe('checkbox') - expect(input.data.checked).toBe(true) - expect(typeof input.data.on.change).toBe('function') - }) -}) diff --git a/packages/basic-modules/__tests__/undo-redo/redo-menu.test.ts b/packages/basic-modules/__tests__/undo-redo/redo-menu.test.ts deleted file mode 100644 index 4aac14179..000000000 --- a/packages/basic-modules/__tests__/undo-redo/redo-menu.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @description redo menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import RedoMenu from '../../src/modules/undo-redo/menu/RedoMenu' - -describe('redo menu', () => { - const editor = createEditor() - const menu = new RedoMenu() - const location = Editor.start(editor, []) // 选区位置 - - it('tag', () => { - expect(menu.tag).toBe('button') - }) - - it('get value', () => { - expect(menu.getValue(editor)).toBe('') - }) - - it('is active', () => { - expect(menu.isActive(editor)).toBeFalsy() - }) - - it('is disable', () => { - // 有选区 - editor.select(location) - expect(menu.isDisabled(editor)).toBeFalsy() - - // 无选区 - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('exec', () => { - const text = editor.getText() - - editor.select(location) - editor.insertText('xxx') - if (typeof editor.undo === 'function') { - editor.undo() - } - menu.exec(editor, '') - - const newText = editor.getText() - expect(newText).toBe(text + 'xxx') - }) -}) diff --git a/packages/basic-modules/__tests__/undo-redo/undo-menu.test.ts b/packages/basic-modules/__tests__/undo-redo/undo-menu.test.ts deleted file mode 100644 index 6bc0ed985..000000000 --- a/packages/basic-modules/__tests__/undo-redo/undo-menu.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @description undo menu test - * @author wangfupeng - */ - -import { Editor } from 'slate' -import createEditor from '../../../../tests/utils/create-editor' -import UndoMenu from '../../src/modules/undo-redo/menu/UndoMenu' - -describe('undo menu', () => { - const editor = createEditor() - const menu = new UndoMenu() - const location = Editor.start(editor, []) // 选区位置 - - it('tag', () => { - expect(menu.tag).toBe('button') - }) - - it('get value', () => { - expect(menu.getValue(editor)).toBe('') - }) - - it('is active', () => { - expect(menu.isActive(editor)).toBeFalsy() - }) - - it('is disable', () => { - // 有选区 - editor.select(location) - expect(menu.isDisabled(editor)).toBeFalsy() - - // 无选区 - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('exec', () => { - const text = editor.getText() - - editor.select(location) - editor.insertText('xxx') - menu.exec(editor, '') - - const newText = editor.getText() - expect(newText).toBe(text) - }) -}) diff --git a/packages/basic-modules/package.json b/packages/basic-modules/package.json deleted file mode 100644 index f6ce8bc75..000000000 --- a/packages/basic-modules/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@wangeditor/basic-modules", - "version": "1.1.7", - "description": "wangEditor basic modules", - "author": "wangfupeng1988 ", - "contributors": [], - "homepage": "https://github.com/wangeditor-team/wangEditor#readme", - "license": "MIT", - "types": "dist/basic-modules/src/index.d.ts", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "browser": { - "./dist/index.js": "./dist/index.js", - "./dist/index.esm.js": "./dist/index.esm.js" - }, - "directories": { - "lib": "dist", - "test": "__tests__" - }, - "files": [ - "dist" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.com/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wangeditor-team/wangEditor.git" - }, - "scripts": { - "test": "jest", - "test-c": "jest --coverage", - "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js", - "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", - "build": "cross-env NODE_ENV=production rollup -c rollup.config.js", - "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js", - "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js" - }, - "bugs": { - "url": "https://github.com/wangeditor-team/wangEditor/issues" - }, - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.throttle": "^4.1.1", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - }, - "dependencies": { - "is-url": "^1.2.4" - }, - "devDependencies": { - "@types/is-url": "^1.2.29" - } -} diff --git a/packages/basic-modules/rollup.config.js b/packages/basic-modules/rollup.config.js deleted file mode 100644 index 03d6485c2..000000000 --- a/packages/basic-modules/rollup.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' -import pkg from './package.json' - -const name = 'WangEditorBasicModules' - -const configList = [] - -// esm -const esmConf = createRollupConfig({ - output: { - file: pkg.module, - format: 'esm', - name, - }, -}) -configList.push(esmConf) - -// umd -const umdConf = createRollupConfig({ - output: { - file: pkg.main, - format: 'umd', - name, - }, -}) -configList.push(umdConf) - -export default configList diff --git a/packages/basic-modules/src/assets/blockquote.less b/packages/basic-modules/src/assets/blockquote.less deleted file mode 100644 index b33e62b0c..000000000 --- a/packages/basic-modules/src/assets/blockquote.less +++ /dev/null @@ -1,11 +0,0 @@ -@import "../../../vars.less"; - -.w-e-text-container [data-slate-editor] blockquote { - display: block; - border-left: 8px solid @textarea-selected-border-color; - padding: 10px 10px; - margin: 10px 0; - line-height: 1.5; - font-size: 100%; - background-color: @textarea-slight-bg-color; -} diff --git a/packages/basic-modules/src/assets/code-block.less b/packages/basic-modules/src/assets/code-block.less deleted file mode 100644 index 513adf1a8..000000000 --- a/packages/basic-modules/src/assets/code-block.less +++ /dev/null @@ -1,11 +0,0 @@ -@import "../../../vars.less"; - -.w-e-text-container [data-slate-editor] pre>code { - display: block; - border: 1px solid @textarea-slight-border-color; - border-radius: 4px 4px; - text-indent: 0; - background-color: @textarea-slight-bg-color; - padding: 10px; - font-size: @size; -} diff --git a/packages/basic-modules/src/assets/color.less b/packages/basic-modules/src/assets/color.less deleted file mode 100644 index 7d92f3de4..000000000 --- a/packages/basic-modules/src/assets/color.less +++ /dev/null @@ -1,42 +0,0 @@ -@import "../../../vars.less"; - -.w-e-panel-content-color { - list-style: none; - text-align: left; - width: 230px; - - li { - display: inline-block; - padding: 2px; - cursor: pointer; - border-radius: 3px 3px; - border: 1px solid @toolbar-bg-color; - - &:hover { - border-color: @toolbar-color; - } - - .color-block { - width: 17px; - height: 17px; - border: 1px solid @toolbar-border-color; - border-radius: 3px 3px; - } - } - - .active { - border-color: @toolbar-color; - } - - .clear { - width: 100%; - line-height: 1.5; - margin-bottom: 5px; - - svg { - width: 16px; - height: 16px; - margin-bottom: -4px; - } - } -} \ No newline at end of file diff --git a/packages/basic-modules/src/assets/divider.less b/packages/basic-modules/src/assets/divider.less deleted file mode 100644 index 569ae6660..000000000 --- a/packages/basic-modules/src/assets/divider.less +++ /dev/null @@ -1,18 +0,0 @@ -@import "../../../vars.less"; - -.w-e-textarea-divider { - padding: 20px 20px; - margin: 20px auto; - border-radius: 3px; - - // &:hover { - // background-color: @textarea-slight-bg-color; - // } - - hr { - display: block; - border: 0; - height: 1px; - background-color: @textarea-border-color; - } -} diff --git a/packages/basic-modules/src/assets/emotion.less b/packages/basic-modules/src/assets/emotion.less deleted file mode 100644 index 6adcdeda9..000000000 --- a/packages/basic-modules/src/assets/emotion.less +++ /dev/null @@ -1,19 +0,0 @@ -@import "../../../vars.less"; - -.w-e-panel-content-emotion { - list-style: none; - text-align: left; - width: 300px; - font-size: 20px; - - li { - display: inline-block; - padding: 0 5px; - cursor: pointer; - border-radius: 3px 3px; - - &:hover { - background-color: @textarea-slight-bg-color; - } - } -} \ No newline at end of file diff --git a/packages/basic-modules/src/assets/image.less b/packages/basic-modules/src/assets/image.less deleted file mode 100644 index a04501a14..000000000 --- a/packages/basic-modules/src/assets/image.less +++ /dev/null @@ -1,58 +0,0 @@ -@import "../../../vars.less"; - -// 拖拽,修改图片尺寸 -.w-e-text-container [data-slate-editor] { - .w-e-image-container { - display: inline-block; - margin: 0 3px; // 从 10px 改为 3px ,可规避 issue 4523 - - &:hover { - box-shadow: 0 0 0 2px @textarea-selected-border-color; - } - } - .w-e-selected-image-container { - position: relative; - overflow: hidden; - - .w-e-image-dragger { - width: 7px; - height: 7px; - background-color: @textarea-handler-bg-color; - position: absolute; - } - .left-top { - top: 0; - left: 0; - cursor: nwse-resize; - } - .right-top { - top: 0; - right: 0; - cursor: nesw-resize; - } - .left-bottom { - left: 0; - bottom: 0; - cursor: nesw-resize; - } - .right-bottom { - right: 0; - bottom: 0; - cursor: nwse-resize; - } - - // 选中之后,不需要 hover 效果 - &:hover { - box-shadow: none; - } - } -} - -// 禁用编辑器时,img hover 不要有样式 -.w-e-text-container [contenteditable="false"] { - .w-e-image-container { - &:hover { - box-shadow: none; - } - } -} \ No newline at end of file diff --git a/packages/basic-modules/src/assets/index.less b/packages/basic-modules/src/assets/index.less deleted file mode 100644 index 36f2eee2e..000000000 --- a/packages/basic-modules/src/assets/index.less +++ /dev/null @@ -1,8 +0,0 @@ -@import "simple-style.less"; -@import "color.less"; -@import "blockquote.less"; -@import "emotion.less"; -@import "divider.less"; -@import "blockquote.less"; -@import "code-block.less"; -@import "image.less"; diff --git a/packages/basic-modules/src/assets/simple-style.less b/packages/basic-modules/src/assets/simple-style.less deleted file mode 100644 index 5a5f01925..000000000 --- a/packages/basic-modules/src/assets/simple-style.less +++ /dev/null @@ -1,8 +0,0 @@ -@import "../../../vars.less"; - -.w-e-text-container [data-slate-editor] code { - font-family: monospace; - background-color: @textarea-slight-bg-color; - padding: 3px; - border-radius: 3px; -} diff --git a/packages/basic-modules/src/constants/icon-svg.ts b/packages/basic-modules/src/constants/icon-svg.ts deleted file mode 100644 index 50da4786c..000000000 --- a/packages/basic-modules/src/constants/icon-svg.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * @description icon svg - * @author wangfupeng - */ - -/** - * 【注意】svg 字符串的长度 ,否则会导致代码体积过大 - * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293 - * 找不到再从 iconfont.com 搜索 - */ - -// 加粗 -export const BOLD_SVG = - '' - -// 下划线 -export const UNDER_LINE_SVG = - '' - -// 斜体 -export const ITALIC_SVG = - '' - -// 删除线 -export const THROUGH_SVG = - '' - -// 代码 -export const CODE_SVG = - '' - -// 清除格式 -export const ERASER_SVG = - '' - -// 链接 -export const LINK_SVG = - '' - -// 取消链接 -export const UN_LINK_SVG = - '' - -// 编辑 -export const PENCIL_SVG = - '' - -// 外部(链接) -export const EXTERNAL_SVG = - '' - -// 标题 -export const HEADER_SVG = - '' - -// 字体颜色 -export const FONT_COLOR_SVG = - '' - -// 背景颜色 -export const BG_COLOR_SVG = - '' - -// 清空(颜色) -export const CLEAN_SVG = - '' - -// 图片 -export const IMAGE_SVG = - '' - -// 垃圾桶(删除) -export const TRASH_SVG = - '' - -// 引用 -export const QUOTE_SVG = - '' - -// 表情 -export const EMOTION_SVG = - '' - -// fontSize -export const FONT_SIZE_SVG = - '' - -// 字体 -export const FONT_FAMILY_SVG = - '' - -// 缩进 left -export const INDENT_LEFT_SVG = - '' - -// 缩进 right -export const INDENT_RIGHT_SVG = - '' - -// 左对齐 -export const JUSTIFY_LEFT_SVG = - '' - -// 右对齐 -export const JUSTIFY_RIGHT_SVG = - '' - -// 居中对齐 -export const JUSTIFY_CENTER_SVG = - '' - -// 两端对齐 -export const JUSTIFY_JUSTIFY_SVG = - '' - -// 行高 -export const LINE_HEIGHT_SVG = - '' - -// 撤销 -export const UNDO_SVG = - '' - -// 重做 -export const REDO_SVG = - '' - -// 分割线 -export const DIVIDER_SVG = - '' - -// 代码块 -export const CODE_BLOCK_SVG = - '' - -// 全屏 -export const FULL_SCREEN_SVG = - '' - -// 上标 -export const SUP_SVG = - '' - -// 下标 -export const SUB_SVG = - '' - -// checkbox -export const CHECK_BOX_SVG = - '' - -// 回车 -export const ENTER_SVG = - '' diff --git a/packages/basic-modules/src/index.ts b/packages/basic-modules/src/index.ts deleted file mode 100644 index 32d518b61..000000000 --- a/packages/basic-modules/src/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @description basic index - * @author wangfupeng - */ - -import './assets/index.less' - -// 配置多语言 -import './locale/index' - -import wangEditorParagraphModule from './modules/paragraph' -import wangEditorTextStyleModule from './modules/text-style' -import wangEditorHeaderModule from './modules/header' -import wangEditorColorModule from './modules/color' -import wangEditorLinkModule from './modules/link' -import wangEditorImageModule from './modules/image' -import wangEditorTodoModule from './modules/todo' -import wangEditorBlockQuoteModule from './modules/blockquote' -import wangEditorEmotionModule from './modules/emotion' -import wangEditorFontSizeAndFamilyModule from './modules/font-size-family' -import wangEditorIndentModule from './modules/indent' -import wangEditorJustifyModule from './modules/justify' -import wangEditorLineHeightModule from './modules/line-height' -import wangEditorUndoRedoModule from './modules/undo-redo' -import wangEditorDividerModule from './modules/divider' -import wangEditorCodeBlockModule from './modules/code-block' -import wangEditorFullScreenModule from './modules/full-screen' -import wangEditorCommonModule from './modules/common' - -export default [ - // text style - wangEditorTextStyleModule, - wangEditorColorModule, - wangEditorFontSizeAndFamilyModule, - - // elem style - wangEditorIndentModule, - wangEditorJustifyModule, - wangEditorLineHeightModule, - - // void node - wangEditorImageModule, - wangEditorDividerModule, - - // inline node - wangEditorEmotionModule, - wangEditorLinkModule, - - // block node —— 【注意】要放在 void-node 和 inline-node 后面!!! - wangEditorCodeBlockModule, - wangEditorBlockQuoteModule, - wangEditorHeaderModule, - wangEditorParagraphModule, - wangEditorTodoModule, - - // command - wangEditorUndoRedoModule, - wangEditorFullScreenModule, - wangEditorCommonModule, -] - -// 输出 image 操作,供 updateImageModule 使用 -export * from './modules/image/helper' diff --git a/packages/basic-modules/src/locale/en.ts b/packages/basic-modules/src/locale/en.ts deleted file mode 100644 index a88751311..000000000 --- a/packages/basic-modules/src/locale/en.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @description i18n en - * @author wangfupeng - */ - -export default { - // 通用的词 - common: { - ok: 'OK', - delete: 'Delete', - enter: 'Enter', - }, - - blockQuote: { - title: 'Quote', - }, - codeBlock: { - title: 'Code block', - }, - color: { - color: 'Font color', - bgColor: 'Back color', - default: 'Default color', - clear: 'Clear back color', - }, - divider: { - title: 'Divider', - }, - emotion: { - title: 'Emotion', - }, - fontSize: { - title: 'Font size', - default: 'Default', - }, - fontFamily: { - title: 'Font family', - default: 'Default', - }, - fullScreen: { - title: 'Full screen', - }, - header: { - title: 'Header', - text: 'Text', - }, - image: { - netImage: 'Net image', - delete: 'Delete image', - edit: 'Edit image', - viewLink: 'View link', - src: 'Image src', - desc: 'Description', - link: 'Image link', - }, - indent: { - decrease: 'Decrease', - increase: 'Increase', - }, - justify: { - left: 'Left', - right: 'Right', - center: 'Center', - justify: 'Justify', - }, - lineHeight: { - title: 'Line height', - default: 'Default', - }, - link: { - insert: 'Insert link', - text: 'Link text', - url: 'Link source', - unLink: 'Unlink', - edit: 'Edit link', - view: 'View link', - }, - textStyle: { - bold: 'Bold', - clear: 'Clear styles', - code: 'Inline code', - italic: 'Italic', - sub: 'Sub', - sup: 'Sup', - through: 'Through', - underline: 'Underline', - }, - undo: { - undo: 'undo', - redo: 'Redo', - }, - todo: { - todo: 'Todo', - }, -} diff --git a/packages/basic-modules/src/locale/index.ts b/packages/basic-modules/src/locale/index.ts deleted file mode 100644 index 22f6ec6cb..000000000 --- a/packages/basic-modules/src/locale/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n entry - * @author wangfupeng - */ - -import { i18nAddResources } from '@wangeditor/core' -import enResources from './en' -import zhResources from './zh-CN' - -i18nAddResources('en', enResources) -i18nAddResources('zh-CN', zhResources) diff --git a/packages/basic-modules/src/locale/zh-CN.ts b/packages/basic-modules/src/locale/zh-CN.ts deleted file mode 100644 index cccc6187b..000000000 --- a/packages/basic-modules/src/locale/zh-CN.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @description i18n zh-CN - * @author wangfupeng - */ - -export default { - // 通用的词 - common: { - ok: '确定', - delete: '删除', - enter: '回车', - }, - - blockQuote: { - title: '引用', - }, - codeBlock: { - title: '代码块', - }, - color: { - color: '文字颜色', - bgColor: '背景色', - default: '默认颜色', - clear: '清除背景色', - }, - divider: { - title: '分割线', - }, - emotion: { - title: '表情', - }, - fontSize: { - title: '字号', - default: '默认字号', - }, - fontFamily: { - title: '字体', - default: '默认字体', - }, - fullScreen: { - title: '全屏', - }, - header: { - title: '标题', - text: '正文', - }, - image: { - netImage: '网络图片', - delete: '删除图片', - edit: '编辑图片', - viewLink: '查看链接', - src: '图片地址', - desc: '图片描述', - link: '图片链接', - }, - indent: { - decrease: '减少缩进', - increase: '增加缩进', - }, - justify: { - left: '左对齐', - right: '右对齐', - center: '居中对齐', - justify: '两端对齐', - }, - lineHeight: { - title: '行高', - default: '默认行高', - }, - link: { - insert: '插入链接', - text: '链接文本', - url: '链接地址', - unLink: '取消链接', - edit: '修改链接', - view: '查看链接', - }, - textStyle: { - bold: '粗体', - clear: '清除格式', - code: '行内代码', - italic: '斜体', - sub: '下标', - sup: '上标', - through: '删除线', - underline: '下划线', - }, - undo: { - undo: '撤销', - redo: '重做', - }, - todo: { - todo: '待办', - }, -} diff --git a/packages/basic-modules/src/modules/blockquote/custom-types.ts b/packages/basic-modules/src/modules/blockquote/custom-types.ts deleted file mode 100644 index f162c645e..000000000 --- a/packages/basic-modules/src/modules/blockquote/custom-types.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @description 自定义 element - * @author wangfupeng - */ - -import { Text } from 'slate' - -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts - -export type BlockQuoteElement = { - type: 'blockquote' - children: Text[] -} diff --git a/packages/basic-modules/src/modules/blockquote/elem-to-html.ts b/packages/basic-modules/src/modules/blockquote/elem-to-html.ts deleted file mode 100644 index d952f2044..000000000 --- a/packages/basic-modules/src/modules/blockquote/elem-to-html.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @description to html - * @author wangfupeng - */ - -import { Element } from 'slate' - -function quoteToHtml(elem: Element, childrenHtml: string): string { - return `
${childrenHtml}
` -} - -export const quoteToHtmlConf = { - type: 'blockquote', - elemToHtml: quoteToHtml, -} diff --git a/packages/basic-modules/src/modules/blockquote/index.ts b/packages/basic-modules/src/modules/blockquote/index.ts deleted file mode 100644 index e01fd986e..000000000 --- a/packages/basic-modules/src/modules/blockquote/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @description blockquote entry - * @author wangfupeng - */ - -import { IModuleConf } from '@wangeditor/core' -import { renderBlockQuoteConf } from './render-elem' -import { quoteToHtmlConf } from './elem-to-html' -import { parseHtmlConf } from './parse-elem-html' -import { blockquoteMenuConf } from './menu/index' -import withBlockquote from './plugin' - -const blockquote: Partial = { - renderElems: [renderBlockQuoteConf], - elemsToHtml: [quoteToHtmlConf], - parseElemsHtml: [parseHtmlConf], - menus: [blockquoteMenuConf], - editorPlugin: withBlockquote, -} - -export default blockquote diff --git a/packages/basic-modules/src/modules/blockquote/menu/BlockquoteMenu.ts b/packages/basic-modules/src/modules/blockquote/menu/BlockquoteMenu.ts deleted file mode 100644 index 7c6b88615..000000000 --- a/packages/basic-modules/src/modules/blockquote/menu/BlockquoteMenu.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @description blockquote menu class - * @author wangfupeng - */ - -import { Editor, Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { QUOTE_SVG } from '../../../constants/icon-svg' - -class BlockquoteMenu implements IButtonMenu { - readonly title = t('blockQuote.title') - readonly iconSvg = QUOTE_SVG - readonly tag = 'button' - - getValue(editor: IDomEditor): string | boolean { - // 用不到 getValue - return '' - } - - isActive(editor: IDomEditor): boolean { - const node = DomEditor.getSelectedNodeByType(editor, 'blockquote') - return !!node - } - - isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true - - const [nodeEntry] = Editor.nodes(editor, { - match: n => { - const type = DomEditor.getNodeType(n) - - // 只可用于 p 和 blockquote - if (type === 'paragraph') return true - if (type === 'blockquote') return true - - return false - }, - universal: true, - mode: 'highest', // 匹配最高层级 - }) - - // 匹配到 p blockquote ,不禁用 - if (nodeEntry) { - return false - } - // 未匹配到,则禁用 - return true - } - - /** - * 执行命令 - * @param editor editor - * @param value node.type - */ - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - const active = this.isActive(editor) - const newType = active ? 'paragraph' : 'blockquote' - - // 执行命令 - Transforms.setNodes(editor, { type: newType }, { mode: 'highest' }) - } -} - -export default BlockquoteMenu diff --git a/packages/basic-modules/src/modules/blockquote/menu/index.ts b/packages/basic-modules/src/modules/blockquote/menu/index.ts deleted file mode 100644 index 0bffcf420..000000000 --- a/packages/basic-modules/src/modules/blockquote/menu/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @description block quote menu - * @author wangfupeng - */ - -import BlockquoteMenu from './BlockquoteMenu' - -export const blockquoteMenuConf = { - key: 'blockquote', - factory() { - return new BlockquoteMenu() - }, -} diff --git a/packages/basic-modules/src/modules/blockquote/parse-elem-html.ts b/packages/basic-modules/src/modules/blockquote/parse-elem-html.ts deleted file mode 100644 index 8fa3233d4..000000000 --- a/packages/basic-modules/src/modules/blockquote/parse-elem-html.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @description parse html - * @author wangfupeng - */ - -import { Descendant, Text } from 'slate' -import $, { DOMElement } from '../../utils/dom' -import { IDomEditor } from '@wangeditor/core' -import { BlockQuoteElement } from './custom-types' - -function parseHtml( - elem: DOMElement, - children: Descendant[], - editor: IDomEditor -): BlockQuoteElement { - const $elem = $(elem) - - children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true - return false - }) - - // 无 children ,则用纯文本 - if (children.length === 0) { - children = [{ text: $elem.text().replace(/\s+/gm, ' ') }] - } - - return { - type: 'blockquote', - // @ts-ignore - children, - } -} - -export const parseHtmlConf = { - selector: 'blockquote:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性 - parseElemHtml: parseHtml, -} diff --git a/packages/basic-modules/src/modules/blockquote/plugin.ts b/packages/basic-modules/src/modules/blockquote/plugin.ts deleted file mode 100644 index 05e985538..000000000 --- a/packages/basic-modules/src/modules/blockquote/plugin.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @description editor 插件,重写 editor API - * @author wangfupeng - */ - -import { Editor, Transforms, Node, Point } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor/core' - -function withBlockquote(editor: T): T { - const { insertBreak, insertText } = editor - const newEditor = editor - - // 重写 insertBreak - 换行时插入 p - newEditor.insertBreak = () => { - const { selection } = newEditor - if (selection == null) return insertBreak() - - const [nodeEntry] = Editor.nodes(editor, { - match: n => DomEditor.checkNodeType(n, 'blockquote'), - universal: true, - }) - if (!nodeEntry) return insertBreak() - - const quoteElem = nodeEntry[0] - const quotePath = DomEditor.findPath(editor, quoteElem) - const quoteEndLocation = Editor.end(editor, quotePath) - - if (Point.equals(quoteEndLocation, selection.focus)) { - // 光标位于 blockquote 最后 - const str = Node.string(quoteElem) - if (str && str.slice(-1) === '\n') { - // blockquote 文本最后一个是 \n - editor.deleteBackward('character') // 删除最后一个 \n - - // 则插入一个 paragraph - const p = { type: 'paragraph', children: [{ text: '' }] } - Transforms.insertNodes(newEditor, p, { mode: 'highest' }) - return - } - } - - // 情况情况,插入换行符 - insertText('\n') - } - - // 返回 editor ,重要! - return newEditor -} - -export default withBlockquote diff --git a/packages/basic-modules/src/modules/blockquote/render-elem.tsx b/packages/basic-modules/src/modules/blockquote/render-elem.tsx deleted file mode 100644 index 8d4fcc2f3..000000000 --- a/packages/basic-modules/src/modules/blockquote/render-elem.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @description render elem - * @author wangfupeng - */ - -import { Element as SlateElement } from 'slate' -import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor/core' - -/** - * render block quote elem - * @param elemNode slate elem - * @param children children - * @param editor editor - * @returns vnode - */ -function renderBlockQuote( - elemNode: SlateElement, - children: VNode[] | null, - editor: IDomEditor -): VNode { - const vnode =
{children}
- return vnode -} - -export const renderBlockQuoteConf = { - type: 'blockquote', - renderElem: renderBlockQuote, -} diff --git a/packages/basic-modules/src/modules/code-block/custom-types.ts b/packages/basic-modules/src/modules/code-block/custom-types.ts deleted file mode 100644 index 735fc7235..000000000 --- a/packages/basic-modules/src/modules/code-block/custom-types.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @description 自定义 element - * @author wangfupeng - */ - -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts - -type PureText = { - text: string -} - -export type PreElement = { - type: 'pre' - children: CodeElement[] -} - -export type CodeElement = { - type: 'code' - language: string - children: PureText[] -} diff --git a/packages/basic-modules/src/modules/code-block/elem-to-html.ts b/packages/basic-modules/src/modules/code-block/elem-to-html.ts deleted file mode 100644 index 22c1269e9..000000000 --- a/packages/basic-modules/src/modules/code-block/elem-to-html.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @description to html - * @author wangfupeng - */ - -import { Element } from 'slate' - -function codeToHtml(elem: Element, childrenHtml: string): string { - // 代码高亮 `class="language-xxx"` 在 code-highlight 中实现 - return `${childrenHtml}` -} - -export const codeToHtmlConf = { - type: 'code', - elemToHtml: codeToHtml, -} - -function preToHtml(elem: Element, childrenHtml: string): string { - return `
${childrenHtml}
` -} - -export const preToHtmlConf = { - type: 'pre', - elemToHtml: preToHtml, -} diff --git a/packages/basic-modules/src/modules/code-block/index.ts b/packages/basic-modules/src/modules/code-block/index.ts deleted file mode 100644 index d9745aefe..000000000 --- a/packages/basic-modules/src/modules/code-block/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @description code block module - * @author wangfupeng - */ - -import { IModuleConf } from '@wangeditor/core' -import { codeBlockMenuConf } from './menu/index' -import withCodeBlock from './plugin' -import { renderPreConf, renderCodeConf } from './render-elem' -import { preParseHtmlConf } from './pre-parse-html' -import { parseCodeHtmlConf, parsePreHtmlConf } from './parse-elem-html' -import { codeToHtmlConf, preToHtmlConf } from './elem-to-html' - -const codeBlockModule: Partial = { - menus: [codeBlockMenuConf], - editorPlugin: withCodeBlock, - renderElems: [renderPreConf, renderCodeConf], - elemsToHtml: [codeToHtmlConf, preToHtmlConf], - preParseHtml: [preParseHtmlConf], - parseElemsHtml: [parseCodeHtmlConf, parsePreHtmlConf], -} - -export default codeBlockModule diff --git a/packages/basic-modules/src/modules/code-block/menu/CodeBlockMenu.ts b/packages/basic-modules/src/modules/code-block/menu/CodeBlockMenu.ts deleted file mode 100644 index afbfd692f..000000000 --- a/packages/basic-modules/src/modules/code-block/menu/CodeBlockMenu.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * @description insert code-block menu - * @author wangfupeng - */ - -import { Editor, Element, Transforms, Node, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { CODE_BLOCK_SVG } from '../../../constants/icon-svg' -import { CodeElement } from '../custom-types' - -class CodeBlockMenu implements IButtonMenu { - readonly title = t('codeBlock.title') - readonly iconSvg = CODE_BLOCK_SVG - readonly tag = 'button' - - private getSelectCodeElem(editor: IDomEditor): CodeElement | null { - const codeNode = DomEditor.getSelectedNodeByType(editor, 'code') - if (codeNode == null) return null - const preNode = DomEditor.getParentNode(editor, codeNode) - if (preNode == null) return null - if (DomEditor.getNodeType(preNode) !== 'pre') return null - - return codeNode as CodeElement - } - - /** - * 获取语言类型 - * @param editor editor - */ - getValue(editor: IDomEditor): string | boolean { - const elem = this.getSelectCodeElem(editor) - if (elem == null) return '' - return elem.language || '' - } - - isActive(editor: IDomEditor): boolean { - const elem = this.getSelectCodeElem(editor) - return !!elem - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - - const selectedElems = DomEditor.getSelectedElems(editor) - - const hasVoid = selectedElems.some(elem => editor.isVoid(elem)) - if (hasVoid) return true - - const isMatch = selectedElems.some(elem => { - const type = DomEditor.getNodeType(elem) - if (type === 'pre' || type === 'paragraph') return true // 匹配 pre 或 paragraph - }) - if (isMatch) return false // 匹配到,则 enable - return true // 否则 disable - } - - exec(editor: IDomEditor, value: string | boolean) { - const active = this.isActive(editor) - if (active) { - // 当前处于 code-block ,需要转换为普通文本 - this.changeToPlainText(editor) - } else { - // 当前未处于 code-block ,需要转换为 code-block - this.changeToCodeBlock(editor, value.toString()) - } - } - - private changeToPlainText(editor: IDomEditor) { - const elem = this.getSelectCodeElem(editor) - if (elem == null) return - - // 获取 code 文本 - const str = Node.string(elem) - - // 删除当前最高层级的节点,即 pre 节点 - Transforms.removeNodes(editor, { mode: 'highest' }) - - // 插入 p 节点 - const pList = str.split('\n').map(s => { - return { type: 'paragraph', children: [{ text: s }] } - }) - Transforms.insertNodes(editor, pList, { mode: 'highest' }) - } - - private changeToCodeBlock(editor: IDomEditor, language: string) { - // 汇总选中的最高层级节点的字符串 - const strArr: string[] = [] - const nodeEntries = Editor.nodes(editor, { - match: n => editor.children.includes(n as Element), // 匹配选中的最高层级的节点 - universal: true, - }) - for (let nodeEntry of nodeEntries) { - const [n] = nodeEntry - if (n) strArr.push(Node.string(n)) - } - - // 删除选中的最高层级的节点 - Transforms.removeNodes(editor, { mode: 'highest' }) - - // 插入 pre 节点 - const newPreNode = { - type: 'pre', - children: [ - { - type: 'code', - language, - children: [ - { text: strArr.join('\n') }, // 选中节点的纯文本 - ], - }, - ], - } - Transforms.insertNodes(editor, newPreNode, { mode: 'highest' }) - } -} - -export default CodeBlockMenu diff --git a/packages/basic-modules/src/modules/code-block/menu/index.ts b/packages/basic-modules/src/modules/code-block/menu/index.ts deleted file mode 100644 index bbeb682b4..000000000 --- a/packages/basic-modules/src/modules/code-block/menu/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @description code-block menu - * @author wangfupeng - */ - -import CodeBlockMenu from './CodeBlockMenu' - -export const codeBlockMenuConf = { - key: 'codeBlock', - factory() { - return new CodeBlockMenu() - }, -} diff --git a/packages/basic-modules/src/modules/code-block/parse-elem-html.ts b/packages/basic-modules/src/modules/code-block/parse-elem-html.ts deleted file mode 100644 index 127eb285f..000000000 --- a/packages/basic-modules/src/modules/code-block/parse-elem-html.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @description parse html - * @author wangfupeng - */ - -import { Descendant } from 'slate' -import $, { DOMElement } from '../../utils/dom' -import { IDomEditor, DomEditor } from '@wangeditor/core' -import { PreElement, CodeElement } from './custom-types' - -function parseCodeHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): CodeElement { - const $elem = $(elem) - - return { - type: 'code', - language: '', // language 在 code-highlight 中实现 - children: [{ text: $elem[0].textContent || '' }], - } -} - -export const parseCodeHtmlConf = { - selector: 'pre:not([data-w-e-type])>code', // 匹配
 下的 
-  parseElemHtml: parseCodeHtml,
-}
-
-function parsePreHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): PreElement {
-  const $elem = $(elem)
-
-  children = children.filter(child => DomEditor.getNodeType(child) === 'code')
-  if (children.length === 0) {
-    children = [{ type: 'code', language: '', children: [{ text: $elem[0].textContent || '' }] }]
-  }
-
-  return {
-    type: 'pre',
-    // @ts-ignore
-    children: children.filter(child => DomEditor.getNodeType(child) === 'code'),
-  }
-}
-
-export const parsePreHtmlConf = {
-  selector: 'pre:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: parsePreHtml,
-}
diff --git a/packages/basic-modules/src/modules/code-block/plugin.ts b/packages/basic-modules/src/modules/code-block/plugin.ts
deleted file mode 100644
index 36a694ae6..000000000
--- a/packages/basic-modules/src/modules/code-block/plugin.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-/**
- * @description editor 插件,重写 editor API
- * @author wangfupeng
- */
-
-import { Editor, Transforms, Node as SlateNode, Element as SlateElement } from 'slate'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-
-function getLastTextLineBeforeSelection(codeNode: SlateNode, editor: IDomEditor): string {
-  const selection = editor.selection
-  if (selection == null) return ''
-
-  const codeText = SlateNode.string(codeNode)
-  const anchorOffset = selection.anchor.offset
-  const textBeforeAnchor = codeText.slice(0, anchorOffset) // 选区前的 text
-  const arr = textBeforeAnchor.split('\n') // 选区前的 text ,按换行拆分
-  const length = arr.length
-  if (length === 0) return ''
-
-  return arr[length - 1]
-}
-
-function withCodeBlock(editor: T): T {
-  const { insertBreak, normalizeNode, insertData, insertNode } = editor
-  const newEditor = editor
-
-  // 重写换行操作
-  newEditor.insertBreak = () => {
-    const codeNode = DomEditor.getSelectedNodeByType(newEditor, 'code')
-    if (codeNode == null) {
-      insertBreak() // 执行默认的换行
-      return
-    }
-
-    // 回车时,根据当前行的空格,自动插入空格
-    const lastLineBeforeSelection = getLastTextLineBeforeSelection(codeNode, newEditor)
-    if (lastLineBeforeSelection) {
-      const arr = lastLineBeforeSelection.match(/^\s+/) // 行开始的空格
-      if (arr != null && arr[0] != null) {
-        const spaces = arr[0]
-        newEditor.insertText(`\n${spaces}`) // 换行后插入空格
-        return
-      }
-    }
-
-    // 普通换行
-    newEditor.insertText('\n')
-  }
-
-  // 重写 normalizeNode
-  newEditor.normalizeNode = ([node, path]) => {
-    const type = DomEditor.getNodeType(node)
-
-    // -------------- code node 不能是顶层,否则替换为 p --------------
-    if (type === 'code' && path.length <= 1) {
-      Transforms.setNodes(newEditor, { type: 'paragraph' }, { at: path })
-    }
-
-    if (type === 'pre') {
-      // -------------- pre 是 editor 最后一个节点,需要后面插入 p --------------
-      const isLast = DomEditor.isLastNode(newEditor, node)
-      if (isLast) {
-        Transforms.insertNodes(newEditor, DomEditor.genEmptyParagraph(), { at: [path[0] + 1] })
-      }
-
-      // -------------- pre 下面必须是 code --------------
-      if (DomEditor.getNodeType((node as SlateElement).children[0]) !== 'code') {
-        Transforms.unwrapNodes(newEditor)
-        Transforms.setNodes(newEditor, { type: 'paragraph' }, { mode: 'highest' })
-      }
-    }
-
-    // 执行默认行为
-    return normalizeNode([node, path])
-  }
-
-  // 重写 insertData - 粘贴文本
-  newEditor.insertData = (data: DataTransfer) => {
-    const codeNode = DomEditor.getSelectedNodeByType(newEditor, 'code')
-    if (codeNode == null) {
-      insertData(data) // 执行默认的 insertData
-      return
-    }
-
-    // 获取文本,并插入到代码块
-    const text = data.getData('text/plain')
-    Editor.insertText(newEditor, text)
-  }
-
-  // 返回 editor ,重要!
-  return newEditor
-}
-
-export default withCodeBlock
diff --git a/packages/basic-modules/src/modules/code-block/pre-parse-html.ts b/packages/basic-modules/src/modules/code-block/pre-parse-html.ts
deleted file mode 100644
index a6c09fafd..000000000
--- a/packages/basic-modules/src/modules/code-block/pre-parse-html.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @description pre parse html
- * @author wangfupeng
- */
-
-import $, { DOMElement } from '../../utils/dom'
-import { getTagName } from '../../utils/dom'
-
-/**
- * pre-prase  ,去掉其中的  (兼容 V4)
- * @param codeElem codeElem
- */
-function preParse(codeElem: DOMElement): DOMElement {
-  const $code = $(codeElem)
-  const tagName = getTagName($code)
-  if (tagName !== 'code') return codeElem
-
-  const $xmp = $code.find('xmp')
-  if ($xmp.length === 0) return codeElem // 不是 V4 格式
-
-  const codeText = $xmp.text()
-  $xmp.remove()
-  $code.text(codeText)
-
-  return $code[0]
-}
-
-export const preParseHtmlConf = {
-  selector: 'pre>code', // 匹配 <pre> 下的 <code>
-  preParseHtml: preParse,
-}
diff --git a/packages/basic-modules/src/modules/code-block/render-elem.tsx b/packages/basic-modules/src/modules/code-block/render-elem.tsx
deleted file mode 100644
index e5d6b18ff..000000000
--- a/packages/basic-modules/src/modules/code-block/render-elem.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @description render elem
- * @author wangfupeng
- */
-
-import { Element as SlateElement } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { IDomEditor } from '@wangeditor/core'
-
-function renderPre(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
-  const vnode = <pre>{children}</pre>
-  return vnode
-}
-
-function renderCode(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
-  // 和 basic/simple-style module 的“行内代码”并不冲突。一个是根据 mark 渲染,一个是根据 node.type 渲染
-  const vnode = <code>{children}</code>
-  return vnode
-}
-
-export const renderPreConf = {
-  type: 'pre',
-  renderElem: renderPre,
-}
-
-export const renderCodeConf = {
-  type: 'code',
-  renderElem: renderCode,
-}
diff --git a/packages/basic-modules/src/modules/color/custom-types.ts b/packages/basic-modules/src/modules/color/custom-types.ts
deleted file mode 100644
index 0a62eedb1..000000000
--- a/packages/basic-modules/src/modules/color/custom-types.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-//【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts
-
-export type ColorText = {
-  text: string
-  color?: string
-  bgColor?: string
-}
diff --git a/packages/basic-modules/src/modules/color/index.ts b/packages/basic-modules/src/modules/color/index.ts
deleted file mode 100644
index f8a25442f..000000000
--- a/packages/basic-modules/src/modules/color/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description color bgColor
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderStyle } from './render-style'
-import { styleToHtml } from './style-to-html'
-import { preParseHtmlConf } from './pre-parse-html'
-import { parseStyleHtml } from './parse-style-html'
-import { colorMenuConf, bgColorMenuConf } from './menu/index'
-
-const color: Partial<IModuleConf> = {
-  renderStyle,
-  styleToHtml,
-  preParseHtml: [preParseHtmlConf],
-  parseStyleHtml,
-  menus: [colorMenuConf, bgColorMenuConf],
-}
-
-export default color
diff --git a/packages/basic-modules/src/modules/color/menu/BaseMenu.ts b/packages/basic-modules/src/modules/color/menu/BaseMenu.ts
deleted file mode 100644
index 3d90e70a8..000000000
--- a/packages/basic-modules/src/modules/color/menu/BaseMenu.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * @description color base menu
- * @author wangfupeng
- */
-
-import { Editor, Range } from 'slate'
-import { IDropPanelMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import $, { Dom7Array, DOMElement } from '../../../utils/dom'
-import { CLEAN_SVG } from '../../../constants/icon-svg'
-
-abstract class BaseMenu implements IDropPanelMenu {
-  abstract readonly title: string
-  abstract readonly iconSvg: string
-  readonly tag = 'button'
-  readonly showDropPanel = true // 点击 button 时显示 dropPanel
-  protected abstract readonly mark: string
-  private $content: Dom7Array | null = null
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    // 点击菜单时,弹出 droPanel 之前,不需要执行其他代码
-    // 此处空着即可
-  }
-
-  getValue(editor: IDomEditor): string | boolean {
-    const mark = this.mark
-    const curMarks = Editor.marks(editor)
-    // @ts-ignore
-    if (curMarks && curMarks[mark]) return curMarks[mark]
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    const color = this.getValue(editor)
-    return !!color
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const [match] = Editor.nodes(editor, {
-      match: n => {
-        const type = DomEditor.getNodeType(n)
-
-        if (type === 'pre') return true // 代码块
-        if (Editor.isVoid(editor, n)) return true // void node
-
-        return false
-      },
-      universal: true,
-    })
-
-    // 命中,则禁用
-    if (match) return true
-    return false
-  }
-
-  getPanelContentElem(editor: IDomEditor): DOMElement {
-    const mark = this.mark
-
-    if (this.$content == null) {
-      // 第一次渲染
-      const $content = $('<ul class="w-e-panel-content-color"></ul>')
-
-      // 绑定事件(只在第一次绑定,不要重复绑定)
-      $content.on('click', 'li', (e: Event) => {
-        const { target } = e
-        if (target == null) return
-        e.preventDefault()
-
-        const { selection } = editor
-        if (selection == null) return
-
-        const $li = $(target)
-        const val = $li.attr('data-value')
-
-        // 修改文本样式
-        if (val === '0') {
-          Editor.removeMark(editor, mark)
-        } else {
-          Editor.addMark(editor, mark, val)
-        }
-      })
-
-      this.$content = $content
-    }
-    const $content = this.$content
-    if ($content == null) return document.createElement('ul')
-    $content.empty() // 清空之后再重置内容
-
-    // 当前选中文本的颜色之
-    const selectedColor = this.getValue(editor)
-
-    // 获取菜单配置
-    const colorConf = editor.getMenuConfig(mark)
-    const { colors = [] } = colorConf
-    // 根据菜单配置生成 panel content
-    colors.forEach((color: string) => {
-      const $block = $(`<div class="color-block" data-value="${color}"></div>`)
-      $block.css('background-color', color)
-
-      const $li = $(`<li data-value="${color}"></li>`)
-      if (selectedColor === color) {
-        $li.addClass('active')
-      }
-      $li.append($block)
-
-      $content.append($li)
-    })
-
-    // 清除颜色
-    let clearText = ''
-    if (mark === 'color') clearText = t('color.default')
-    if (mark === 'bgColor') clearText = t('color.clear')
-    const $clearLi = $(`
-      <li data-value="0" class="clear">
-        ${CLEAN_SVG}
-        ${clearText}
-      </li>
-    `)
-    $content.prepend($clearLi)
-
-    return $content[0]
-  }
-}
-
-export default BaseMenu
diff --git a/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts b/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts
deleted file mode 100644
index c665cba6e..000000000
--- a/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * @description bg color menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { BG_COLOR_SVG } from '../../../constants/icon-svg'
-
-class BgColorMenu extends BaseMenu {
-  readonly title = t('color.bgColor')
-  readonly iconSvg = BG_COLOR_SVG
-  readonly mark = 'bgColor'
-}
-
-export default BgColorMenu
diff --git a/packages/basic-modules/src/modules/color/menu/ColorMenu.ts b/packages/basic-modules/src/modules/color/menu/ColorMenu.ts
deleted file mode 100644
index 9b73e4348..000000000
--- a/packages/basic-modules/src/modules/color/menu/ColorMenu.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * @description color menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { FONT_COLOR_SVG } from '../../../constants/icon-svg'
-
-class ColorMenu extends BaseMenu {
-  readonly title = t('color.color')
-  readonly iconSvg = FONT_COLOR_SVG
-  readonly mark = 'color'
-}
-
-export default ColorMenu
diff --git a/packages/basic-modules/src/modules/color/menu/config.ts b/packages/basic-modules/src/modules/color/menu/config.ts
deleted file mode 100644
index e350e1c83..000000000
--- a/packages/basic-modules/src/modules/color/menu/config.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * @description menu config
- * @author wangfupeng
- */
-
-const COLORS = [
-  'rgb(0, 0, 0)',
-  'rgb(38, 38, 38)',
-  'rgb(89, 89, 89)',
-  'rgb(140, 140, 140)',
-  'rgb(191, 191, 191)',
-  'rgb(217, 217, 217)',
-  'rgb(233, 233, 233)',
-  'rgb(245, 245, 245)',
-  'rgb(250, 250, 250)',
-  'rgb(255, 255, 255)', // 10
-  'rgb(225, 60, 57)',
-  'rgb(231, 95, 51)',
-  'rgb(235, 144, 58)',
-  'rgb(245, 219, 77)',
-  'rgb(114, 192, 64)',
-  'rgb(89, 191, 192)',
-  'rgb(66, 144, 247)',
-  'rgb(54, 88, 226)',
-  'rgb(106, 57, 201)',
-  'rgb(216, 68, 147)', // 10
-  'rgb(251, 233, 230)',
-  'rgb(252, 237, 225)',
-  'rgb(252, 239, 212)',
-  'rgb(252, 251, 207)',
-  'rgb(231, 246, 213)',
-  'rgb(218, 244, 240)',
-  'rgb(217, 237, 250)',
-  'rgb(224, 232, 250)',
-  'rgb(237, 225, 248)',
-  'rgb(246, 226, 234)', // 10
-  'rgb(255, 163, 158)',
-  'rgb(255, 187, 150)',
-  'rgb(255, 213, 145)',
-  'rgb(255, 251, 143)',
-  'rgb(183, 235, 143)',
-  'rgb(135, 232, 222)',
-  'rgb(145, 213, 255)',
-  'rgb(173, 198, 255)',
-  'rgb(211, 173, 247)',
-  'rgb(255, 173, 210)', // 10
-  'rgb(255, 77, 79)',
-  'rgb(255, 122, 69)',
-  'rgb(255, 169, 64)',
-  'rgb(255, 236, 61)',
-  'rgb(115, 209, 61)',
-  'rgb(54, 207, 201)',
-  'rgb(64, 169, 255)',
-  'rgb(89, 126, 247)',
-  'rgb(146, 84, 222)',
-  'rgb(247, 89, 171)', // 10
-  'rgb(207, 19, 34)',
-  'rgb(212, 56, 13)',
-  'rgb(212, 107, 8)',
-  'rgb(212, 177, 6)',
-  'rgb(56, 158, 13)',
-  'rgb(8, 151, 156)',
-  'rgb(9, 109, 217)',
-  'rgb(29, 57, 196)',
-  'rgb(83, 29, 171)',
-  'rgb(196, 29, 127)', // 10
-  'rgb(130, 0, 20)',
-  'rgb(135, 20, 0)',
-  'rgb(135, 56, 0)',
-  'rgb(97, 71, 0)',
-  'rgb(19, 82, 0)',
-  'rgb(0, 71, 79)',
-  'rgb(0, 58, 140)',
-  'rgb(6, 17, 120)',
-  'rgb(34, 7, 94)',
-  'rgb(120, 6, 80)', // 10
-]
-
-export function genColors() {
-  return COLORS
-}
-
-export function genBgColors() {
-  return COLORS
-}
diff --git a/packages/basic-modules/src/modules/color/menu/index.ts b/packages/basic-modules/src/modules/color/menu/index.ts
deleted file mode 100644
index 8dfa76d14..000000000
--- a/packages/basic-modules/src/modules/color/menu/index.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @description menu entry
- * @author wangfupeng
- */
-
-import ColorMenu from './ColorMenu'
-import BgColorMenu from './BgColorMenu'
-import { genColors, genBgColors } from './config'
-
-export const colorMenuConf = {
-  key: 'color',
-  factory() {
-    return new ColorMenu()
-  },
-
-  // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
-  // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
-  config: {
-    colors: genColors(),
-  },
-}
-
-export const bgColorMenuConf = {
-  key: 'bgColor',
-  factory() {
-    return new BgColorMenu()
-  },
-  config: {
-    colors: genBgColors(),
-  },
-}
diff --git a/packages/basic-modules/src/modules/color/parse-style-html.ts b/packages/basic-modules/src/modules/color/parse-style-html.ts
deleted file mode 100644
index 444363846..000000000
--- a/packages/basic-modules/src/modules/color/parse-style-html.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @description parse style html
- * @author wangfupeng
- */
-
-import { Descendant, Text } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { ColorText } from './custom-types'
-import $, { DOMElement, getStyleValue } from '../../utils/dom'
-
-export function parseStyleHtml(text: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
-  const $text = $(text)
-  if (!Text.isText(node)) return node
-
-  const textNode = node as ColorText
-
-  const color = getStyleValue($text, 'color')
-  if (color) {
-    textNode.color = color
-  }
-
-  let bgColor = getStyleValue($text, 'background-color')
-  if (!bgColor) bgColor = getStyleValue($text, 'background') // word 背景色
-  if (bgColor) {
-    textNode.bgColor = bgColor
-  }
-
-  return textNode
-}
diff --git a/packages/basic-modules/src/modules/color/pre-parse-html.ts b/packages/basic-modules/src/modules/color/pre-parse-html.ts
deleted file mode 100644
index 9e88028af..000000000
--- a/packages/basic-modules/src/modules/color/pre-parse-html.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @description pre-parse html
- * @author wangfupeng
- */
-
-import $, { DOMElement, getTagName } from '../../utils/dom'
-
-/**
- * pre-prase font ,兼容 V4
- * @param fontElem fontElem
- */
-function preParse(fontElem: DOMElement): DOMElement {
-  const $font = $(fontElem)
-  const tagName = getTagName($font)
-  if (tagName !== 'font') return fontElem
-
-  // 处理 color (V4 使用 <font color="#ccc">xx</font> 格式)
-  const color = $font.attr('color') || ''
-  if (color) {
-    $font.removeAttr('color')
-    $font.css('color', color)
-  }
-
-  return $font[0]
-}
-
-export const preParseHtmlConf = {
-  selector: 'font',
-  preParseHtml: preParse,
-}
diff --git a/packages/basic-modules/src/modules/color/render-style.tsx b/packages/basic-modules/src/modules/color/render-style.tsx
deleted file mode 100644
index 264c67a30..000000000
--- a/packages/basic-modules/src/modules/color/render-style.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @description render color style
- * @author wangfupeng
- */
-
-import { Descendant } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { addVnodeStyle } from '../../utils/vdom'
-import { ColorText } from './custom-types'
-
-/**
- * 添加样式
- * @param node text node
- * @param vnode vnode
- * @returns vnode
- */
-export function renderStyle(node: Descendant, vnode: VNode): VNode {
-  const { color, bgColor } = node as ColorText
-  let styleVnode: VNode = vnode
-
-  if (color) {
-    addVnodeStyle(styleVnode, { color })
-  }
-  if (bgColor) {
-    addVnodeStyle(styleVnode, { backgroundColor: bgColor })
-  }
-
-  return styleVnode
-}
diff --git a/packages/basic-modules/src/modules/color/style-to-html.ts b/packages/basic-modules/src/modules/color/style-to-html.ts
deleted file mode 100644
index 3ec1c0be4..000000000
--- a/packages/basic-modules/src/modules/color/style-to-html.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * @description textStyle to html
- * @author wangfupeng
- */
-
-import { Text, Descendant } from 'slate'
-import $, { getOuterHTML, getTagName, isPlainText } from '../../utils/dom'
-import { ColorText } from './custom-types'
-
-/**
- * style to html
- * @param textNode slate text node
- * @param textHtml text html
- * @returns styled html
- */
-export function styleToHtml(textNode: Descendant, textHtml: string): string {
-  if (!Text.isText(textNode)) return textHtml
-
-  const { color, bgColor } = textNode as ColorText
-  if (!color && !bgColor) return textHtml
-
-  let $text
-
-  if (isPlainText(textHtml)) {
-    // textHtml 是纯文本,不是 html tag
-    $text = $(`<span>${textHtml}</span>`)
-  } else {
-    // textHtml 是 html tag
-    $text = $(textHtml)
-    const tagName = getTagName($text)
-    if (tagName !== 'span') {
-      // 如果不是 span ,则包裹一层,接下来要设置 css
-      $text = $(`<span>${textHtml}</span>`)
-    }
-  }
-
-  // 设置样式
-  if (color) $text.css('color', color)
-  if (bgColor) $text.css('background-color', bgColor)
-
-  // 输出 html
-  return getOuterHTML($text)
-}
diff --git a/packages/basic-modules/src/modules/common/index.ts b/packages/basic-modules/src/modules/common/index.ts
deleted file mode 100644
index cde307b4e..000000000
--- a/packages/basic-modules/src/modules/common/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @description common module
- * @author wangfupeng
- */
-import { IModuleConf } from '@wangeditor/core'
-import { enterMenuConf } from './menu/index'
-
-const commonModule: Partial<IModuleConf> = {
-  menus: [enterMenuConf],
-}
-
-export default commonModule
diff --git a/packages/basic-modules/src/modules/common/menu/EnterMenu.ts b/packages/basic-modules/src/modules/common/menu/EnterMenu.ts
deleted file mode 100644
index e12ae461e..000000000
--- a/packages/basic-modules/src/modules/common/menu/EnterMenu.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @description enter menu
- * @author wangfupeng
- */
-
-import { Range, Transforms, Editor } from 'slate'
-import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
-import { ENTER_SVG } from '../../../constants/icon-svg'
-
-class EnterMenu implements IButtonMenu {
-  title = t('common.enter')
-  iconSvg = ENTER_SVG
-  tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    const { selection } = editor
-    if (selection == null) return true
-    if (Range.isExpanded(selection)) return true
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    const { selection } = editor
-    if (selection == null) return
-    const { anchor } = selection
-    const { path } = anchor
-
-    // 在当前位置插入空行,当前元素下移
-    const newElem = { type: 'paragraph', children: [{ text: '' }] }
-    const newPath = [path[0]]
-    Transforms.insertNodes(editor, newElem, { at: newPath })
-    editor.select(Editor.start(editor, newPath))
-  }
-}
-
-export default EnterMenu
diff --git a/packages/basic-modules/src/modules/common/menu/index.ts b/packages/basic-modules/src/modules/common/menu/index.ts
deleted file mode 100644
index 45eb2a891..000000000
--- a/packages/basic-modules/src/modules/common/menu/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description common menu config
- * @author wangfupeng
- */
-
-import EnterMenu from './EnterMenu'
-
-export const enterMenuConf = {
-  key: 'enter',
-  factory() {
-    return new EnterMenu()
-  },
-}
diff --git a/packages/basic-modules/src/modules/divider/README.md b/packages/basic-modules/src/modules/divider/README.md
deleted file mode 100644
index a857e3010..000000000
--- a/packages/basic-modules/src/modules/divider/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# 分割线
\ No newline at end of file
diff --git a/packages/basic-modules/src/modules/divider/custom-types.ts b/packages/basic-modules/src/modules/divider/custom-types.ts
deleted file mode 100644
index 10eca9291..000000000
--- a/packages/basic-modules/src/modules/divider/custom-types.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @description divider element
- * @author wangfupeng
- */
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-type EmptyText = {
-  text: ''
-}
-
-export type DividerElement = {
-  type: 'divider'
-  children: EmptyText[]
-}
diff --git a/packages/basic-modules/src/modules/divider/elem-to-html.ts b/packages/basic-modules/src/modules/divider/elem-to-html.ts
deleted file mode 100644
index 387f5cb92..000000000
--- a/packages/basic-modules/src/modules/divider/elem-to-html.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @description to html
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-
-function dividerToHtml(elem: Element, childrenHtml: string): string {
-  return `<hr/>`
-}
-
-export const dividerToHtmlConf = {
-  type: 'divider',
-  elemToHtml: dividerToHtml,
-}
diff --git a/packages/basic-modules/src/modules/divider/index.ts b/packages/basic-modules/src/modules/divider/index.ts
deleted file mode 100644
index d4f49e00d..000000000
--- a/packages/basic-modules/src/modules/divider/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description divider module
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import withDivider from './plugin'
-import { renderDividerConf } from './render-elem'
-import { dividerToHtmlConf } from './elem-to-html'
-import { parseHtmlConf } from './parse-elem-html'
-import { insertDividerMenuConf } from './menu/index'
-
-const image: Partial<IModuleConf> = {
-  renderElems: [renderDividerConf],
-  elemsToHtml: [dividerToHtmlConf],
-  parseElemsHtml: [parseHtmlConf],
-  menus: [insertDividerMenuConf],
-  editorPlugin: withDivider,
-}
-
-export default image
diff --git a/packages/basic-modules/src/modules/divider/menu/DeleteDividerMenu.ts.bak b/packages/basic-modules/src/modules/divider/menu/DeleteDividerMenu.ts.bak
deleted file mode 100644
index 252dacf17..000000000
--- a/packages/basic-modules/src/modules/divider/menu/DeleteDividerMenu.ts.bak
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * @description delete divider menu
- * @author wangfupeng
- */
-
-import { Transforms } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import { TRASH_SVG } from '../../../constants/icon-svg'
-
-class DeleteDividerMenu implements IButtonMenu {
-  readonly title = t('common.delete')
-  readonly iconSvg = TRASH_SVG
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 无需获取 val
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const dividerNode = DomEditor.getSelectedNodeByType(editor, 'divider')
-    if (dividerNode == null) {
-      // 选区未处于 divider node ,则禁用
-      return true
-    }
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (this.isDisabled(editor)) return
-
-    // 删除
-    Transforms.removeNodes(editor, {
-      match: n => DomEditor.checkNodeType(n, 'divider'),
-    })
-  }
-}
-
-export default DeleteDividerMenu
diff --git a/packages/basic-modules/src/modules/divider/menu/InsertDividerMenu.ts b/packages/basic-modules/src/modules/divider/menu/InsertDividerMenu.ts
deleted file mode 100644
index 7337629e9..000000000
--- a/packages/basic-modules/src/modules/divider/menu/InsertDividerMenu.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * @description insert divider menu
- * @author wangfupeng
- */
-
-import { Transforms } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import { DIVIDER_SVG } from '../../../constants/icon-svg'
-import { DividerElement } from '../custom-types'
-
-class InsertDividerMenu implements IButtonMenu {
-  readonly title = t('divider.title')
-  readonly iconSvg = DIVIDER_SVG
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 不需要 active
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    const { selection } = editor
-    if (selection == null) return true
-
-    const selectedElems = DomEditor.getSelectedElems(editor)
-    const hasVoidOrTableOrPre = selectedElems.some(elem => {
-      if (editor.isVoid(elem)) return true
-      const type = DomEditor.getNodeType(elem)
-      if (type === 'table') return true
-      if (type === 'pre') return true
-    })
-    if (hasVoidOrTableOrPre) return true // 匹配,则 disable
-
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean): void {
-    const node: DividerElement = {
-      type: 'divider',
-      children: [{ text: '' }], // 【注意】void node 需要一个空 text 作为 children
-    }
-
-    Transforms.insertNodes(editor, node, { mode: 'highest' })
-  }
-}
-
-export default InsertDividerMenu
diff --git a/packages/basic-modules/src/modules/divider/menu/index.ts b/packages/basic-modules/src/modules/divider/menu/index.ts
deleted file mode 100644
index da05475b8..000000000
--- a/packages/basic-modules/src/modules/divider/menu/index.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @description divider menu
- * @author wangfupeng
- */
-
-import InsertDividerMenu from './InsertDividerMenu'
-// import DeleteDividerMenu from './DeleteDividerMenu.ts'
-
-export const insertDividerMenuConf = {
-  key: 'divider',
-  factory() {
-    return new InsertDividerMenu()
-  },
-}
-
-// export const deleteDividerMenuConf = {
-//   key: 'deleteDivider',
-//   factory() {
-//     return new DeleteDividerMenu()
-//   },
-// }
-// divider 可用键盘删除了,所以注释掉该菜单 wangfupeng 22.02.23
diff --git a/packages/basic-modules/src/modules/divider/parse-elem-html.ts b/packages/basic-modules/src/modules/divider/parse-elem-html.ts
deleted file mode 100644
index 4519c76c8..000000000
--- a/packages/basic-modules/src/modules/divider/parse-elem-html.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description parse html
- * @author wangfupeng
- */
-
-import { Descendant } from 'slate'
-import $, { DOMElement } from '../../utils/dom'
-import { IDomEditor } from '@wangeditor/core'
-import { DividerElement } from './custom-types'
-
-function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): DividerElement {
-  return {
-    type: 'divider',
-    children: [{ text: '' }], // void node 有一个空白 text
-  }
-}
-
-export const parseHtmlConf = {
-  selector: 'hr:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: parseHtml,
-}
diff --git a/packages/basic-modules/src/modules/divider/plugin.ts b/packages/basic-modules/src/modules/divider/plugin.ts
deleted file mode 100644
index 479fe8b91..000000000
--- a/packages/basic-modules/src/modules/divider/plugin.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * @description editor 插件,重写 editor API
- * @author wangfupeng
- */
-
-import { Transforms, Element } from 'slate'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-
-function withDivider<T extends IDomEditor>(editor: T): T {
-  const { isVoid, normalizeNode } = editor
-  const newEditor = editor
-
-  // 重写 isVoid
-  newEditor.isVoid = elem => {
-    const { type } = elem
-
-    if (type === 'divider') {
-      return true
-    }
-
-    return isVoid(elem)
-  }
-
-  // 重新 normalize
-  newEditor.normalizeNode = ([node, path]) => {
-    const type = DomEditor.getNodeType(node)
-    if (type !== 'divider') {
-      // 未命中 divider ,执行默认的 normalizeNode
-      return normalizeNode([node, path])
-    }
-
-    // -------------- divider 是 editor 最后一个节点,需要后面插入 p --------------
-    const isLast = DomEditor.isLastNode(newEditor, node)
-    if (isLast) {
-      Transforms.insertNodes(newEditor, DomEditor.genEmptyParagraph(), { at: [path[0] + 1] })
-    }
-  }
-
-  // 返回 editor ,重要!
-  return newEditor
-}
-
-export default withDivider
diff --git a/packages/basic-modules/src/modules/divider/render-elem.tsx b/packages/basic-modules/src/modules/divider/render-elem.tsx
deleted file mode 100644
index 95de65491..000000000
--- a/packages/basic-modules/src/modules/divider/render-elem.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * @description render divider elem
- * @author wangfupeng
- */
-
-import { Element as SlateElement } from 'slate'
-import { h, VNode } from 'snabbdom'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-
-function renderDivider(
-  elemNode: SlateElement,
-  children: VNode[] | null,
-  editor: IDomEditor
-): VNode {
-  const renderStyle: any = {}
-
-  // 是否选中
-  const selected = DomEditor.isNodeSelected(editor, elemNode)
-
-  const vnode = h(
-    'div',
-    {
-      props: {
-        contentEditable: false,
-        className: 'w-e-textarea-divider',
-      },
-      dataset: {
-        selected: selected ? 'true' : '',
-      },
-      style: renderStyle,
-      on: {
-        mousedown: event => event.preventDefault(),
-      },
-    },
-    [h('hr')]
-  )
-  // 【注意】void node 中,renderElem 不用处理 children 。core 会统一处理。
-
-  return vnode
-}
-
-const renderDividerConf = {
-  type: 'divider', // 和 elemNode.type 一致
-  renderElem: renderDivider,
-}
-
-export { renderDividerConf }
diff --git a/packages/basic-modules/src/modules/emotion/index.ts b/packages/basic-modules/src/modules/emotion/index.ts
deleted file mode 100644
index 0f0bf453e..000000000
--- a/packages/basic-modules/src/modules/emotion/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description emotion entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { emotionMenuConf } from './menu/index'
-
-const emotion: Partial<IModuleConf> = {
-  menus: [emotionMenuConf],
-}
-
-export default emotion
diff --git a/packages/basic-modules/src/modules/emotion/menu/EmotionMenu.ts b/packages/basic-modules/src/modules/emotion/menu/EmotionMenu.ts
deleted file mode 100644
index f86848013..000000000
--- a/packages/basic-modules/src/modules/emotion/menu/EmotionMenu.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * @description emotion menu
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import { IDropPanelMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import $, { Dom7Array, DOMElement } from '../../../utils/dom'
-import { EMOTION_SVG } from '../../../constants/icon-svg'
-
-class EmotionMenu implements IDropPanelMenu {
-  readonly title = t('emotion.title')
-  readonly iconSvg = EMOTION_SVG
-  readonly tag = 'button'
-  readonly showDropPanel = true // 点击 button 时显示 dropPanel
-  private $content: Dom7Array | null = null
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    // 点击菜单时,弹出 droPanel 之前,不需要执行其他代码
-    // 此处空着即可
-  }
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 不需要 getValue
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 不需要 active
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const [match] = Editor.nodes(editor, {
-      match: n => {
-        const type = DomEditor.getNodeType(n)
-
-        if (type === 'pre') return true // 代码块
-        if (Editor.isVoid(editor, n)) return true // void node
-
-        return false
-      },
-      universal: true,
-    })
-
-    if (match) return true
-    return false
-  }
-
-  getPanelContentElem(editor: IDomEditor): DOMElement {
-    if (this.$content == null) {
-      // 第一次渲染
-      const $content = $('<ul class="w-e-panel-content-emotion"></ul>')
-
-      // 绑定事件(仅第一次绑定,不可重复绑定)
-      $content.on('click', 'li', (e: Event) => {
-        const { target } = e
-        if (target == null) return
-        e.preventDefault()
-
-        const $li = $(target)
-        const emotionStr = $li.text()
-        editor.insertText(emotionStr)
-      })
-
-      this.$content = $content
-    }
-
-    const $content = this.$content
-    if ($content == null) return document.createElement('ul')
-    $content.empty() // 清空之后再重置内容
-
-    // 获取菜单配置
-    const colorConf = editor.getMenuConfig('emotion')
-    const { emotions = [] } = colorConf
-    // 根据菜单配置生成 panel content
-    emotions.forEach((emotion: string) => {
-      const $li = $(`<li>${emotion}</li>`)
-      $content.append($li)
-    })
-
-    return $content[0]
-  }
-}
-
-export default EmotionMenu
diff --git a/packages/basic-modules/src/modules/emotion/menu/config.ts b/packages/basic-modules/src/modules/emotion/menu/config.ts
deleted file mode 100644
index f07b8a2e8..000000000
--- a/packages/basic-modules/src/modules/emotion/menu/config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @description menu config
- * @author wangfupeng
- */
-
-export function genConfig() {
-  const emotions =
-    '😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😛 😝 😜 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😢 😭 😤 😠 😡 😳 😱 😨 🤗 🤔 😶 😑 😬 🙄 😯 😴 😷 🤑 😈 🤡 💩 👻 💀 👀 👣 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🖕 ✍️ 🙏'
-  return emotions.split(' ')
-}
diff --git a/packages/basic-modules/src/modules/emotion/menu/index.ts b/packages/basic-modules/src/modules/emotion/menu/index.ts
deleted file mode 100644
index ff318d165..000000000
--- a/packages/basic-modules/src/modules/emotion/menu/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @description emotion menu
- * @author wangfupeng
- */
-
-import EmotionMenu from './EmotionMenu'
-import { genConfig } from './config'
-
-export const emotionMenuConf = {
-  key: 'emotion',
-  factory() {
-    return new EmotionMenu()
-  },
-
-  // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
-  // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
-  config: {
-    emotions: genConfig(),
-  },
-}
diff --git a/packages/basic-modules/src/modules/font-size-family/custom-types.ts b/packages/basic-modules/src/modules/font-size-family/custom-types.ts
deleted file mode 100644
index fb6c84152..000000000
--- a/packages/basic-modules/src/modules/font-size-family/custom-types.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-//【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts
-
-export type FontSizeAndFamilyText = {
-  text: string
-  fontSize?: string
-  fontFamily?: string
-}
diff --git a/packages/basic-modules/src/modules/font-size-family/index.ts b/packages/basic-modules/src/modules/font-size-family/index.ts
deleted file mode 100644
index 03307c584..000000000
--- a/packages/basic-modules/src/modules/font-size-family/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description font-size font-family
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderStyle } from './render-style'
-import { styleToHtml } from './style-to-html'
-import { preParseHtmlConf } from './pre-parse-html'
-import { parseStyleHtml } from './parse-style-html'
-import { fontSizeMenuConf, fontFamilyMenuConf } from './menu/index'
-
-const fontSizeAndFamily: Partial<IModuleConf> = {
-  renderStyle,
-  styleToHtml,
-  preParseHtml: [preParseHtmlConf],
-  parseStyleHtml,
-  menus: [fontSizeMenuConf, fontFamilyMenuConf],
-}
-
-export default fontSizeAndFamily
diff --git a/packages/basic-modules/src/modules/font-size-family/menu/BaseMenu.ts b/packages/basic-modules/src/modules/font-size-family/menu/BaseMenu.ts
deleted file mode 100644
index afb9abdaa..000000000
--- a/packages/basic-modules/src/modules/font-size-family/menu/BaseMenu.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @description header menu
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import { ISelectMenu, IDomEditor, DomEditor, IOption } from '@wangeditor/core'
-
-abstract class BaseMenu implements ISelectMenu {
-  abstract readonly title: string
-  abstract readonly iconSvg: string
-  abstract readonly mark: string // 'fontSize'/'fontFamily'
-  readonly tag = 'select'
-  readonly width = 80
-
-  abstract getOptions(editor: IDomEditor): IOption[]
-
-  isActive(editor: IDomEditor): boolean {
-    // select menu 会显示 selected value ,用不到 active
-    return false
-  }
-
-  getValue(editor: IDomEditor): string | boolean {
-    const mark = this.mark
-    const curMarks = Editor.marks(editor)
-    // @ts-ignore
-    if (curMarks && curMarks[mark]) return curMarks[mark]
-    return ''
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const mark = this.mark
-    const [match] = Editor.nodes(editor, {
-      match: n => {
-        const type = DomEditor.getNodeType(n)
-        if (type === 'pre') return true // 代码块
-        if (Editor.isVoid(editor, n)) return true // void node
-
-        return false
-      },
-      universal: true,
-    })
-
-    // 匹配到,则禁用
-    if (match) return true
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    const mark = this.mark
-    if (value) {
-      editor.addMark(mark, value)
-    } else {
-      editor.removeMark(mark)
-    }
-  }
-}
-
-export default BaseMenu
diff --git a/packages/basic-modules/src/modules/font-size-family/menu/FontFamilyMenu.ts b/packages/basic-modules/src/modules/font-size-family/menu/FontFamilyMenu.ts
deleted file mode 100644
index 0e8971ac0..000000000
--- a/packages/basic-modules/src/modules/font-size-family/menu/FontFamilyMenu.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * @description font-family menu
- * @author wangfupeng
- */
-
-import { IDomEditor, IOption, t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { FONT_FAMILY_SVG } from '../../../constants/icon-svg'
-
-class FontFamilyMenu extends BaseMenu {
-  readonly title = t('fontFamily.title')
-  readonly iconSvg = FONT_FAMILY_SVG
-  readonly mark = 'fontFamily'
-  readonly selectPanelWidth = 150
-
-  getOptions(editor: IDomEditor): IOption[] {
-    const options: IOption[] = []
-
-    // 获取配置,参考 './config.ts'
-    const { fontFamilyList = [] } = editor.getMenuConfig(this.mark)
-
-    // 生成 options
-    options.push({
-      text: t('fontFamily.default'),
-      value: '', // this.getValue(editor) 未找到结果时,会返回 '' ,正好对应到这里
-    })
-    fontFamilyList.forEach((family: string | { name: string; value: string }) => {
-      if (typeof family === 'string') {
-        options.push({
-          text: family,
-          value: family,
-          styleForRenderMenuList: { 'font-family': family },
-        })
-      } else if (typeof family === 'object') {
-        const { name, value } = family
-        options.push({
-          text: name,
-          value,
-          styleForRenderMenuList: { 'font-family': value },
-        })
-      }
-    })
-
-    // 设置 selected
-    const curValue = this.getValue(editor)
-    options.forEach(opt => {
-      if (opt.value === curValue) {
-        opt.selected = true
-      } else {
-        delete opt.selected
-      }
-    })
-
-    return options
-  }
-}
-
-export default FontFamilyMenu
diff --git a/packages/basic-modules/src/modules/font-size-family/menu/FontSizeMenu.ts b/packages/basic-modules/src/modules/font-size-family/menu/FontSizeMenu.ts
deleted file mode 100644
index b05a4fff0..000000000
--- a/packages/basic-modules/src/modules/font-size-family/menu/FontSizeMenu.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * @description font-size menu
- * @author wangfupeng
- */
-
-import { IDomEditor, IOption, t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { FONT_SIZE_SVG } from '../../../constants/icon-svg'
-
-class FontSizeMenu extends BaseMenu {
-  readonly title = t('fontSize.title')
-  readonly iconSvg = FONT_SIZE_SVG
-  readonly mark = 'fontSize'
-
-  getOptions(editor: IDomEditor): IOption[] {
-    const options: IOption[] = []
-
-    // 获取配置,参考 './config.ts'
-    const { fontSizeList = [] } = editor.getMenuConfig(this.mark)
-
-    // 生成 options
-    options.push({
-      text: t('fontSize.default'),
-      value: '', // this.getValue(editor) 未找到结果时,会返回 '' ,正好对应到这里
-    })
-    fontSizeList.forEach((size: string | { name: string; value: string }) => {
-      if (typeof size === 'string') {
-        options.push({
-          text: size,
-          value: size,
-        })
-      } else if (typeof size === 'object') {
-        const { name, value } = size
-        options.push({
-          text: name,
-          value: value,
-        })
-      }
-    })
-
-    // 设置 selected
-    const curValue = this.getValue(editor)
-    options.forEach(opt => {
-      if (opt.value === curValue) {
-        opt.selected = true
-      } else {
-        delete opt.selected
-      }
-    })
-
-    return options
-  }
-}
-
-export default FontSizeMenu
diff --git a/packages/basic-modules/src/modules/font-size-family/menu/config.ts b/packages/basic-modules/src/modules/font-size-family/menu/config.ts
deleted file mode 100644
index dfb9094ea..000000000
--- a/packages/basic-modules/src/modules/font-size-family/menu/config.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @description font-size font-family config
- * @author wangfupeng
- */
-
-export function genFontSizeConfig() {
-  const fontSizeList: Array<string | { name: string; value: string }> = [
-    // 元素支持两种形式:1. 字符串;2. { name: 'xxx', value: 'xxx' }
-    '12px',
-    { name: '13px', value: '13px' },
-    '14px',
-    '15px',
-    '16px',
-    '19px',
-    { name: '22px', value: '22px' },
-    '24px',
-    '29px',
-    '32px',
-    '40px',
-    '48px',
-  ]
-
-  return fontSizeList
-}
-
-export function getFontFamilyConfig() {
-  let fontFamilyList: Array<string | { name: string; value: string }> = [
-    // 元素支持两种形式:1. 字符串;2. { name: 'xxx', value: 'xxx' }
-    '黑体',
-    { name: '仿宋', value: '仿宋' },
-    '楷体',
-    '标楷体',
-    '华文仿宋',
-    '华文楷体',
-    { name: '宋体', value: '宋体' },
-    '微软雅黑',
-    'Arial',
-    'Tahoma',
-    'Verdana',
-    'Times New Roman',
-    'Courier New',
-  ]
-
-  return fontFamilyList
-}
diff --git a/packages/basic-modules/src/modules/font-size-family/menu/index.ts b/packages/basic-modules/src/modules/font-size-family/menu/index.ts
deleted file mode 100644
index 727599959..000000000
--- a/packages/basic-modules/src/modules/font-size-family/menu/index.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @description font-size font-family menu entry
- * @author wangfupeng
- */
-
-import FontSizeMenu from './FontSizeMenu'
-import FontFamilyMenu from './FontFamilyMenu'
-import { genFontSizeConfig, getFontFamilyConfig } from './config'
-
-export const fontSizeMenuConf = {
-  key: 'fontSize',
-  factory() {
-    return new FontSizeMenu()
-  },
-
-  // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
-  // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
-  config: {
-    fontSizeList: genFontSizeConfig(),
-  },
-}
-
-export const fontFamilyMenuConf = {
-  key: 'fontFamily',
-  factory() {
-    return new FontFamilyMenu()
-  },
-  config: {
-    fontFamilyList: getFontFamilyConfig(),
-  },
-}
diff --git a/packages/basic-modules/src/modules/font-size-family/parse-style-html.ts b/packages/basic-modules/src/modules/font-size-family/parse-style-html.ts
deleted file mode 100644
index 5efa7cf5b..000000000
--- a/packages/basic-modules/src/modules/font-size-family/parse-style-html.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @description parse style html
- * @author wangfupeng
- */
-
-import { Descendant, Text } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { FontSizeAndFamilyText } from './custom-types'
-import $, { DOMElement, getStyleValue } from '../../utils/dom'
-
-export function parseStyleHtml(text: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
-  const $text = $(text)
-  if (!Text.isText(node)) return node
-
-  const textNode = node as FontSizeAndFamilyText
-
-  // -------- 处理 font-size --------
-  const { fontSizeList = [] } = editor.getMenuConfig('fontSize')
-  const fontSize = getStyleValue($text, 'font-size')
-
-  const includesSize =
-    fontSizeList.find(item => item.value && item.value === fontSize) ||
-    fontSizeList.includes(fontSize)
-
-  if (fontSize && includesSize) {
-    textNode.fontSize = fontSize
-  }
-
-  // -------- 处理 font-family --------
-  const { fontFamilyList = [] } = editor.getMenuConfig('fontFamily')
-  // 这里需要替换掉 ", css 设置 font-family,会将有空格的字体使用 " 包裹
-  const fontFamily = getStyleValue($text, 'font-family').replace(/"/g, '')
-
-  // getFontFamilyConfig 配置支持对象形式
-  const includesFamily =
-    fontFamilyList.find(item => item.value && item.value === fontFamily) ||
-    fontFamilyList.includes(fontFamily)
-
-  if (fontFamily && includesFamily) {
-    textNode.fontFamily = fontFamily
-  }
-
-  return textNode
-}
diff --git a/packages/basic-modules/src/modules/font-size-family/pre-parse-html.ts b/packages/basic-modules/src/modules/font-size-family/pre-parse-html.ts
deleted file mode 100644
index c99ce7737..000000000
--- a/packages/basic-modules/src/modules/font-size-family/pre-parse-html.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * @description pre-parse html
- * @author wangfupeng
- */
-
-import $, { DOMElement, getTagName } from '../../utils/dom'
-
-// V4 font-size 对应关系(V4 使用 <font size="1">xxx</font> 格式)
-const FONT_SIZE_MAP_FOR_V4 = {
-  '1': '12px',
-  '2': '14px',
-  '3': '16px',
-  '4': '19px',
-  '5': '24px',
-  '6': '32px',
-  '7': '48px',
-}
-
-/**
- * pre-prase font ,兼容 V4
- * @param fontElem fontElem
- */
-function preParse(fontElem: DOMElement): DOMElement {
-  const $font = $(fontElem)
-  const tagName = getTagName($font)
-  if (tagName !== 'font') return fontElem
-
-  // 处理 size (V4 使用 <font size="1">xxx</font> 格式)
-  const size = $font.attr('size') || ''
-  if (size) {
-    $font.removeAttr('size')
-    $font.css('font-size', FONT_SIZE_MAP_FOR_V4[size])
-  }
-
-  // 处理 face (V4 使用 <font face="黑体">xx</font> 格式)
-  const face = $font.attr('face') || ''
-  if (face) {
-    $font.removeAttr('face')
-    $font.css('font-family', face)
-  }
-
-  return $font[0]
-}
-
-export const preParseHtmlConf = {
-  selector: 'font',
-  preParseHtml: preParse,
-}
diff --git a/packages/basic-modules/src/modules/font-size-family/render-style.tsx b/packages/basic-modules/src/modules/font-size-family/render-style.tsx
deleted file mode 100644
index e0d31eece..000000000
--- a/packages/basic-modules/src/modules/font-size-family/render-style.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @description render font-size font-family style
- * @author wangfupeng
- */
-
-import { Descendant } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { addVnodeStyle } from '../../utils/vdom'
-import { FontSizeAndFamilyText } from './custom-types'
-
-/**
- * 添加样式
- * @param node slate elem
- * @param vnode vnode
- * @returns vnode
- */
-export function renderStyle(node: Descendant, vnode: VNode): VNode {
-  const { fontSize, fontFamily } = node as FontSizeAndFamilyText
-  let styleVnode: VNode = vnode
-
-  if (fontSize) {
-    addVnodeStyle(styleVnode, { fontSize })
-  }
-  if (fontFamily) {
-    addVnodeStyle(styleVnode, { fontFamily })
-  }
-
-  return styleVnode
-}
diff --git a/packages/basic-modules/src/modules/font-size-family/style-to-html.ts b/packages/basic-modules/src/modules/font-size-family/style-to-html.ts
deleted file mode 100644
index dd74d829d..000000000
--- a/packages/basic-modules/src/modules/font-size-family/style-to-html.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * @description textStyle to html
- * @author wangfupeng
- */
-
-import { Text, Descendant } from 'slate'
-import $, { getOuterHTML, getTagName, isPlainText } from '../../utils/dom'
-import { FontSizeAndFamilyText } from './custom-types'
-
-/**
- * style to html
- * @param textNode slate text node
- * @param textHtml text html
- * @returns styled html
- */
-export function styleToHtml(textNode: Descendant, textHtml: string): string {
-  if (!Text.isText(textNode)) return textHtml
-
-  const { fontSize, fontFamily } = textNode as FontSizeAndFamilyText
-  if (!fontSize && !fontFamily) return textHtml
-
-  let $text
-
-  if (isPlainText(textHtml)) {
-    // textHtml 是纯文本,不是 html tag
-    $text = $(`<span>${textHtml}</span>`)
-  } else {
-    // textHtml 是 html tag
-    $text = $(textHtml)
-    const tagName = getTagName($text)
-    if (tagName !== 'span') {
-      // 如果不是 span ,则包裹一层,接下来要设置 css
-      $text = $(`<span>${textHtml}</span>`)
-    }
-  }
-
-  if (fontSize) $text.css('font-size', fontSize)
-  if (fontFamily) $text.css('font-family', fontFamily)
-
-  return getOuterHTML($text)
-}
diff --git a/packages/basic-modules/src/modules/full-screen/index.ts b/packages/basic-modules/src/modules/full-screen/index.ts
deleted file mode 100644
index 8ced1e536..000000000
--- a/packages/basic-modules/src/modules/full-screen/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description 全屏
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { fullScreenConf } from './menu/index'
-
-const fullScreen: Partial<IModuleConf> = {
-  menus: [fullScreenConf],
-}
-
-export default fullScreen
diff --git a/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts b/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts
deleted file mode 100644
index a013f5bd8..000000000
--- a/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @description redo menu
- * @author wangfupeng
- */
-
-import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
-import { FULL_SCREEN_SVG } from '../../../constants/icon-svg'
-
-class FullScreen implements IButtonMenu {
-  title = t('fullScreen.title')
-  iconSvg = FULL_SCREEN_SVG
-  tag = 'button'
-  alwaysEnable = true
-
-  getValue(editor: IDomEditor): string | boolean {
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    return editor.isFullScreen
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (editor.isFullScreen) {
-      editor.unFullScreen()
-    } else {
-      editor.fullScreen()
-    }
-  }
-}
-
-export default FullScreen
diff --git a/packages/basic-modules/src/modules/full-screen/menu/index.ts b/packages/basic-modules/src/modules/full-screen/menu/index.ts
deleted file mode 100644
index a5e98e2b2..000000000
--- a/packages/basic-modules/src/modules/full-screen/menu/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description menu entry
- * @author wangfupeng
- */
-
-import FullScreen from './FullScreen'
-
-export const fullScreenConf = {
-  key: 'fullScreen',
-  factory() {
-    return new FullScreen()
-  },
-}
diff --git a/packages/basic-modules/src/modules/header/custom-types.ts b/packages/basic-modules/src/modules/header/custom-types.ts
deleted file mode 100644
index c57813d76..000000000
--- a/packages/basic-modules/src/modules/header/custom-types.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-export type Header1Element = {
-  type: 'header1'
-  children: Text[]
-}
-
-export type Header2Element = {
-  type: 'header2'
-  children: Text[]
-}
-
-export type Header3Element = {
-  type: 'header3'
-  children: Text[]
-}
-
-export type Header4Element = {
-  type: 'header4'
-  children: Text[]
-}
-
-export type Header5Element = {
-  type: 'header5'
-  children: Text[]
-}
diff --git a/packages/basic-modules/src/modules/header/elem-to-html.ts b/packages/basic-modules/src/modules/header/elem-to-html.ts
deleted file mode 100644
index 1f7687e56..000000000
--- a/packages/basic-modules/src/modules/header/elem-to-html.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * @description to html
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-
-function genToHtmlFn(level: number) {
-  function headerToHtml(elem: Element, childrenHtml: string): string {
-    return `<h${level}>${childrenHtml}</h${level}>`
-  }
-  return headerToHtml
-}
-
-export const header1ToHtmlConf = {
-  type: 'header1',
-  elemToHtml: genToHtmlFn(1),
-}
-
-export const header2ToHtmlConf = {
-  type: 'header2',
-  elemToHtml: genToHtmlFn(2),
-}
-
-export const header3ToHtmlConf = {
-  type: 'header3',
-  elemToHtml: genToHtmlFn(3),
-}
-
-export const header4ToHtmlConf = {
-  type: 'header4',
-  elemToHtml: genToHtmlFn(4),
-}
-
-export const header5ToHtmlConf = {
-  type: 'header5',
-  elemToHtml: genToHtmlFn(5),
-}
diff --git a/packages/basic-modules/src/modules/header/helper.ts b/packages/basic-modules/src/modules/header/helper.ts
deleted file mode 100644
index 5337dece9..000000000
--- a/packages/basic-modules/src/modules/header/helper.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * @description header helper
- * @author wangfupeng
- */
-
-import { Editor, Transforms } from 'slate'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-
-/**
- * 获取 node type('header1' 'header2' 等),未匹配则返回 'paragraph'
- */
-export function getHeaderType(editor: IDomEditor): string {
-  const [match] = Editor.nodes(editor, {
-    match: n => {
-      const type = DomEditor.getNodeType(n)
-      return type.startsWith('header') // 匹配 node.type 是 header 开头的 node
-    },
-    universal: true,
-  })
-
-  // 未匹配到 header
-  if (match == null) return 'paragraph'
-
-  // 匹配到 header
-  const [n] = match
-
-  return DomEditor.getNodeType(n)
-}
-
-export function isMenuDisabled(editor: IDomEditor): boolean {
-  if (editor.selection == null) return true
-
-  const [nodeEntry] = Editor.nodes(editor, {
-    match: n => {
-      const type = DomEditor.getNodeType(n)
-
-      // 只可用于 p 和 header
-      if (type === 'paragraph') return true
-      if (type.startsWith('header')) return true
-
-      return false
-    },
-    universal: true,
-    mode: 'highest', // 匹配最高层级
-  })
-
-  // 匹配到 p header ,不禁用
-  if (nodeEntry) {
-    return false
-  }
-  // 未匹配到 p header ,则禁用
-  return true
-}
-
-/**
- * 设置 node type ('header1' 'header2' 'paragraph' 等)
- */
-export function setHeaderType(editor: IDomEditor, type: string) {
-  if (!type) return
-
-  // 执行命令
-  Transforms.setNodes(editor, {
-    type: type,
-  })
-}
diff --git a/packages/basic-modules/src/modules/header/index.ts b/packages/basic-modules/src/modules/header/index.ts
deleted file mode 100644
index 46e875ec9..000000000
--- a/packages/basic-modules/src/modules/header/index.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * @description header entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import {
-  renderHeader1Conf,
-  renderHeader2Conf,
-  renderHeader3Conf,
-  renderHeader4Conf,
-  renderHeader5Conf,
-} from './render-elem'
-import {
-  HeaderSelectMenuConf,
-  Header1ButtonMenuConf,
-  Header2ButtonMenuConf,
-  Header3ButtonMenuConf,
-  Header4ButtonMenuConf,
-  Header5ButtonMenuConf,
-} from './menu/index'
-import {
-  header1ToHtmlConf,
-  header2ToHtmlConf,
-  header3ToHtmlConf,
-  header4ToHtmlConf,
-  header5ToHtmlConf,
-} from './elem-to-html'
-import {
-  parseHeader1HtmlConf,
-  parseHeader2HtmlConf,
-  parseHeader3HtmlConf,
-  parseHeader4HtmlConf,
-  parseHeader5HtmlConf,
-} from './parse-elem-html'
-import withHeader from './plugin'
-
-const header: Partial<IModuleConf> = {
-  renderElems: [
-    renderHeader1Conf,
-    renderHeader2Conf,
-    renderHeader3Conf,
-    renderHeader4Conf,
-    renderHeader5Conf,
-  ],
-  elemsToHtml: [
-    header1ToHtmlConf,
-    header2ToHtmlConf,
-    header3ToHtmlConf,
-    header4ToHtmlConf,
-    header5ToHtmlConf,
-  ],
-  parseElemsHtml: [
-    parseHeader1HtmlConf,
-    parseHeader2HtmlConf,
-    parseHeader3HtmlConf,
-    parseHeader4HtmlConf,
-    parseHeader5HtmlConf,
-  ],
-  menus: [
-    HeaderSelectMenuConf,
-    Header1ButtonMenuConf,
-    Header2ButtonMenuConf,
-    Header3ButtonMenuConf,
-    Header4ButtonMenuConf,
-    Header5ButtonMenuConf,
-  ],
-  editorPlugin: withHeader,
-}
-
-export default header
diff --git a/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts
deleted file mode 100644
index 89426b5fc..000000000
--- a/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description header1 button menu
- * @author wangfupeng
- */
-
-import HeaderButtonMenuBase from './HeaderButtonMenuBase'
-
-class Header1ButtonMenu extends HeaderButtonMenuBase {
-  title = 'H1'
-  type = 'header1'
-}
-
-export default Header1ButtonMenu
diff --git a/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts
deleted file mode 100644
index 28fd480f1..000000000
--- a/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description header2 button menu
- * @author wangfupeng
- */
-
-import HeaderButtonMenuBase from './HeaderButtonMenuBase'
-
-class Header2ButtonMenu extends HeaderButtonMenuBase {
-  title = 'H2'
-  type = 'header2'
-}
-
-export default Header2ButtonMenu
diff --git a/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts
deleted file mode 100644
index c8915421f..000000000
--- a/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description header3 button menu
- * @author wangfupeng
- */
-
-import HeaderButtonMenuBase from './HeaderButtonMenuBase'
-
-class Header3ButtonMenu extends HeaderButtonMenuBase {
-  title = 'H3'
-  type = 'header3'
-}
-
-export default Header3ButtonMenu
diff --git a/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts
deleted file mode 100644
index ef8ba304f..000000000
--- a/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description header4 button menu
- * @author wangfupeng
- */
-
-import HeaderButtonMenuBase from './HeaderButtonMenuBase'
-
-class Header4ButtonMenu extends HeaderButtonMenuBase {
-  title = 'H4'
-  type = 'header4'
-}
-
-export default Header4ButtonMenu
diff --git a/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts
deleted file mode 100644
index 9cff14a80..000000000
--- a/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description header5 button menu
- * @author wangfupeng
- */
-
-import HeaderButtonMenuBase from './HeaderButtonMenuBase'
-
-class Header5ButtonMenu extends HeaderButtonMenuBase {
-  title = 'H5'
-  type = 'header5'
-}
-
-export default Header5ButtonMenu
diff --git a/packages/basic-modules/src/modules/header/menu/HeaderButtonMenuBase.ts b/packages/basic-modules/src/modules/header/menu/HeaderButtonMenuBase.ts
deleted file mode 100644
index 76ae63af5..000000000
--- a/packages/basic-modules/src/modules/header/menu/HeaderButtonMenuBase.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @description button menu base
- * @author wangfupeng
- */
-
-import { IButtonMenu, IDomEditor } from '@wangeditor/core'
-import { getHeaderType, isMenuDisabled, setHeaderType } from '../helper'
-
-abstract class HeaderButtonMenuBase implements IButtonMenu {
-  abstract readonly title: string
-  abstract readonly type: string // 'header1' 'header2' 等
-  readonly tag = 'button'
-
-  /**
-   * 获取选中节点的 node.type
-   * @param editor editor
-   */
-  getValue(editor: IDomEditor): string | boolean {
-    return getHeaderType(editor)
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    return this.getValue(editor) === this.type
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    return isMenuDisabled(editor)
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    const { type } = this
-    let newType
-    if (value === type) {
-      // 选中的 node.type 和当前 type 一样,则取消
-      newType = 'paragraph'
-    } else {
-      // 否则,则设置
-      newType = type
-    }
-
-    setHeaderType(editor, newType)
-  }
-}
-
-export default HeaderButtonMenuBase
diff --git a/packages/basic-modules/src/modules/header/menu/HeaderSelectMenu.ts b/packages/basic-modules/src/modules/header/menu/HeaderSelectMenu.ts
deleted file mode 100644
index a79031d15..000000000
--- a/packages/basic-modules/src/modules/header/menu/HeaderSelectMenu.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * @description header menu
- * @author wangfupeng
- */
-
-import { ISelectMenu, IDomEditor, IOption, t } from '@wangeditor/core'
-import { HEADER_SVG } from '../../../constants/icon-svg'
-import { getHeaderType, isMenuDisabled, setHeaderType } from '../helper'
-
-class HeaderSelectMenu implements ISelectMenu {
-  readonly title = t('header.title')
-  readonly iconSvg = HEADER_SVG
-  readonly tag = 'select'
-  readonly width = 60
-
-  getOptions(editor: IDomEditor): IOption[] {
-    // 基本的 options 列表
-    const options = [
-      // value 和 elemNode.type 对应
-      {
-        value: 'header1',
-        text: 'H1',
-        styleForRenderMenuList: { 'font-size': '32px', 'font-weight': 'bold' },
-      },
-      {
-        value: 'header2',
-        text: 'H2',
-        styleForRenderMenuList: { 'font-size': '24px', 'font-weight': 'bold' },
-      },
-      {
-        value: 'header3',
-        text: 'H3',
-        styleForRenderMenuList: { 'font-size': '18px', 'font-weight': 'bold' },
-      },
-      {
-        value: 'header4',
-        text: 'H4',
-        styleForRenderMenuList: { 'font-size': '16px', 'font-weight': 'bold' },
-      },
-      {
-        value: 'header5',
-        text: 'H5',
-        styleForRenderMenuList: { 'font-size': '13px', 'font-weight': 'bold' },
-      },
-      { value: 'paragraph', text: t('header.text') },
-    ]
-
-    // 获取 value ,设置 selected
-    const curValue = this.getValue(editor).toString()
-    options.forEach((opt: IOption) => {
-      if (opt.value === curValue) {
-        opt.selected = true
-      } else {
-        delete opt.selected
-      }
-    })
-
-    return options
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // select menu 会显示 selected value ,用不到 active
-    return false
-  }
-
-  /**
-   * 获取选中节点的 node.type
-   * @param editor editor
-   */
-  getValue(editor: IDomEditor): string | boolean {
-    return getHeaderType(editor)
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    return isMenuDisabled(editor)
-  }
-
-  /**
-   * 执行命令
-   * @param editor editor
-   * @param value node.type
-   */
-  exec(editor: IDomEditor, value: string | boolean) {
-    //【注意】value 是 select change 时获取的,并不是 this.getValue 的值
-    setHeaderType(editor, value.toString())
-  }
-}
-
-export default HeaderSelectMenu
diff --git a/packages/basic-modules/src/modules/header/menu/index.ts b/packages/basic-modules/src/modules/header/menu/index.ts
deleted file mode 100644
index e63d1ff8d..000000000
--- a/packages/basic-modules/src/modules/header/menu/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * @description menu entry
- * @author wangfupeng
- */
-
-import HeaderSelectMenu from './HeaderSelectMenu'
-import Header1ButtonMenu from './Header1ButtonMenu'
-import Header2ButtonMenu from './Header2ButtonMenu'
-import Header3ButtonMenu from './Header3ButtonMenu'
-import Header4ButtonMenu from './Header4ButtonMenu'
-import Header5ButtonMenu from './Header5ButtonMenu'
-
-export const HeaderSelectMenuConf = {
-  key: 'headerSelect',
-  factory() {
-    return new HeaderSelectMenu()
-  },
-}
-
-export const Header1ButtonMenuConf = {
-  key: 'header1',
-  factory() {
-    return new Header1ButtonMenu()
-  },
-}
-
-export const Header2ButtonMenuConf = {
-  key: 'header2',
-  factory() {
-    return new Header2ButtonMenu()
-  },
-}
-
-export const Header3ButtonMenuConf = {
-  key: 'header3',
-  factory() {
-    return new Header3ButtonMenu()
-  },
-}
-
-export const Header4ButtonMenuConf = {
-  key: 'header4',
-  factory() {
-    return new Header4ButtonMenu()
-  },
-}
-
-export const Header5ButtonMenuConf = {
-  key: 'header5',
-  factory() {
-    return new Header5ButtonMenu()
-  },
-}
diff --git a/packages/basic-modules/src/modules/header/parse-elem-html.ts b/packages/basic-modules/src/modules/header/parse-elem-html.ts
deleted file mode 100644
index bac537f55..000000000
--- a/packages/basic-modules/src/modules/header/parse-elem-html.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @description parse html
- * @author wangfupeng
- */
-
-import { Descendant, Text } from 'slate'
-import $, { DOMElement } from '../../utils/dom'
-import { IDomEditor } from '@wangeditor/core'
-import {
-  Header1Element,
-  Header2Element,
-  Header3Element,
-  Header4Element,
-  Header5Element,
-} from './custom-types'
-
-function genParser<T>(level: number) {
-  function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): T {
-    const $elem = $(elem)
-    children = children.filter(child => {
-      if (Text.isText(child)) return true
-      if (editor.isInline(child)) return true
-      return false
-    })
-
-    // 无 children ,则用纯文本
-    if (children.length === 0) {
-      children = [{ text: $elem.text().replace(/\s+/gm, ' ') }]
-    }
-
-    const headerNode = {
-      type: `header${level}`,
-      children,
-    } as unknown as T
-
-    return headerNode
-  }
-  return parseHtml
-}
-
-export const parseHeader1HtmlConf = {
-  selector: 'h1:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: genParser<Header1Element>(1),
-}
-
-export const parseHeader2HtmlConf = {
-  selector: 'h2:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: genParser<Header2Element>(2),
-}
-
-export const parseHeader3HtmlConf = {
-  selector: 'h3:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: genParser<Header3Element>(3),
-}
-
-export const parseHeader4HtmlConf = {
-  selector: 'h4:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: genParser<Header4Element>(4),
-}
-
-export const parseHeader5HtmlConf = {
-  selector: 'h5:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: genParser<Header5Element>(5),
-}
diff --git a/packages/basic-modules/src/modules/header/plugin.ts b/packages/basic-modules/src/modules/header/plugin.ts
deleted file mode 100644
index f96731def..000000000
--- a/packages/basic-modules/src/modules/header/plugin.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @description editor 插件,重写 editor API
- * @author wangfupeng
- */
-
-import { Editor, Transforms } from 'slate'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-
-function withHeader<T extends IDomEditor>(editor: T): T {
-  const { insertBreak, insertNode } = editor
-  const newEditor = editor
-
-  // 重写 insertBreak - header 末尾回车时要插入 paragraph
-  newEditor.insertBreak = () => {
-    const [match] = Editor.nodes(newEditor, {
-      match: n => {
-        const type = DomEditor.getNodeType(n)
-        return type.startsWith('header') // 匹配 node.type 是 header 开头的 node
-      },
-      universal: true,
-    })
-
-    if (!match) {
-      // 未匹配到
-      insertBreak()
-      return
-    }
-
-    const isAtLineEnd = DomEditor.isSelectionAtLineEnd(editor, match[1])
-
-    // 如果在行末插入一个空 p,否则正常换行
-    if (isAtLineEnd) {
-      const p = { type: 'paragraph', children: [{ text: '' }] }
-      Transforms.insertNodes(newEditor, p, { mode: 'highest' })
-    } else {
-      insertBreak()
-    }
-  }
-
-  // 返回 editor ,重要!
-  return newEditor
-}
-
-export default withHeader
diff --git a/packages/basic-modules/src/modules/header/render-elem.tsx b/packages/basic-modules/src/modules/header/render-elem.tsx
deleted file mode 100644
index 84cbf4bf8..000000000
--- a/packages/basic-modules/src/modules/header/render-elem.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * @description render header
- * @author wangfupeng
- */
-
-import { Element as SlateElement } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { IDomEditor } from '@wangeditor/core'
-
-function genRenderElem(level: number) {
-  /**
-   * render header elem
-   * @param elemNode slate elem
-   * @param children children
-   * @param editor editor
-   * @returns vnode
-   */
-  function renderHeader(
-    elemNode: SlateElement,
-    children: VNode[] | null,
-    editor: IDomEditor
-  ): VNode {
-    const Tag = `h${level}`
-    const vnode = <Tag>{children}</Tag>
-    return vnode
-  }
-
-  return renderHeader
-}
-
-const renderHeader1Conf = {
-  type: 'header1', // 和 elemNode.type 一致
-  renderElem: genRenderElem(1),
-}
-const renderHeader2Conf = {
-  type: 'header2',
-  renderElem: genRenderElem(2),
-}
-const renderHeader3Conf = {
-  type: 'header3',
-  renderElem: genRenderElem(3),
-}
-const renderHeader4Conf = {
-  type: 'header4',
-  renderElem: genRenderElem(4),
-}
-const renderHeader5Conf = {
-  type: 'header5',
-  renderElem: genRenderElem(5),
-}
-
-export {
-  renderHeader1Conf,
-  renderHeader2Conf,
-  renderHeader3Conf,
-  renderHeader4Conf,
-  renderHeader5Conf,
-}
diff --git a/packages/basic-modules/src/modules/image/custom-types.ts b/packages/basic-modules/src/modules/image/custom-types.ts
deleted file mode 100644
index 08abfcead..000000000
--- a/packages/basic-modules/src/modules/image/custom-types.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @description image element
- * @author wangfupeng
- */
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-type EmptyText = {
-  text: ''
-}
-
-export type ImageStyle = {
-  width?: string
-  height?: string
-}
-
-export type ImageElement = {
-  type: 'image'
-  src: string
-  alt?: string
-  href?: string
-  style?: ImageStyle
-  children: EmptyText[]
-}
diff --git a/packages/basic-modules/src/modules/image/elem-to-html.ts b/packages/basic-modules/src/modules/image/elem-to-html.ts
deleted file mode 100644
index 35798dd1a..000000000
--- a/packages/basic-modules/src/modules/image/elem-to-html.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @description to html
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-import { ImageElement } from './custom-types'
-
-function imageToHtml(elemNode: Element, childrenHtml: string): string {
-  const { src, alt = '', href = '', style = {} } = elemNode as ImageElement
-  const { width = '', height = '' } = style
-
-  let styleStr = ''
-  if (width) styleStr += `width: ${width};`
-  if (height) styleStr += `height: ${height};`
-  return `<img src="${src}" alt="${alt}" data-href="${href}" style="${styleStr}"/>`
-}
-
-export const imageToHtmlConf = {
-  type: 'image',
-  elemToHtml: imageToHtml,
-}
diff --git a/packages/basic-modules/src/modules/image/helper.ts b/packages/basic-modules/src/modules/image/helper.ts
deleted file mode 100644
index 01ea745b0..000000000
--- a/packages/basic-modules/src/modules/image/helper.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * @description image menu helper
- * @author wangfupeng
- */
-
-import { Transforms, Range, Editor } from 'slate'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-import { ImageElement, ImageStyle } from './custom-types'
-import { replaceSymbols } from '../../utils/util'
-
-async function check(
-  menuKey: string,
-  editor: IDomEditor,
-  src: string,
-  alt: string = '',
-  href: string = ''
-): Promise<boolean> {
-  const { checkImage } = editor.getMenuConfig(menuKey)
-  if (checkImage) {
-    const res = await checkImage(src, alt, href)
-    if (typeof res === 'string') {
-      // 检验未通过,提示信息
-      editor.alert(res, 'error')
-      return false
-    }
-    if (res == null) {
-      // 检验未通过,不提示信息
-      return false
-    }
-  }
-
-  return true
-}
-
-async function parseSrc(menuKey: string, editor: IDomEditor, src: string): Promise<string> {
-  const { parseImageSrc } = editor.getMenuConfig(menuKey)
-  if (parseImageSrc) {
-    const newSrc = await parseImageSrc(src)
-    return newSrc
-  }
-  return src
-}
-
-export async function insertImageNode(
-  editor: IDomEditor,
-  src: string,
-  alt: string = '',
-  href: string = ''
-) {
-  const res = await check('insertImage', editor, src, alt, href)
-  if (!res) return // 检查失败,终止操作
-
-  const parsedSrc = await parseSrc('insertImage', editor, src)
-
-  // 新建一个 image node
-  const image: ImageElement = {
-    type: 'image',
-    src: replaceSymbols(parsedSrc),
-    href,
-    alt,
-    style: {},
-    children: [{ text: '' }], // 【注意】void node 需要一个空 text 作为 children
-  }
-
-  // 如果 blur ,则恢复选区
-  if (editor.selection === null) editor.restoreSelection()
-
-  // 如果当前正好选中了图片,则 move 一下(如:连续上传多张图片时)
-  if (DomEditor.getSelectedNodeByType(editor, 'image')) {
-    editor.move(1)
-  }
-
-  if (isInsertImageMenuDisabled(editor)) return
-
-  // 插入图片
-  Transforms.insertNodes(editor, image)
-
-  // 回调
-  const { onInsertedImage } = editor.getMenuConfig('insertImage')
-  if (onInsertedImage) onInsertedImage(image)
-}
-
-export async function updateImageNode(
-  editor: IDomEditor,
-  src: string,
-  alt: string = '',
-  href: string = '',
-  style: ImageStyle = {}
-) {
-  const res = await check('editImage', editor, src, alt, href)
-  if (!res) return // 检查失败,终止操作
-
-  const parsedSrc = await parseSrc('editImage', editor, src)
-
-  const selectedImageNode = DomEditor.getSelectedNodeByType(editor, 'image')
-  if (selectedImageNode == null) return
-  const { style: curStyle = {} } = selectedImageNode as ImageElement
-
-  // 修改图片
-  const nodeProps: Partial<ImageElement> = {
-    src: parsedSrc,
-    alt,
-    href,
-    style: {
-      ...curStyle,
-      ...style,
-    },
-  }
-  Transforms.setNodes(editor, nodeProps, {
-    match: n => DomEditor.checkNodeType(n, 'image'),
-  })
-
-  // 回调
-  const imageNode = DomEditor.getSelectedNodeByType(editor, 'image')
-  const { onUpdatedImage } = editor.getMenuConfig('editImage')
-  if (onUpdatedImage) onUpdatedImage(imageNode)
-}
-
-/**
- * 判断菜单是否要 disabled
- * @param editor editor
- */
-export function isInsertImageMenuDisabled(editor: IDomEditor): boolean {
-  const { selection } = editor
-  if (selection == null) return true
-  if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用
-
-  const [match] = Editor.nodes(editor, {
-    match: n => {
-      const type = DomEditor.getNodeType(n)
-
-      if (type === 'code') return true // 代码块
-      if (type === 'pre') return true // 代码块
-      if (type === 'link') return true // 链接
-      if (type === 'list-item') return true // list
-      if (type.startsWith('header')) return true // 标题
-      if (type === 'blockquote') return true // 引用
-      if (Editor.isVoid(editor, n)) return true // void
-
-      return false
-    },
-    universal: true,
-  })
-
-  if (match) return true
-  return false
-}
diff --git a/packages/basic-modules/src/modules/image/index.ts b/packages/basic-modules/src/modules/image/index.ts
deleted file mode 100644
index f22b1bfd8..000000000
--- a/packages/basic-modules/src/modules/image/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @description image module entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import withImage from './plugin'
-import { renderImageConf } from './render-elem'
-import { imageToHtmlConf } from './elem-to-html'
-import { parseHtmlConf } from './parse-elem-html'
-import {
-  insertImageMenuConf,
-  deleteImageMenuConf,
-  editImageMenuConf,
-  viewImageLinkMenuConf,
-  imageWidth30MenuConf,
-  imageWidth50MenuConf,
-  imageWidth100MenuConf,
-} from './menu/index'
-
-const image: Partial<IModuleConf> = {
-  renderElems: [renderImageConf],
-  elemsToHtml: [imageToHtmlConf],
-  parseElemsHtml: [parseHtmlConf],
-  menus: [
-    insertImageMenuConf,
-    deleteImageMenuConf,
-    editImageMenuConf,
-    viewImageLinkMenuConf,
-    imageWidth30MenuConf,
-    imageWidth50MenuConf,
-    imageWidth100MenuConf,
-  ],
-  editorPlugin: withImage,
-}
-
-export default image
diff --git a/packages/basic-modules/src/modules/image/menu/DeleteImage.ts b/packages/basic-modules/src/modules/image/menu/DeleteImage.ts
deleted file mode 100644
index 60dddff33..000000000
--- a/packages/basic-modules/src/modules/image/menu/DeleteImage.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * @description delete image menu
- * @author wangfupeng
- */
-
-import { Transforms } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import { TRASH_SVG } from '../../../constants/icon-svg'
-
-class DeleteImage implements IButtonMenu {
-  readonly title = t('image.delete')
-  readonly iconSvg = TRASH_SVG
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 无需获取 val
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const imageNode = DomEditor.getSelectedNodeByType(editor, 'image')
-    if (imageNode == null) {
-      // 选区未处于 image node ,则禁用
-      return true
-    }
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (this.isDisabled(editor)) return
-
-    // 删除图片
-    Transforms.removeNodes(editor, {
-      match: n => DomEditor.checkNodeType(n, 'image'),
-    })
-  }
-}
-
-export default DeleteImage
diff --git a/packages/basic-modules/src/modules/image/menu/EditImage.ts b/packages/basic-modules/src/modules/image/menu/EditImage.ts
deleted file mode 100644
index 81b8410ad..000000000
--- a/packages/basic-modules/src/modules/image/menu/EditImage.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- * @description editor image menu
- * @author wangfupeng
- */
-
-import { Node, Range } from 'slate'
-import {
-  IModalMenu,
-  IDomEditor,
-  DomEditor,
-  genModalInputElems,
-  genModalButtonElems,
-  t,
-} from '@wangeditor/core'
-import $, { Dom7Array, DOMElement } from '../../../utils/dom'
-import { genRandomStr } from '../../../utils/util'
-import { PENCIL_SVG } from '../../../constants/icon-svg'
-import { updateImageNode } from '../helper'
-import { ImageElement, ImageStyle } from '../custom-types'
-
-/**
- * 生成唯一的 DOM ID
- */
-function genDomID(): string {
-  return genRandomStr('w-e-edit-image')
-}
-
-class EditImage implements IModalMenu {
-  readonly title = t('image.edit')
-  readonly iconSvg = PENCIL_SVG
-  readonly tag = 'button'
-  readonly showModal = true // 点击 button 时显示 modal
-  readonly modalWidth = 300
-  private $content: Dom7Array | null = null
-  private readonly srcInputId = genDomID()
-  private readonly altInputId = genDomID()
-  private readonly hrefInputId = genDomID()
-  private readonly buttonId = genDomID()
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 编辑图片,用不到 getValue
-    return ''
-  }
-
-  private getImageNode(editor: IDomEditor): Node | null {
-    return DomEditor.getSelectedNodeByType(editor, 'image')
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    // 点击菜单时,弹出 modal 之前,不需要执行其他代码
-    // 此处空着即可
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    const { selection } = editor
-    if (selection == null) return true
-    if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用
-
-    const imageNode = DomEditor.getSelectedNodeByType(editor, 'image')
-
-    // 未匹配到 image node 则禁用
-    if (imageNode == null) return true
-    return false
-  }
-
-  getModalPositionNode(editor: IDomEditor): Node | null {
-    return this.getImageNode(editor)
-  }
-
-  getModalContentElem(editor: IDomEditor): DOMElement {
-    const { srcInputId, altInputId, hrefInputId, buttonId } = this
-
-    const selectedImageNode = this.getImageNode(editor)
-    if (selectedImageNode == null) {
-      throw new Error('Not found selected image node')
-    }
-
-    // 获取 input button elem
-    const [srcContainerElem, inputSrcElem] = genModalInputElems(t('image.src'), srcInputId)
-    const $inputSrc = $(inputSrcElem)
-    const [altContainerElem, inputAltElem] = genModalInputElems(t('image.desc'), altInputId)
-    const $inputAlt = $(inputAltElem)
-    const [hrefContainerElem, inputHrefElem] = genModalInputElems(t('image.link'), hrefInputId)
-    const $inputHref = $(inputHrefElem)
-    const [buttonContainerElem] = genModalButtonElems(buttonId, t('common.ok'))
-
-    if (this.$content == null) {
-      // 第一次渲染
-      const $content = $('<div></div>')
-
-      // 绑定事件(第一次渲染时绑定,不要重复绑定)
-      $content.on('click', `#${buttonId}`, e => {
-        e.preventDefault()
-
-        const src = $content.find(`#${srcInputId}`).val()
-        const alt = $content.find(`#${altInputId}`).val()
-        const href = $content.find(`#${hrefInputId}`).val()
-        this.updateImage(editor, src, alt, href)
-        editor.hidePanelOrModal() // 隐藏 modal
-      })
-
-      // 记录属性,重要
-      this.$content = $content
-    }
-
-    const $content = this.$content
-    $content.empty() // 先清空内容
-
-    // append inputs and button
-    $content.append(srcContainerElem)
-    $content.append(altContainerElem)
-    $content.append(hrefContainerElem)
-    $content.append(buttonContainerElem)
-
-    // 设置 input val
-    const { src, alt = '', href = '' } = selectedImageNode as ImageElement
-    $inputSrc.val(src)
-    $inputAlt.val(alt)
-    $inputHref.val(href)
-
-    // focus 一个 input(异步,此时 DOM 尚未渲染)
-    setTimeout(() => {
-      $inputSrc.focus()
-    })
-
-    return $content[0]
-  }
-
-  private updateImage(
-    editor: IDomEditor,
-    src: string,
-    alt: string = '',
-    href: string = '',
-    style: ImageStyle = {}
-  ) {
-    if (!src) return
-
-    // 还原选区
-    editor.restoreSelection()
-
-    if (this.isDisabled(editor)) return
-
-    // 修改图片信息
-    updateImageNode(editor, src, alt, href, style)
-  }
-}
-
-export default EditImage
diff --git a/packages/basic-modules/src/modules/image/menu/InsertImage.ts b/packages/basic-modules/src/modules/image/menu/InsertImage.ts
deleted file mode 100644
index cf348a744..000000000
--- a/packages/basic-modules/src/modules/image/menu/InsertImage.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * @description insert image menu
- * @author wangfupeng
- */
-
-import { Node } from 'slate'
-import {
-  IModalMenu,
-  IDomEditor,
-  genModalInputElems,
-  genModalButtonElems,
-  t,
-} from '@wangeditor/core'
-import $, { Dom7Array, DOMElement } from '../../../utils/dom'
-import { genRandomStr } from '../../../utils/util'
-import { IMAGE_SVG } from '../../../constants/icon-svg'
-import { insertImageNode, isInsertImageMenuDisabled } from '../helper'
-
-/**
- * 生成唯一的 DOM ID
- */
-function genDomID(): string {
-  return genRandomStr('w-e-insert-image')
-}
-
-class InsertImage implements IModalMenu {
-  readonly title = t('image.netImage')
-  readonly iconSvg = IMAGE_SVG
-  readonly tag = 'button'
-  readonly showModal = true // 点击 button 时显示 modal
-  readonly modalWidth = 300
-  private $content: Dom7Array | null = null
-  private readonly srcInputId = genDomID()
-  private readonly altInputId = genDomID()
-  private readonly hrefInputId = genDomID()
-  private readonly buttonId = genDomID()
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 插入菜单,不需要 value
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 任何时候,都不用激活 menu
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    // 点击菜单时,弹出 modal 之前,不需要执行其他代码
-    // 此处空着即可
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    return isInsertImageMenuDisabled(editor)
-  }
-
-  getModalPositionNode(editor: IDomEditor): Node | null {
-    return null // modal 依据选区定位
-  }
-
-  getModalContentElem(editor: IDomEditor): DOMElement {
-    const { srcInputId, altInputId, hrefInputId, buttonId } = this
-
-    // 获取 input button elem
-    const [srcContainerElem, inputSrcElem] = genModalInputElems(t('image.src'), srcInputId)
-    const $inputSrc = $(inputSrcElem)
-    const [altContainerElem, inputAltElem] = genModalInputElems(t('image.desc'), altInputId)
-    const $inputAlt = $(inputAltElem)
-    const [hrefContainerElem, inputHrefElem] = genModalInputElems(t('image.link'), hrefInputId)
-    const $inputHref = $(inputHrefElem)
-    const [buttonContainerElem] = genModalButtonElems(buttonId, t('common.ok'))
-
-    if (this.$content == null) {
-      // 第一次渲染
-      const $content = $('<div></div>')
-
-      // 绑定事件(第一次渲染时绑定,不要重复绑定)
-      $content.on('click', `#${buttonId}`, e => {
-        e.preventDefault()
-        const src = $content.find(`#${srcInputId}`).val().trim()
-        const alt = $content.find(`#${altInputId}`).val().trim()
-        const href = $content.find(`#${hrefInputId}`).val().trim()
-        this.insertImage(editor, src, alt, href)
-        editor.hidePanelOrModal() // 隐藏 modal
-      })
-
-      // 记录属性,重要
-      this.$content = $content
-    }
-
-    const $content = this.$content
-    $content.empty() // 先清空内容
-
-    // append inputs and button
-    $content.append(srcContainerElem)
-    $content.append(altContainerElem)
-    $content.append(hrefContainerElem)
-    $content.append(buttonContainerElem)
-
-    // 设置 input val
-    $inputSrc.val('')
-    $inputAlt.val('')
-    $inputHref.val('')
-
-    // focus 一个 input(异步,此时 DOM 尚未渲染)
-    setTimeout(() => {
-      $inputSrc.focus()
-    })
-
-    return $content[0]
-  }
-
-  private insertImage(editor: IDomEditor, src: string, alt: string = '', href: string = '') {
-    if (!src) return
-
-    // 还原选区
-    editor.restoreSelection()
-
-    if (this.isDisabled(editor)) return
-
-    // 插入图片
-    insertImageNode(editor, src, alt, href)
-  }
-}
-
-export default InsertImage
diff --git a/packages/basic-modules/src/modules/image/menu/ViewImageLink.ts b/packages/basic-modules/src/modules/image/menu/ViewImageLink.ts
deleted file mode 100644
index ca61baaab..000000000
--- a/packages/basic-modules/src/modules/image/menu/ViewImageLink.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * @description view image link menu
- * @author wangfupeng
- */
-
-import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import { EXTERNAL_SVG } from '../../../constants/icon-svg'
-import { ImageElement } from '../custom-types'
-
-class ViewImageLink implements IButtonMenu {
-  readonly title = t('image.viewLink')
-  readonly iconSvg = EXTERNAL_SVG
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    const imageNode = DomEditor.getSelectedNodeByType(editor, 'image')
-    if (imageNode) {
-      // 选区处于 image node
-      return (imageNode as ImageElement).href || ''
-    }
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const href = this.getValue(editor)
-    if (href) {
-      // 有 image href ,则不禁用
-      return false
-    }
-    return true
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (this.isDisabled(editor)) return
-
-    if (!value || typeof value !== 'string') {
-      throw new Error(`View image link failed, image.href is '${value}'`)
-      return
-    }
-
-    // 查看链接
-    window.open(value, '_blank')
-  }
-}
-
-export default ViewImageLink
diff --git a/packages/basic-modules/src/modules/image/menu/Width100.ts b/packages/basic-modules/src/modules/image/menu/Width100.ts
deleted file mode 100644
index f4959e64f..000000000
--- a/packages/basic-modules/src/modules/image/menu/Width100.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description image width 100%
- * @author wangfupeng
- */
-
-import ImageWidthBaseClass from './WidthBase'
-
-class ImageWidth100 extends ImageWidthBaseClass {
-  readonly title = '100%' // 菜单标题
-  readonly value = '100%' // css width 的值
-}
-
-export default ImageWidth100
diff --git a/packages/basic-modules/src/modules/image/menu/Width30.ts b/packages/basic-modules/src/modules/image/menu/Width30.ts
deleted file mode 100644
index 2f2047569..000000000
--- a/packages/basic-modules/src/modules/image/menu/Width30.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description image width 30%
- * @author wangfupeng
- */
-
-import ImageWidthBaseClass from './WidthBase'
-
-class ImageWidth30 extends ImageWidthBaseClass {
-  readonly title = '30%' // 菜单标题
-  readonly value = '30%' // css width 的值
-}
-
-export default ImageWidth30
diff --git a/packages/basic-modules/src/modules/image/menu/Width50.ts b/packages/basic-modules/src/modules/image/menu/Width50.ts
deleted file mode 100644
index 9888dfe29..000000000
--- a/packages/basic-modules/src/modules/image/menu/Width50.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description image width 50%
- * @author wangfupeng
- */
-
-import ImageWidthBaseClass from './WidthBase'
-
-class ImageWidth50 extends ImageWidthBaseClass {
-  readonly title = '50%' // 菜单标题
-  readonly value = '50%' // css width 的值
-}
-
-export default ImageWidth50
diff --git a/packages/basic-modules/src/modules/image/menu/WidthBase.ts b/packages/basic-modules/src/modules/image/menu/WidthBase.ts
deleted file mode 100644
index 06742e2ad..000000000
--- a/packages/basic-modules/src/modules/image/menu/WidthBase.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * @description image width base class
- * @author wangfupeng
- */
-
-import { Transforms, Node } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor/core'
-import { ImageElement } from '../custom-types'
-
-abstract class ImageWidthBaseClass implements IButtonMenu {
-  abstract readonly title: string // 菜单标题
-  readonly tag = 'button'
-  abstract readonly value: string // css width 的值
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 无需获取 val
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  private getSelectedNode(editor: IDomEditor): Node | null {
-    return DomEditor.getSelectedNodeByType(editor, 'image')
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const imageNode = this.getSelectedNode(editor)
-    if (imageNode == null) {
-      // 选区未处于 image node ,则禁用
-      return true
-    }
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (this.isDisabled(editor)) return
-
-    const imageNode = this.getSelectedNode(editor)
-    if (imageNode == null) return
-
-    // 隐藏 hoverbar
-    const hoverbar = DomEditor.getHoverbar(editor)
-    if (hoverbar) hoverbar.hideAndClean()
-
-    const { style = {} } = imageNode as ImageElement
-    const props: Partial<ImageElement> = {
-      style: {
-        ...style,
-        width: this.value, // 修改 width
-        height: '', // 清空 height
-      },
-    }
-
-    Transforms.setNodes(editor, props, {
-      match: n => DomEditor.checkNodeType(n, 'image'),
-    })
-  }
-}
-
-export default ImageWidthBaseClass
diff --git a/packages/basic-modules/src/modules/image/menu/config.ts b/packages/basic-modules/src/modules/image/menu/config.ts
deleted file mode 100644
index e6b24725c..000000000
--- a/packages/basic-modules/src/modules/image/menu/config.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * @description 图片菜单配置
- * @author wangfupeng
- */
-
-import { ImageElement } from '../custom-types'
-
-export function genImageMenuConfig() {
-  return {
-    /**
-     * 插入图片之后的回调
-     * @param imageElem ImageElement
-     */
-    onInsertedImage(imageElem: ImageElement) {
-      /*自定义*/
-    },
-
-    /**
-     * 更新图片之后的回调
-     * @param node image node
-     */
-    onUpdatedImage(node: ImageElement | null) {
-      /*自定义*/
-    },
-
-    /**
-     * 检查图片信息,支持 async fn
-     * @param src image src
-     * @param alt image alt
-     * @param href image href
-     */
-    checkImage(src: string, alt: string, href: string): boolean | string | undefined {
-      // 1. 返回 true ,说明检查通过
-      // 2. 返回一个字符串,说明检查未通过,编辑器会阻止图片插入。会 alert 出错误信息(即返回的字符串)
-      // 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止图片插入
-      return true
-    },
-
-    /**
-     * parse image src
-     * @param src image src
-     * @returns new src
-     */
-    parseImageSrc(src: string): string {
-      return src
-    },
-  }
-}
diff --git a/packages/basic-modules/src/modules/image/menu/index.ts b/packages/basic-modules/src/modules/image/menu/index.ts
deleted file mode 100644
index 03abbc60f..000000000
--- a/packages/basic-modules/src/modules/image/menu/index.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * @description image menu entry
- * @author wangfupeng
- */
-
-import InsertImage from './InsertImage'
-import DeleteImage from './DeleteImage'
-import EditImage from './EditImage'
-import ViewImageLink from './ViewImageLink'
-import ImageWidth30 from './Width30'
-import ImageWidth50 from './Width50'
-import ImageWidth100 from './Width100'
-import { genImageMenuConfig } from './config'
-
-const config = genImageMenuConfig() // menu config
-
-export const insertImageMenuConf = {
-  key: 'insertImage',
-  factory() {
-    return new InsertImage()
-  },
-
-  // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
-  // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
-  config,
-}
-
-export const deleteImageMenuConf = {
-  key: 'deleteImage',
-  factory() {
-    return new DeleteImage()
-  },
-}
-
-export const editImageMenuConf = {
-  key: 'editImage',
-  factory() {
-    return new EditImage()
-  },
-  config,
-}
-
-export const viewImageLinkMenuConf = {
-  key: 'viewImageLink',
-  factory() {
-    return new ViewImageLink()
-  },
-}
-
-export const imageWidth30MenuConf = {
-  key: 'imageWidth30',
-  factory() {
-    return new ImageWidth30()
-  },
-}
-
-export const imageWidth50MenuConf = {
-  key: 'imageWidth50',
-  factory() {
-    return new ImageWidth50()
-  },
-}
-
-export const imageWidth100MenuConf = {
-  key: 'imageWidth100',
-  factory() {
-    return new ImageWidth100()
-  },
-}
diff --git a/packages/basic-modules/src/modules/image/parse-elem-html.ts b/packages/basic-modules/src/modules/image/parse-elem-html.ts
deleted file mode 100644
index b7a581917..000000000
--- a/packages/basic-modules/src/modules/image/parse-elem-html.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @description parse html
- * @author wangfupeng
- */
-
-import { Descendant } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { ImageElement } from './custom-types'
-import $, { DOMElement, getStyleValue } from '../../utils/dom'
-
-function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): ImageElement {
-  const $elem = $(elem)
-  let href = $elem.attr('data-href') || ''
-  href = decodeURIComponent(href) // 兼容 V4
-
-  return {
-    type: 'image',
-    src: $elem.attr('src') || '',
-    alt: $elem.attr('alt') || '',
-    href,
-    style: {
-      width: getStyleValue($elem, 'width'),
-      height: getStyleValue($elem, 'height'),
-    },
-    children: [{ text: '' }], // void node 有一个空白 text
-  }
-}
-
-export const parseHtmlConf = {
-  selector: 'img:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: parseHtml,
-}
diff --git a/packages/basic-modules/src/modules/image/plugin.ts b/packages/basic-modules/src/modules/image/plugin.ts
deleted file mode 100644
index 6425fc393..000000000
--- a/packages/basic-modules/src/modules/image/plugin.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @description editor 插件,重写 editor API
- * @author wangfupeng
- */
-
-// import { Editor, Path, Operation } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-
-function withImage<T extends IDomEditor>(editor: T): T {
-  const { isInline, isVoid, insertNode } = editor
-  const newEditor = editor
-
-  // 重写 isInline
-  newEditor.isInline = elem => {
-    const { type } = elem
-
-    if (type === 'image') {
-      return true
-    }
-
-    return isInline(elem)
-  }
-
-  // 重写 isVoid
-  newEditor.isVoid = elem => {
-    const { type } = elem
-
-    if (type === 'image') {
-      return true
-    }
-
-    return isVoid(elem)
-  }
-
-  // 返回 editor ,重要!
-  return newEditor
-}
-
-export default withImage
diff --git a/packages/basic-modules/src/modules/image/render-elem.tsx b/packages/basic-modules/src/modules/image/render-elem.tsx
deleted file mode 100644
index 867b5e042..000000000
--- a/packages/basic-modules/src/modules/image/render-elem.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * @description image render elem
- * @author wangfupeng
- */
-
-import throttle from 'lodash.throttle'
-import { Element as SlateElement, Transforms } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-import $, { Dom7Array } from '../../utils/dom'
-import { ImageElement } from './custom-types'
-
-interface IImageSize {
-  width?: string
-  height?: string
-}
-
-function genContainerId(editor: IDomEditor, elemNode: SlateElement) {
-  const { id } = DomEditor.findKey(editor, elemNode) // node 唯一 id
-  return `w-e-image-container-${id}`
-}
-
-/**
- * 未选中时,渲染 image container
- */
-function renderContainer(
-  editor: IDomEditor,
-  elemNode: SlateElement,
-  imageVnode: VNode,
-  imageInfo: IImageSize
-): VNode {
-  const { width, height } = imageInfo
-
-  const style: any = {}
-  if (width) style.width = width
-  if (height) style.height = height
-
-  const containerId = genContainerId(editor, elemNode)
-
-  return (
-    <div id={containerId} style={style} className="w-e-image-container">
-      {imageVnode}
-    </div>
-  )
-}
-
-/**
- * 选中状态下,渲染 image container(渲染拖拽容器,修改图片尺寸)
- */
-function renderResizeContainer(
-  editor: IDomEditor,
-  elemNode: SlateElement,
-  imageVnode: VNode,
-  imageInfo: IImageSize
-) {
-  const $body = $('body')
-  const containerId = genContainerId(editor, elemNode)
-  const { width, height } = imageInfo
-
-  let originalX = 0
-  let originalWith = 0
-  let originalHeight = 0
-  let revers = false // 是否反转。如向右拖拽 right-top 需增加宽度(非反转),但向右拖拽 left-top 则需要减少宽度(反转)
-  let $container: Dom7Array | null = null
-
-  function getContainerElem(): Dom7Array {
-    const $container = $(`#${containerId}`)
-    if ($container.length === 0) throw new Error('Cannot find image container elem')
-    return $container
-  }
-
-  /**
-   * 初始化。监听事件,记录原始数据
-   */
-  function init(clientX: number) {
-    $container = getContainerElem()
-
-    // 记录当前 x 坐标值
-    originalX = clientX
-
-    // 记录 img 原始宽高
-    const $img = $container.find('img')
-    if ($img.length === 0) throw new Error('Cannot find image elem')
-    originalWith = $img.width()
-    originalHeight = $img.height()
-
-    // 监听 mousemove
-    $body.on('mousemove', onMousemove)
-
-    // 监听 mouseup
-    $body.on('mouseup', onMouseup)
-
-    // 隐藏 hoverbar
-    const hoverbar = DomEditor.getHoverbar(editor)
-    if (hoverbar) hoverbar.hideAndClean()
-  }
-
-  // mouseover callback (节流)
-  const onMousemove = throttle((e: Event) => {
-    e.preventDefault()
-
-    const { clientX } = e as MouseEvent
-    const gap = revers ? originalX - clientX : clientX - originalX // 考虑是否反转
-    const newWidth = originalWith + gap
-    const newHeight = originalHeight * (newWidth / originalWith) // 根据 width ,按比例计算 height
-
-    // 实时修改 img 宽高 -【注意】这里只修改 DOM ,mouseup 时再统一不修改 node
-    if ($container == null) return
-    if (newWidth <= 15 || newHeight <= 15) return // 最小就是 15px
-
-    $container.css('width', `${newWidth}px`)
-    $container.css('height', `${newHeight}px`)
-  }, 100)
-
-  function onMouseup(e: Event) {
-    // 取消监听 mousemove
-    $body.off('mousemove', onMousemove)
-
-    if ($container == null) return
-    const newWidth = $container.width().toFixed(2)
-    const newHeight = $container.height().toFixed(2)
-
-    // 修改 node
-    const props: Partial<ImageElement> = {
-      style: {
-        ...(elemNode as ImageElement).style,
-        width: `${newWidth}px`,
-        height: `${newHeight}px`,
-      },
-    }
-    Transforms.setNodes(editor, props, { at: DomEditor.findPath(editor, elemNode) })
-
-    // 取消监听 mouseup
-    $body.off('mouseup', onMouseup)
-  }
-
-  const style: any = {}
-  if (width) style.width = width
-  if (height) style.height = height
-  // style.boxShadow = '0 0 0 1px #B4D5FF' // 自定义 selected 样式,因为有拖拽触手
-
-  return (
-    <div
-      id={containerId}
-      style={style}
-      className="w-e-image-container w-e-selected-image-container"
-      on={{
-        // 统一绑定拖拽触手的 mousedown 事件
-        mousedown: (e: MouseEvent) => {
-          const $target = $(e.target as Element)
-          if (!$target.hasClass('w-e-image-dragger')) {
-            // target 不是 .w-e-image-dragger 拖拽触手,则忽略
-            return
-          }
-          e.preventDefault()
-
-          if ($target.hasClass('left-top') || $target.hasClass('left-bottom')) {
-            revers = true // 反转。向右拖拽,减少宽度
-          }
-          init(e.clientX) // 初始化
-        },
-      }}
-    >
-      {imageVnode}
-
-      {/* 拖拽的触手,会统一在上级 DOM 绑定拖拽事件 */}
-      <div className="w-e-image-dragger left-top"></div>
-      <div className="w-e-image-dragger right-top"></div>
-      <div className="w-e-image-dragger left-bottom"></div>
-      <div className="w-e-image-dragger right-bottom"></div>
-    </div>
-  )
-}
-
-function renderImage(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
-  const { src, alt = '', href = '', style = {} } = elemNode as ImageElement
-  const { width = '', height = '' } = style
-  const selected = DomEditor.isNodeSelected(editor, elemNode) // 图片是否选中
-
-  const imageStyle: any = {}
-  if (width) imageStyle.width = '100%'
-  if (height) imageStyle.height = '100%'
-
-  // 【注意】void node 中,renderElem 不用处理 children 。core 会统一处理。
-  const vnode = <img style={imageStyle} src={src} alt={alt} data-href={href} />
-
-  const isDisabled = editor.isDisabled()
-
-  if (selected && !isDisabled) {
-    // 选中,未禁用 - 渲染 resize container
-    return renderResizeContainer(editor, elemNode, vnode, { width, height })
-  }
-
-  // 其他,渲染普通 image container
-  return renderContainer(editor, elemNode, vnode, { width, height })
-}
-
-const renderImageConf = {
-  type: 'image', // 和 elemNode.type 一致
-  renderElem: renderImage,
-}
-
-export { renderImageConf }
diff --git a/packages/basic-modules/src/modules/indent/custom-types.ts b/packages/basic-modules/src/modules/indent/custom-types.ts
deleted file mode 100644
index eaafd6873..000000000
--- a/packages/basic-modules/src/modules/indent/custom-types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-export type IndentElement = {
-  type: string
-  indent?: string | null
-  children: Text[]
-}
diff --git a/packages/basic-modules/src/modules/indent/index.ts b/packages/basic-modules/src/modules/indent/index.ts
deleted file mode 100644
index 256650822..000000000
--- a/packages/basic-modules/src/modules/indent/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description indent entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderStyle } from './render-style'
-import { styleToHtml } from './style-to-html'
-import { preParseHtmlConf } from './pre-parse-html'
-import { parseStyleHtml } from './parse-style-html'
-import { indentMenuConf, delIndentMenuConf } from './menu/index'
-
-const indent: Partial<IModuleConf> = {
-  renderStyle,
-  styleToHtml,
-  preParseHtml: [preParseHtmlConf],
-  parseStyleHtml,
-  menus: [indentMenuConf, delIndentMenuConf],
-}
-
-export default indent
diff --git a/packages/basic-modules/src/modules/indent/menu/BaseMenu.ts b/packages/basic-modules/src/modules/indent/menu/BaseMenu.ts
deleted file mode 100644
index c4a59dff0..000000000
--- a/packages/basic-modules/src/modules/indent/menu/BaseMenu.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @description indent base menu
- * @author wangfupeng
- */
-
-import { Editor, Node } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor/core'
-
-abstract class BaseMenu implements IButtonMenu {
-  abstract readonly title: string
-  abstract readonly iconSvg: string
-  readonly tag = 'button'
-
-  /**
-   * 获取 node.indent 的值,如 `2em`
-   * @param editor editor
-   */
-  getValue(editor: IDomEditor): string | boolean {
-    const [nodeEntry] = Editor.nodes(editor, {
-      // @ts-ignore
-      match: n => !!n.indent,
-      universal: true,
-    })
-
-    if (nodeEntry == null) return ''
-    const [n] = nodeEntry
-    // @ts-ignore
-    return n.indent || ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 不需要 active
-    return false
-  }
-
-  /**
-   * 获取 node 节点
-   * @param editor editor
-   */
-  protected getMatchNode(editor: IDomEditor): Node | null {
-    const [nodeEntry] = Editor.nodes(editor, {
-      match: n => {
-        const type = DomEditor.getNodeType(n)
-
-        // 只可用于 p 和 header
-        if (type === 'paragraph') return true
-        if (type.startsWith('header')) return true
-
-        return false
-      },
-      universal: true,
-      mode: 'highest', // 匹配最高层级
-    })
-
-    if (nodeEntry == null) return null
-    return nodeEntry[0]
-  }
-
-  abstract isDisabled(editor: IDomEditor): boolean
-
-  abstract exec(editor: IDomEditor, value: string | boolean): void
-}
-
-export default BaseMenu
diff --git a/packages/basic-modules/src/modules/indent/menu/DecreaseIndentMenu.ts b/packages/basic-modules/src/modules/indent/menu/DecreaseIndentMenu.ts
deleted file mode 100644
index 9390f59cc..000000000
--- a/packages/basic-modules/src/modules/indent/menu/DecreaseIndentMenu.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @description 减少缩进
- * @author wangfupeng
- */
-
-import { Transforms, Element } from 'slate'
-import { IDomEditor, t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { INDENT_LEFT_SVG } from '../../../constants/icon-svg'
-import { IndentElement } from '../custom-types'
-
-class DecreaseIndentMenu extends BaseMenu {
-  readonly title = t('indent.decrease')
-  readonly iconSvg = INDENT_LEFT_SVG
-
-  isDisabled(editor: IDomEditor): boolean {
-    const matchNode = this.getMatchNode(editor)
-    if (matchNode == null) return true // 未匹配 p header 等,则禁用
-
-    const { indent } = matchNode as IndentElement
-    if (!indent) {
-      // 没有 indent ,则禁用
-      return true
-    }
-
-    return false // 其他情况,不禁用
-  }
-
-  exec(editor: IDomEditor, value: string | boolean): void {
-    Transforms.setNodes(
-      editor,
-      {
-        indent: null,
-      },
-      { match: n => Element.isElement(n) }
-    )
-  }
-}
-
-export default DecreaseIndentMenu
diff --git a/packages/basic-modules/src/modules/indent/menu/IncreaseIndentMenu.ts b/packages/basic-modules/src/modules/indent/menu/IncreaseIndentMenu.ts
deleted file mode 100644
index caaf1a9e1..000000000
--- a/packages/basic-modules/src/modules/indent/menu/IncreaseIndentMenu.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * @description 增加缩进
- * @author wangfupeng
- */
-
-import { Transforms, Element, Editor, Text } from 'slate'
-import { IDomEditor, t, DomEditor } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { INDENT_RIGHT_SVG } from '../../../constants/icon-svg'
-import { IndentElement } from '../custom-types'
-import type { FontSizeAndFamilyText } from '../../font-size-family/custom-types'
-
-class IncreaseIndentMenu extends BaseMenu {
-  readonly title = t('indent.increase')
-  readonly iconSvg = INDENT_RIGHT_SVG
-  private DEFAULT_INDENT_VALUE = '2em'
-
-  isDisabled(editor: IDomEditor): boolean {
-    const matchNode = this.getMatchNode(editor)
-    if (matchNode == null) return true // 未匹配 p header 等,则禁用
-
-    const { indent } = matchNode as IndentElement
-    if (indent) {
-      // 有 indent ,则禁用
-      return true
-    }
-
-    return false
-  }
-
-  private getIndentValue(editor: IDomEditor) {
-    const matchNode = this.getMatchNode(editor)
-
-    if (!matchNode) return this.DEFAULT_INDENT_VALUE
-    const textChildren = (matchNode as Element).children.filter(Text.isText)
-
-    const lastTextNode = textChildren[0] as FontSizeAndFamilyText
-
-    if (!lastTextNode || !lastTextNode.fontSize) return this.DEFAULT_INDENT_VALUE
-
-    // 如果段落的第一个 Text 节点 设置了 fontSize 样式,indent 值需要根据 fontSize 进行计算
-    const fontSize = lastTextNode.fontSize
-    const value = parseInt(lastTextNode.fontSize)
-    const unit = fontSize.replace(`${value}`, '')
-
-    return `${value * 2}${unit}`
-  }
-
-  exec(editor: IDomEditor, value: string | boolean): void {
-    const indent = this.getIndentValue(editor)
-
-    Transforms.setNodes(
-      editor,
-      {
-        indent,
-      },
-      {
-        match: n => Element.isElement(n),
-        mode: 'highest',
-      }
-    )
-  }
-}
-
-export default IncreaseIndentMenu
diff --git a/packages/basic-modules/src/modules/indent/menu/index.ts b/packages/basic-modules/src/modules/indent/menu/index.ts
deleted file mode 100644
index a030595a0..000000000
--- a/packages/basic-modules/src/modules/indent/menu/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description indent menu entry
- * @author wangfupeng
- */
-
-import DecreaseIndentMenu from './DecreaseIndentMenu'
-import IncreaseIndentMenu from './IncreaseIndentMenu'
-
-export const indentMenuConf = {
-  key: 'indent',
-  factory() {
-    return new IncreaseIndentMenu()
-  },
-}
-
-export const delIndentMenuConf = {
-  key: 'delIndent',
-  factory() {
-    return new DecreaseIndentMenu()
-  },
-}
diff --git a/packages/basic-modules/src/modules/indent/parse-style-html.ts b/packages/basic-modules/src/modules/indent/parse-style-html.ts
deleted file mode 100644
index 74f83f1e2..000000000
--- a/packages/basic-modules/src/modules/indent/parse-style-html.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @description parse style html
- * @author wangfupeng
- */
-
-import { Descendant, Element } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { IndentElement } from './custom-types'
-import $, { DOMElement, getStyleValue } from '../../utils/dom'
-
-export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
-  const $elem = $(elem)
-  if (!Element.isElement(node)) return node
-
-  const elemNode = node as IndentElement
-
-  const indent = getStyleValue($elem, 'text-indent')
-  const indentNumber = parseInt(indent, 10)
-  if (indent && indentNumber > 0) {
-    elemNode.indent = indent
-  }
-
-  return elemNode
-}
diff --git a/packages/basic-modules/src/modules/indent/pre-parse-html.ts b/packages/basic-modules/src/modules/indent/pre-parse-html.ts
deleted file mode 100644
index 3c3534c4d..000000000
--- a/packages/basic-modules/src/modules/indent/pre-parse-html.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @description pre-parse html
- * @author wangfupeng
- */
-
-import $, { DOMElement, getStyleValue } from '../../utils/dom'
-
-/**
- * pre-prase text-indent 兼容 V4 和 V5 早期格式(都使用 padding-left)
- * @param elem elem
- */
-function preParse(elem: DOMElement): DOMElement {
-  const $elem = $(elem)
-  const paddingLeft = getStyleValue($elem, 'padding-left')
-
-  if (/\dem/.test(paddingLeft)) {
-    // 如 '2em' ,V4 格式
-    $elem.css('text-indent', '2em')
-  }
-
-  if (/\dpx/.test(paddingLeft)) {
-    // px 单位
-    const num = parseInt(paddingLeft, 10)
-    if (num % 32 === 0) {
-      // 如 32px 64px ,V5 早期格式
-      $elem.css('text-indent', '2em')
-    }
-  }
-
-  return $elem[0]
-}
-
-export const preParseHtmlConf = {
-  selector: 'p,h1,h2,h3,h4,h5',
-  preParseHtml: preParse,
-}
diff --git a/packages/basic-modules/src/modules/indent/render-style.tsx b/packages/basic-modules/src/modules/indent/render-style.tsx
deleted file mode 100644
index b0019df35..000000000
--- a/packages/basic-modules/src/modules/indent/render-style.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @description render indent style
- * @author wangfupeng
- */
-
-import { Element, Descendant } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { addVnodeStyle } from '../../utils/vdom'
-import { IndentElement } from './custom-types'
-
-/**
- * 添加样式
- * @param node slate elem
- * @param vnode vnode
- * @returns vnode
- */
-export function renderStyle(node: Descendant, vnode: VNode): VNode {
-  if (!Element.isElement(node)) return vnode
-
-  const { indent } = node as IndentElement // 如 '2em'
-  let styleVnode: VNode = vnode
-
-  if (indent) {
-    addVnodeStyle(styleVnode, { textIndent: indent })
-  }
-
-  return styleVnode
-}
diff --git a/packages/basic-modules/src/modules/indent/style-to-html.ts b/packages/basic-modules/src/modules/indent/style-to-html.ts
deleted file mode 100644
index c94f0877c..000000000
--- a/packages/basic-modules/src/modules/indent/style-to-html.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @description textStyle to html
- * @author wangfupeng
- */
-
-import { Element, Descendant } from 'slate'
-import $, { getOuterHTML } from '../../utils/dom'
-import { IndentElement } from './custom-types'
-
-export function styleToHtml(node: Descendant, elemHtml: string): string {
-  if (!Element.isElement(node)) return elemHtml
-
-  const { indent } = node as IndentElement // 如 '2em'
-  if (!indent) return elemHtml
-
-  // 设置样式
-  const $elem = $(elemHtml)
-  $elem.css('text-indent', indent)
-
-  // 输出 html
-  return getOuterHTML($elem)
-}
diff --git a/packages/basic-modules/src/modules/justify/custom-types.ts b/packages/basic-modules/src/modules/justify/custom-types.ts
deleted file mode 100644
index 5c2f3b366..000000000
--- a/packages/basic-modules/src/modules/justify/custom-types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-export type JustifyElement = {
-  type: string
-  textAlign?: string
-  children: Text[]
-}
diff --git a/packages/basic-modules/src/modules/justify/index.ts b/packages/basic-modules/src/modules/justify/index.ts
deleted file mode 100644
index 46fe593ae..000000000
--- a/packages/basic-modules/src/modules/justify/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @description justify module entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderStyle } from './render-style'
-import { styleToHtml } from './style-to-html'
-import { parseStyleHtml } from './parse-style-html'
-import {
-  justifyLeftMenuConf,
-  justifyRightMenuConf,
-  justifyCenterMenuConf,
-  justifyJustifyMenuConf,
-} from './menu/index'
-
-const justify: Partial<IModuleConf> = {
-  renderStyle,
-  styleToHtml,
-  parseStyleHtml,
-  menus: [justifyLeftMenuConf, justifyRightMenuConf, justifyCenterMenuConf, justifyJustifyMenuConf],
-}
-
-export default justify
diff --git a/packages/basic-modules/src/modules/justify/menu/BaseMenu.ts b/packages/basic-modules/src/modules/justify/menu/BaseMenu.ts
deleted file mode 100644
index 69d9f464f..000000000
--- a/packages/basic-modules/src/modules/justify/menu/BaseMenu.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * @description justify base menu
- * @author wangfupeng
- */
-
-import { Editor, Node, Element } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor/core'
-
-abstract class BaseMenu implements IButtonMenu {
-  abstract readonly title: string
-  abstract readonly iconSvg: string
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 不需要 value
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 不需要 active
-    return false
-  }
-
-  /**
-   * 获取 node 节点
-   * @param editor editor
-   */
-  protected getMatchNode(editor: IDomEditor): Node | null {
-    const [nodeEntry] = Editor.nodes(editor, {
-      match: n => {
-        const type = DomEditor.getNodeType(n)
-
-        // 只可用于 p blockquote header
-        if (type === 'paragraph') return true
-        if (type === 'blockquote') return true
-        if (type.startsWith('header')) return true
-
-        return false
-      },
-      universal: true,
-      mode: 'highest', // 匹配最高层级
-    })
-
-    if (nodeEntry == null) return null
-    return nodeEntry[0]
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const selectedElems = DomEditor.getSelectedElems(editor)
-    const notMatch = selectedElems.some((elem: Node) => {
-      if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem)) return true
-
-      const { type } = elem as unknown as Element
-      if (['pre', 'code'].includes(type)) return true
-    })
-    if (notMatch) return true
-
-    return false
-  }
-
-  abstract exec(editor: IDomEditor, value: string | boolean): void
-}
-
-export default BaseMenu
diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts
deleted file mode 100644
index 5f843130e..000000000
--- a/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @description justify center menu
- * @author wangfupeng
- */
-
-import { Transforms, Element } from 'slate'
-import { IDomEditor, t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { JUSTIFY_CENTER_SVG } from '../../../constants/icon-svg'
-
-class JustifyCenterMenu extends BaseMenu {
-  readonly title = t('justify.center')
-  readonly iconSvg = JUSTIFY_CENTER_SVG
-
-  exec(editor: IDomEditor, value: string | boolean): void {
-    Transforms.setNodes(
-      editor,
-      {
-        textAlign: 'center',
-      },
-      { match: n => Element.isElement(n) && !editor.isInline(n) } // inline 元素设置text-align 是没作用的
-    )
-  }
-}
-
-export default JustifyCenterMenu
diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts
deleted file mode 100644
index 70c032cf0..000000000
--- a/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @description 两端对齐
- * @author wangfupeng
- */
-
-import { Transforms, Element } from 'slate'
-import { IDomEditor, t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { JUSTIFY_JUSTIFY_SVG } from '../../../constants/icon-svg'
-
-class JustifyJustifyMenu extends BaseMenu {
-  readonly title = t('justify.justify')
-  readonly iconSvg = JUSTIFY_JUSTIFY_SVG
-
-  exec(editor: IDomEditor, value: string | boolean): void {
-    Transforms.setNodes(
-      editor,
-      {
-        textAlign: 'justify',
-      },
-      { match: n => Element.isElement(n) && !editor.isInline(n) }
-    )
-  }
-}
-
-export default JustifyJustifyMenu
diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts
deleted file mode 100644
index 226005187..000000000
--- a/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @description justify left menu
- * @author wangfupeng
- */
-
-import { Transforms, Element } from 'slate'
-import { IDomEditor, t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { JUSTIFY_LEFT_SVG } from '../../../constants/icon-svg'
-
-class JustifyLeftMenu extends BaseMenu {
-  readonly title = t('justify.left')
-  readonly iconSvg = JUSTIFY_LEFT_SVG
-
-  exec(editor: IDomEditor, value: string | boolean): void {
-    Transforms.setNodes(
-      editor,
-      {
-        textAlign: 'left',
-      },
-      { match: n => Element.isElement(n) && !editor.isInline(n) }
-    )
-  }
-}
-
-export default JustifyLeftMenu
diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts
deleted file mode 100644
index 828057bc2..000000000
--- a/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @description justify right menu
- * @author wangfupeng
- */
-
-import { Transforms, Element } from 'slate'
-import { IDomEditor, t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { JUSTIFY_RIGHT_SVG } from '../../../constants/icon-svg'
-
-class JustifyRightMenu extends BaseMenu {
-  readonly title = t('justify.right')
-  readonly iconSvg = JUSTIFY_RIGHT_SVG
-
-  exec(editor: IDomEditor, value: string | boolean): void {
-    Transforms.setNodes(
-      editor,
-      {
-        textAlign: 'right',
-      },
-      { match: n => Element.isElement(n) && !editor.isInline(n) }
-    )
-  }
-}
-
-export default JustifyRightMenu
diff --git a/packages/basic-modules/src/modules/justify/menu/index.ts b/packages/basic-modules/src/modules/justify/menu/index.ts
deleted file mode 100644
index 6273afd02..000000000
--- a/packages/basic-modules/src/modules/justify/menu/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @description justify menu entry
- * @author wangfupeng
- */
-
-import JustifyLeftMenu from './JustifyLeftMenu'
-import JustifyRightMenu from './JustifyRightMenu'
-import JustifyCenterMenu from './JustifyCenterMenu'
-import JustifyJustifyMenu from './JustifyJustifyMenu'
-
-export const justifyLeftMenuConf = {
-  key: 'justifyLeft',
-  factory() {
-    return new JustifyLeftMenu()
-  },
-}
-
-export const justifyRightMenuConf = {
-  key: 'justifyRight',
-  factory() {
-    return new JustifyRightMenu()
-  },
-}
-
-export const justifyCenterMenuConf = {
-  key: 'justifyCenter',
-  factory() {
-    return new JustifyCenterMenu()
-  },
-}
-
-export const justifyJustifyMenuConf = {
-  key: 'justifyJustify',
-  factory() {
-    return new JustifyJustifyMenu()
-  },
-}
diff --git a/packages/basic-modules/src/modules/justify/parse-style-html.ts b/packages/basic-modules/src/modules/justify/parse-style-html.ts
deleted file mode 100644
index 6461e6434..000000000
--- a/packages/basic-modules/src/modules/justify/parse-style-html.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @description parse style html
- * @author wangfupeng
- */
-
-import { Descendant, Element } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { JustifyElement } from './custom-types'
-import $, { DOMElement, getStyleValue } from '../../utils/dom'
-
-export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
-  const $elem = $(elem)
-  if (!Element.isElement(node)) return node
-
-  const elemNode = node as JustifyElement
-
-  const textAlign = getStyleValue($elem, 'text-align')
-  if (textAlign) {
-    elemNode.textAlign = textAlign
-  }
-
-  return elemNode
-}
diff --git a/packages/basic-modules/src/modules/justify/render-style.tsx b/packages/basic-modules/src/modules/justify/render-style.tsx
deleted file mode 100644
index c9afe7a30..000000000
--- a/packages/basic-modules/src/modules/justify/render-style.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @description render justify style
- * @author wangfupeng
- */
-
-import { Descendant, Element } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { addVnodeStyle } from '../../utils/vdom'
-import { JustifyElement } from './custom-types'
-
-/**
- * 添加样式
- * @param node slate elem
- * @param vnode vnode
- * @returns vnode
- */
-export function renderStyle(node: Descendant, vnode: VNode): VNode {
-  if (!Element.isElement(node)) return vnode
-
-  const { textAlign } = node as JustifyElement // 如 'left'/'right'/'center' 等
-  let styleVnode: VNode = vnode
-
-  if (textAlign) {
-    addVnodeStyle(styleVnode, { textAlign })
-  }
-
-  return styleVnode
-}
diff --git a/packages/basic-modules/src/modules/justify/style-to-html.ts b/packages/basic-modules/src/modules/justify/style-to-html.ts
deleted file mode 100644
index a8330a8c1..000000000
--- a/packages/basic-modules/src/modules/justify/style-to-html.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @description textStyle to html
- * @author wangfupeng
- */
-
-import { Element, Descendant } from 'slate'
-import $, { getOuterHTML } from '../../utils/dom'
-import { JustifyElement } from './custom-types'
-
-export function styleToHtml(node: Descendant, elemHtml: string): string {
-  if (!Element.isElement(node)) return elemHtml
-
-  const { textAlign } = node as JustifyElement // 如 'left'/'right'/'center' 等
-  if (!textAlign) return elemHtml
-
-  // 设置样式
-  const $elem = $(elemHtml)
-  $elem.css('text-align', textAlign)
-
-  // 输出 html
-  const outerHtml = getOuterHTML($elem)
-  return outerHtml
-}
diff --git a/packages/basic-modules/src/modules/line-height/custom-types.ts b/packages/basic-modules/src/modules/line-height/custom-types.ts
deleted file mode 100644
index 517ae2e8e..000000000
--- a/packages/basic-modules/src/modules/line-height/custom-types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-export type LineHeightElement = {
-  type: string
-  lineHeight?: string
-  children: Text[]
-}
diff --git a/packages/basic-modules/src/modules/line-height/index.ts b/packages/basic-modules/src/modules/line-height/index.ts
deleted file mode 100644
index 8861458d6..000000000
--- a/packages/basic-modules/src/modules/line-height/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @description line-height module entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderStyle } from './render-style'
-import { styleToHtml } from './style-to-html'
-import { lineHeightMenuConf } from './menu/index'
-import { parseStyleHtml } from './parse-style-html'
-
-const lineHeight: Partial<IModuleConf> = {
-  renderStyle,
-  styleToHtml,
-  parseStyleHtml,
-  menus: [lineHeightMenuConf],
-}
-
-export default lineHeight
diff --git a/packages/basic-modules/src/modules/line-height/menu/LineHeightMenu.ts b/packages/basic-modules/src/modules/line-height/menu/LineHeightMenu.ts
deleted file mode 100644
index 6c741a341..000000000
--- a/packages/basic-modules/src/modules/line-height/menu/LineHeightMenu.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * @description header menu
- * @author wangfupeng
- */
-
-import { Editor, Node, Element, Transforms } from 'slate'
-import { ISelectMenu, IDomEditor, DomEditor, IOption, t } from '@wangeditor/core'
-import { LINE_HEIGHT_SVG } from '../../../constants/icon-svg'
-import { LineHeightElement } from '../custom-types'
-
-class LineHeightMenu implements ISelectMenu {
-  readonly title = t('lineHeight.title')
-  readonly iconSvg = LINE_HEIGHT_SVG
-  readonly tag = 'select'
-  readonly width = 80
-
-  getOptions(editor: IDomEditor): IOption[] {
-    const options: IOption[] = []
-
-    // 获取配置,参考 './config.ts'
-    const { lineHeightList = [] } = editor.getMenuConfig('lineHeight')
-
-    // 生成 options
-    options.push({
-      text: t('lineHeight.default'),
-      value: '', // this.getValue(editor) 未找到结果时,会返回 '' ,正好对应到这里
-    })
-    lineHeightList.forEach((height: string) => {
-      options.push({
-        text: height,
-        value: height,
-      })
-    })
-
-    // 设置 selected
-    const curValue = this.getValue(editor)
-    options.forEach(opt => {
-      if (opt.value === curValue) {
-        opt.selected = true
-      } else {
-        delete opt.selected
-      }
-    })
-
-    return options
-  }
-
-  /**
-   * 获取匹配的 node 节点
-   * @param editor editor
-   */
-  private getMatchNode(editor: IDomEditor): Node | null {
-    const [nodeEntry] = Editor.nodes(editor, {
-      match: n => {
-        const type = DomEditor.getNodeType(n)
-
-        // line-height 匹配如下类型的 node
-        if (type.startsWith('header')) return true
-        if (['paragraph', 'blockquote', 'list-item'].includes(type)) {
-          return true
-        }
-
-        return false
-      },
-      universal: true,
-      mode: 'highest', // 匹配最高层级
-    })
-
-    if (nodeEntry == null) return null
-    return nodeEntry[0]
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // select menu 会显示 selected value ,用不到 active
-    return false
-  }
-
-  /**
-   * 获取 node.lineHeight 的值(如 '1' '1.5'),没有则返回 ''
-   * @param editor editor
-   */
-  getValue(editor: IDomEditor): string | boolean {
-    const node = this.getMatchNode(editor)
-    if (node == null) return ''
-    if (!Element.isElement(node)) return ''
-
-    return (node as LineHeightElement).lineHeight || ''
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true // 禁用
-
-    const node = this.getMatchNode(editor)
-    if (node == null) return true // 未匹配到指定 node ,禁用
-
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    Transforms.setNodes(
-      editor,
-      {
-        lineHeight: value.toString(),
-      },
-      { mode: 'highest' }
-    )
-  }
-}
-
-export default LineHeightMenu
diff --git a/packages/basic-modules/src/modules/line-height/menu/config.ts b/packages/basic-modules/src/modules/line-height/menu/config.ts
deleted file mode 100644
index 0401c2454..000000000
--- a/packages/basic-modules/src/modules/line-height/menu/config.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * @description line-height config
- * @author wangfupeng
- */
-
-export function genLineHeightConfig() {
-  return ['1', '1.15', '1.5', '2', '2.5', '3']
-}
diff --git a/packages/basic-modules/src/modules/line-height/menu/index.ts b/packages/basic-modules/src/modules/line-height/menu/index.ts
deleted file mode 100644
index 1982fdecf..000000000
--- a/packages/basic-modules/src/modules/line-height/menu/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @description line-height menu entry
- * @author wangfupeng
- */
-
-import LineHeightMenu from './LineHeightMenu'
-import { genLineHeightConfig } from './config'
-
-export const lineHeightMenuConf = {
-  key: 'lineHeight',
-  factory() {
-    return new LineHeightMenu()
-  },
-
-  // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
-  // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
-  config: {
-    lineHeightList: genLineHeightConfig(),
-  },
-}
diff --git a/packages/basic-modules/src/modules/line-height/parse-style-html.ts b/packages/basic-modules/src/modules/line-height/parse-style-html.ts
deleted file mode 100644
index 02563b906..000000000
--- a/packages/basic-modules/src/modules/line-height/parse-style-html.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @description parse style html
- * @author wangfupeng
- */
-
-import { Descendant, Element } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { LineHeightElement } from './custom-types'
-import $, { DOMElement, getStyleValue } from '../../utils/dom'
-
-export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
-  const $elem = $(elem)
-  if (!Element.isElement(node)) return node
-
-  const elemNode = node as LineHeightElement
-
-  const { lineHeightList = [] } = editor.getMenuConfig('lineHeight')
-  const lineHeight = getStyleValue($elem, 'line-height')
-  if (lineHeight && lineHeightList.includes(lineHeight)) {
-    elemNode.lineHeight = lineHeight
-  }
-
-  return elemNode
-}
diff --git a/packages/basic-modules/src/modules/line-height/render-style.tsx b/packages/basic-modules/src/modules/line-height/render-style.tsx
deleted file mode 100644
index 951c816e0..000000000
--- a/packages/basic-modules/src/modules/line-height/render-style.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @description render line-height style
- * @author wangfupeng
- */
-
-import { Element, Descendant } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { addVnodeStyle } from '../../utils/vdom'
-import { LineHeightElement } from './custom-types'
-
-/**
- * 添加样式
- * @param node slate elem
- * @param vnode vnode
- * @returns vnode
- */
-export function renderStyle(node: Descendant, vnode: VNode): VNode {
-  if (!Element.isElement(node)) return vnode
-
-  const { lineHeight } = node as LineHeightElement // 如 '1' '1.5'
-  let styleVnode: VNode = vnode
-
-  if (lineHeight) {
-    addVnodeStyle(styleVnode, { lineHeight })
-  }
-
-  return styleVnode
-}
diff --git a/packages/basic-modules/src/modules/line-height/style-to-html.ts b/packages/basic-modules/src/modules/line-height/style-to-html.ts
deleted file mode 100644
index d937fdfd6..000000000
--- a/packages/basic-modules/src/modules/line-height/style-to-html.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @description textStyle to html
- * @author wangfupeng
- */
-
-import { Element, Descendant } from 'slate'
-import $, { getOuterHTML } from '../../utils/dom'
-import { LineHeightElement } from './custom-types'
-
-export function styleToHtml(node: Descendant, elemHtml: string): string {
-  if (!Element.isElement(node)) return elemHtml
-
-  const { lineHeight } = node as LineHeightElement // 如 '1' '1.5'
-  if (!lineHeight) return elemHtml
-
-  // 设置样式
-  const $elem = $(elemHtml)
-  $elem.css('line-height', lineHeight)
-
-  // 输出 html
-  return getOuterHTML($elem)
-}
diff --git a/packages/basic-modules/src/modules/link/custom-types.ts b/packages/basic-modules/src/modules/link/custom-types.ts
deleted file mode 100644
index ff9647d7b..000000000
--- a/packages/basic-modules/src/modules/link/custom-types.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-export type LinkElement = {
-  type: 'link'
-  url: string
-  target?: string
-  children: Text[]
-}
diff --git a/packages/basic-modules/src/modules/link/elem-to-html.ts b/packages/basic-modules/src/modules/link/elem-to-html.ts
deleted file mode 100644
index 6a270d884..000000000
--- a/packages/basic-modules/src/modules/link/elem-to-html.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @description to html
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-import { LinkElement } from './custom-types'
-
-function linkToHtml(elem: Element, childrenHtml: string): string {
-  const { url, target = '_blank' } = elem as LinkElement
-
-  return `<a href="${url}" target="${target}">${childrenHtml}</a>`
-}
-
-export const linkToHtmlConf = {
-  type: 'link',
-  elemToHtml: linkToHtml,
-}
diff --git a/packages/basic-modules/src/modules/link/helper.ts b/packages/basic-modules/src/modules/link/helper.ts
deleted file mode 100644
index 75fc9cf2c..000000000
--- a/packages/basic-modules/src/modules/link/helper.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
- * @description link helper
- * @author wangfupeng
- */
-
-import { Editor, Range, Transforms } from 'slate'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-import { LinkElement } from './custom-types'
-import { replaceSymbols } from '../../utils/util'
-
-/**
- * 校验 link
- * @param menuKey menu key
- * @param editor editor
- * @param text menu text
- * @param url menu url
- */
-async function check(
-  menuKey: string,
-  editor: IDomEditor,
-  text: string,
-  url: string
-): Promise<boolean> {
-  const { checkLink } = editor.getMenuConfig(menuKey)
-  if (checkLink) {
-    const res = await checkLink(text, url)
-    if (typeof res === 'string') {
-      // 检验未通过,提示信息
-      editor.alert(res, 'error')
-      return false
-    }
-    if (res == null) {
-      // 检验未通过,不提示信息
-      return false
-    }
-  }
-
-  return true // 校验通过
-}
-
-/**
- * 转换链接 url
- * @param menuKey menu key
- * @param editor editor
- * @param url url
- * @returns parsedUrl
- */
-async function parse(menuKey: string, editor: IDomEditor, url: string): Promise<string> {
-  const { parseLinkUrl } = editor.getMenuConfig(menuKey)
-  if (parseLinkUrl) {
-    const newUrl = await parseLinkUrl(url)
-    return newUrl
-  }
-  return url
-}
-
-export function isMenuDisabled(editor: IDomEditor): boolean {
-  if (editor.selection == null) return true
-
-  const selectedElems = DomEditor.getSelectedElems(editor)
-  const notMatch = selectedElems.some(elem => {
-    const { type } = elem
-    if (editor.isVoid(elem)) return true
-    if (['pre', 'code', 'link'].includes(type)) return true
-  })
-  if (notMatch) return true // disabled
-  return false // enable
-}
-
-/**
- * 生成 link node
- * @param url url
- * @param text text
- */
-function genLinkNode(url: string, text?: string): LinkElement {
-  const linkNode: LinkElement = {
-    type: 'link',
-    url: replaceSymbols(url),
-    children: text ? [{ text }] : [],
-  }
-  return linkNode
-}
-
-/**
- * 插入 link
- * @param editor editor
- * @param text text
- * @param url url
- */
-export async function insertLink(editor: IDomEditor, text: string, url: string) {
-  if (!url) return
-  if (!text) text = url // 无 text 则用 url 代替
-
-  // 还原选区
-  editor.restoreSelection()
-
-  if (isMenuDisabled(editor)) return
-
-  // 校验
-  const checkRes = await check('insertLink', editor, text, url)
-  if (!checkRes) return // 校验未通过
-
-  // 转换 url
-  const parsedUrl = await parse('insertLink', editor, url)
-
-  // 判断选区是否折叠
-  const { selection } = editor
-  if (selection == null) return
-  const isCollapsed = Range.isCollapsed(selection)
-
-  // 执行:插入链接
-  if (isCollapsed) {
-    // 链接前后插入空格,方便操作
-    editor.insertText(' ')
-
-    const linkNode = genLinkNode(parsedUrl, text)
-    Transforms.insertNodes(editor, linkNode)
-
-    // https://github.com/wangeditor-team/wangEditor/issues/332
-    // 不能直接使用 insertText, 会造成添加的空格被添加到链接文本中,参考上面 issue,替换为 insertFragment 方式添加空格
-    editor.insertFragment([{ text: ' ' }])
-  } else {
-    const selectedText = Editor.string(editor, selection) // 选中的文字
-    if (selectedText !== text) {
-      // 选中的文字和输入的文字不一样,则删掉文字,插入链接
-      editor.deleteFragment()
-      const linkNode = genLinkNode(parsedUrl, text)
-      Transforms.insertNodes(editor, linkNode)
-    } else {
-      // 选中的文字和输入的文字一样,则只包裹链接即可
-      const linkNode = genLinkNode(parsedUrl)
-      Transforms.wrapNodes(editor, linkNode, { split: true })
-      Transforms.collapse(editor, { edge: 'end' })
-    }
-  }
-}
-
-/**
- * 修改 link url
- * @param editor editor
- * @param text text
- * @param url link url
- */
-export async function updateLink(editor: IDomEditor, text: string, url: string) {
-  if (!url) return
-
-  // 校验
-  const checkRes = await check('editLink', editor, text, url)
-  if (!checkRes) return // 校验未通过
-
-  // 转换 url
-  const parsedUrl = await parse('editLink', editor, url)
-
-  // 修改链接
-  const props: Partial<LinkElement> = { url: replaceSymbols(parsedUrl) }
-  Transforms.setNodes(editor, props, {
-    match: n => DomEditor.checkNodeType(n, 'link'),
-  })
-}
diff --git a/packages/basic-modules/src/modules/link/index.ts b/packages/basic-modules/src/modules/link/index.ts
deleted file mode 100644
index eb4aef170..000000000
--- a/packages/basic-modules/src/modules/link/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @description link entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import withLink from './plugin'
-import { renderLinkConf } from './render-elem'
-import { linkToHtmlConf } from './elem-to-html'
-import { parseHtmlConf } from './parse-elem-html'
-import {
-  insertLinkMenuConf,
-  editLinkMenuConf,
-  unLinkMenuConf,
-  viewLinkMenuConf,
-} from './menu/index'
-
-const link: Partial<IModuleConf> = {
-  renderElems: [renderLinkConf],
-  elemsToHtml: [linkToHtmlConf],
-  parseElemsHtml: [parseHtmlConf],
-  menus: [insertLinkMenuConf, editLinkMenuConf, unLinkMenuConf, viewLinkMenuConf],
-  editorPlugin: withLink,
-}
-
-export default link
diff --git a/packages/basic-modules/src/modules/link/menu/EditLink.ts b/packages/basic-modules/src/modules/link/menu/EditLink.ts
deleted file mode 100644
index dbaa41ad9..000000000
--- a/packages/basic-modules/src/modules/link/menu/EditLink.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
- * @description update link menu
- * @author wangfupeng
- */
-
-import { Node } from 'slate'
-import {
-  IModalMenu,
-  IDomEditor,
-  DomEditor,
-  genModalInputElems,
-  genModalButtonElems,
-  t,
-} from '@wangeditor/core'
-import $, { Dom7Array, DOMElement } from '../../../utils/dom'
-import { genRandomStr } from '../../../utils/util'
-import { PENCIL_SVG } from '../../../constants/icon-svg'
-import { updateLink } from '../helper'
-import { LinkElement } from '../custom-types'
-
-/**
- * 生成唯一的 DOM ID
- */
-function genDomID(): string {
-  return genRandomStr('w-e-update-link')
-}
-
-class EditLinkMenu implements IModalMenu {
-  readonly title = t('link.edit')
-  readonly iconSvg = PENCIL_SVG
-  readonly tag = 'button'
-  readonly showModal = true // 点击 button 时显示 modal
-  readonly modalWidth = 300
-
-  private $content: Dom7Array | null = null
-  private urlInputId = genDomID()
-  private buttonId = genDomID()
-
-  private getSelectedLinkElem(editor: IDomEditor): LinkElement | null {
-    const node = DomEditor.getSelectedNodeByType(editor, 'link')
-    if (node == null) return null
-    return node as LinkElement
-  }
-
-  /**
-   * 获取 node.url
-   * @param editor editor
-   */
-  getValue(editor: IDomEditor): string | boolean {
-    const linkElem = this.getSelectedLinkElem(editor)
-    if (linkElem) {
-      return linkElem.url || ''
-    }
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    // 点击菜单时,弹出 modal 之前,不需要执行其他代码
-    // 此处空着即可
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const linkElem = this.getSelectedLinkElem(editor)
-
-    // 未匹配到 link node 则禁用
-    if (linkElem == null) return true
-    return false
-  }
-
-  // modal 定位
-  getModalPositionNode(editor: IDomEditor): Node | null {
-    return DomEditor.getSelectedNodeByType(editor, 'link')
-  }
-
-  getModalContentElem(editor: IDomEditor): DOMElement {
-    const { urlInputId, buttonId } = this
-
-    // 获取 input button elem
-    const [urlContainerElem, inputUrlElem] = genModalInputElems(t('link.url'), urlInputId)
-    const $inputUrl = $(inputUrlElem)
-    const [buttonContainerElem] = genModalButtonElems(buttonId, t('common.ok'))
-
-    if (this.$content == null) {
-      // 第一次渲染
-      const $content = $('<div></div>')
-
-      // 绑定事件(第一次渲染时绑定,不要重复绑定)
-      $content.on('click', 'button', e => {
-        e.preventDefault()
-        editor.restoreSelection() // 还原选区
-
-        const n = DomEditor.getSelectedNodeByType(editor, 'link')
-        const text = n ? Node.string(n) : ''
-        const url = $content.find(`#${urlInputId}`).val()
-        updateLink(editor, text, url) // 修改链接
-
-        editor.hidePanelOrModal() // 隐藏 modal
-      })
-
-      // 记录属性,重要
-      this.$content = $content
-    }
-
-    const $content = this.$content
-    $content.empty() // 先清空内容
-
-    // append input and button
-    $content.append(urlContainerElem)
-    $content.append(buttonContainerElem)
-
-    // 设置 input val
-    const url = this.getValue(editor)
-    $inputUrl.val(url)
-
-    // focus 一个 input(异步,此时 DOM 尚未渲染)
-    setTimeout(() => {
-      $inputUrl.focus()
-    })
-
-    return $content[0]
-  }
-}
-
-export default EditLinkMenu
diff --git a/packages/basic-modules/src/modules/link/menu/InsertLink.ts b/packages/basic-modules/src/modules/link/menu/InsertLink.ts
deleted file mode 100644
index 1b7a74de2..000000000
--- a/packages/basic-modules/src/modules/link/menu/InsertLink.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * @description insert link menu
- * @author wangfupeng
- */
-
-import { Editor, Range, Node } from 'slate'
-import {
-  IModalMenu,
-  IDomEditor,
-  genModalInputElems,
-  genModalButtonElems,
-  t,
-} from '@wangeditor/core'
-import $, { Dom7Array, DOMElement } from '../../../utils/dom'
-import { genRandomStr } from '../../../utils/util'
-import { LINK_SVG } from '../../../constants/icon-svg'
-import { isMenuDisabled, insertLink } from '../helper'
-
-/**
- * 生成唯一的 DOM ID
- */
-function genDomID(): string {
-  return genRandomStr('w-e-insert-link')
-}
-
-class InsertLinkMenu implements IModalMenu {
-  readonly title = t('link.insert')
-  readonly iconSvg = LINK_SVG
-  readonly tag = 'button'
-  readonly showModal = true // 点击 button 时显示 modal
-  readonly modalWidth = 300
-  private $content: Dom7Array | null = null
-  private readonly textInputId = genDomID()
-  private readonly urlInputId = genDomID()
-  private readonly buttonId = genDomID()
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 插入菜单,不需要 value
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 任何时候,都不用激活 menu
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    // 点击菜单时,弹出 modal 之前,不需要执行其他代码
-    // 此处空着即可
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    return isMenuDisabled(editor)
-  }
-
-  getModalPositionNode(editor: IDomEditor): Node | null {
-    return null // modal 依据选区定位
-  }
-
-  getModalContentElem(editor: IDomEditor): DOMElement {
-    const { selection } = editor
-    const { textInputId, urlInputId, buttonId } = this
-
-    // 获取 input button elem
-    const [textContainerElem, inputTextElem] = genModalInputElems(t('link.text'), textInputId)
-    const $inputText = $(inputTextElem)
-    const [urlContainerElem, inputUrlElem] = genModalInputElems(t('link.url'), urlInputId)
-    const $inputUrl = $(inputUrlElem)
-    const [buttonContainerElem] = genModalButtonElems(buttonId, t('common.ok'))
-
-    if (this.$content == null) {
-      // 第一次渲染
-      const $content = $('<div></div>')
-
-      // 绑定事件(第一次渲染时绑定,不要重复绑定)
-      $content.on('click', `#${buttonId}`, e => {
-        e.preventDefault()
-        const text = $content.find(`#${textInputId}`).val()
-        const url = $content.find(`#${urlInputId}`).val()
-        insertLink(editor, text, url) // 插入链接
-        editor.hidePanelOrModal() // 隐藏 modal
-      })
-
-      // 记录属性,重要
-      this.$content = $content
-    }
-
-    const $content = this.$content
-    $content.empty() // 先清空内容
-
-    // append inputs and button
-    $content.append(textContainerElem)
-    $content.append(urlContainerElem)
-    $content.append(buttonContainerElem)
-
-    // 设置 input val
-    if (selection == null || Range.isCollapsed(selection)) {
-      // 选区无内容
-      $inputText.val('')
-    } else {
-      // 选区有内容
-      const selectionText = Editor.string(editor, selection)
-      $inputText.val(selectionText)
-    }
-    $inputUrl.val('')
-
-    // focus 一个 input(异步,此时 DOM 尚未渲染)
-    setTimeout(() => {
-      $inputText.focus()
-    })
-
-    return $content[0]
-  }
-}
-
-export default InsertLinkMenu
diff --git a/packages/basic-modules/src/modules/link/menu/UnLink.ts b/packages/basic-modules/src/modules/link/menu/UnLink.ts
deleted file mode 100644
index d801eb8bf..000000000
--- a/packages/basic-modules/src/modules/link/menu/UnLink.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * @description unlink menu
- * @author wangfupeng
- */
-
-import { Transforms } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import { UN_LINK_SVG } from '../../../constants/icon-svg'
-
-class UnLink implements IButtonMenu {
-  readonly title = t('link.unLink')
-  readonly iconSvg = UN_LINK_SVG
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 无需获取 val
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const linkNode = DomEditor.getSelectedNodeByType(editor, 'link')
-    if (linkNode == null) {
-      // 选区未处于 link node ,则禁用
-      return true
-    }
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (this.isDisabled(editor)) return
-
-    // 取消链接
-    Transforms.unwrapNodes(editor, {
-      match: n => DomEditor.checkNodeType(n, 'link'),
-    })
-  }
-}
-
-export default UnLink
diff --git a/packages/basic-modules/src/modules/link/menu/ViewLink.ts b/packages/basic-modules/src/modules/link/menu/ViewLink.ts
deleted file mode 100644
index ead689771..000000000
--- a/packages/basic-modules/src/modules/link/menu/ViewLink.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * @description view link menu
- * @author wangfupeng
- */
-
-import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import { EXTERNAL_SVG } from '../../../constants/icon-svg'
-import { LinkElement } from '../custom-types'
-
-class ViewLink implements IButtonMenu {
-  readonly title = t('link.view')
-  readonly iconSvg = EXTERNAL_SVG
-  readonly tag = 'button'
-
-  private getSelectedLinkElem(editor: IDomEditor): LinkElement | null {
-    const node = DomEditor.getSelectedNodeByType(editor, 'link')
-    if (node == null) return null
-    return node as LinkElement
-  }
-
-  getValue(editor: IDomEditor): string | boolean {
-    const linkElem = this.getSelectedLinkElem(editor)
-    if (linkElem) {
-      return linkElem.url || ''
-    }
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // 无需 active
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const linkElem = this.getSelectedLinkElem(editor)
-    if (linkElem == null) {
-      // 选区未处于 link node ,则禁用
-      return true
-    }
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (this.isDisabled(editor)) return
-
-    if (!value || typeof value !== 'string') {
-      throw new Error(`View link failed, link url is '${value}'`)
-    }
-
-    // 查看链接
-    window.open(value, '_blank')
-  }
-}
-
-export default ViewLink
diff --git a/packages/basic-modules/src/modules/link/menu/config.ts b/packages/basic-modules/src/modules/link/menu/config.ts
deleted file mode 100644
index 7d843b2d5..000000000
--- a/packages/basic-modules/src/modules/link/menu/config.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @description link menu config
- * @author wangfupeng
- */
-
-export function genLinkMenuConfig() {
-  return {
-    /**
-     * 检查链接,支持 async fn
-     * @param text link text
-     * @param url link url
-     */
-    checkLink(text: string, url: string): boolean | string | undefined {
-      // 1. 返回 true ,说明检查通过
-      // 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
-      // 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入
-      return true
-    },
-
-    /**
-     * parse link url
-     * @param url url
-     * @returns newUrl
-     */
-    parseLinkUrl(url: string): string {
-      return url
-    },
-  }
-}
diff --git a/packages/basic-modules/src/modules/link/menu/index.ts b/packages/basic-modules/src/modules/link/menu/index.ts
deleted file mode 100644
index c75a8f936..000000000
--- a/packages/basic-modules/src/modules/link/menu/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * @description link menu entry
- * @author wangfupeng
- */
-
-import InsertLink from './InsertLink'
-import EditLink from './EditLink'
-import UnLink from './UnLink'
-import ViewLink from './ViewLink'
-import { genLinkMenuConfig } from './config'
-
-const config = genLinkMenuConfig() // menu config
-
-const insertLinkMenuConf = {
-  key: 'insertLink',
-  factory() {
-    return new InsertLink()
-  },
-
-  // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
-  // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
-  config,
-}
-
-const editLinkMenuConf = {
-  key: 'editLink',
-  factory() {
-    return new EditLink()
-  },
-  config,
-}
-
-const unLinkMenuConf = {
-  key: 'unLink',
-  factory() {
-    return new UnLink()
-  },
-}
-
-const viewLinkMenuConf = {
-  key: 'viewLink',
-  factory() {
-    return new ViewLink()
-  },
-}
-
-export { insertLinkMenuConf, editLinkMenuConf, unLinkMenuConf, viewLinkMenuConf }
diff --git a/packages/basic-modules/src/modules/link/parse-elem-html.ts b/packages/basic-modules/src/modules/link/parse-elem-html.ts
deleted file mode 100644
index 921ea56fc..000000000
--- a/packages/basic-modules/src/modules/link/parse-elem-html.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @description parse html
- * @author wangfupeng
- */
-
-import { Descendant, Text } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { LinkElement } from './custom-types'
-import $, { DOMElement } from '../../utils/dom'
-
-function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): LinkElement {
-  const $elem = $(elem)
-  children = children.filter(child => {
-    if (Text.isText(child)) return true
-    if (editor.isInline(child)) return true
-    return false
-  })
-
-  // 无 children ,则用纯文本
-  if (children.length === 0) {
-    children = [{ text: $elem.text().replace(/\s+/gm, ' ') }]
-  }
-
-  return {
-    type: 'link',
-    url: $elem.attr('href') || '',
-    target: $elem.attr('target') || '',
-    // @ts-ignore
-    children,
-  }
-}
-
-export const parseHtmlConf = {
-  selector: 'a:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: parseHtml,
-}
diff --git a/packages/basic-modules/src/modules/link/plugin.ts b/packages/basic-modules/src/modules/link/plugin.ts
deleted file mode 100644
index 0d23e0cef..000000000
--- a/packages/basic-modules/src/modules/link/plugin.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * @description editor 插件,重写 editor API
- * @author wangfupeng
- */
-
-import { Editor, Node, Transforms } from 'slate'
-import { DomEditor, IDomEditor } from '@wangeditor/core'
-import isUrl from 'is-url'
-import { isMenuDisabled, insertLink } from './helper'
-
-function withLink<T extends IDomEditor>(editor: T): T {
-  const { isInline, insertData, normalizeNode, insertNode, insertText } = editor
-  const newEditor = editor
-
-  // 重写 isInline
-  newEditor.isInline = elem => {
-    const { type } = elem
-
-    if (type === 'link') {
-      return true
-    }
-
-    return isInline(elem)
-  }
-
-  // 重写 insertData ,粘贴插入链接
-  newEditor.insertData = (data: DataTransfer) => {
-    const text = data.getData('text/plain')
-    if (!isUrl(text)) {
-      // 非链接
-      insertData(data)
-      return
-    }
-
-    // 插入链接
-    if (isMenuDisabled(newEditor)) return // disabled
-    const { selection } = newEditor
-    if (selection == null) return
-    const selectedText = Editor.string(newEditor, selection) // 获取选中的文字
-    insertLink(newEditor, selectedText, text)
-  }
-
-  newEditor.normalizeNode = ([node, path]) => {
-    const type = DomEditor.getNodeType(node)
-    if (type !== 'link') {
-      // 未命中 link ,执行默认的 normalizeNode
-      return normalizeNode([node, path])
-    }
-
-    // 如果链接内容为空,则删除
-    const str = Node.string(node)
-    if (str === '') {
-      return Transforms.removeNodes(newEditor, { at: path })
-    }
-
-    return normalizeNode([node, path])
-  }
-
-  // 返回 editor ,重要!
-  return newEditor
-}
-
-export default withLink
diff --git a/packages/basic-modules/src/modules/link/render-elem.tsx b/packages/basic-modules/src/modules/link/render-elem.tsx
deleted file mode 100644
index 184f8be3d..000000000
--- a/packages/basic-modules/src/modules/link/render-elem.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @description render link elem
- * @author wangfupeng
- */
-
-import { Element as SlateElement } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { IDomEditor } from '@wangeditor/core'
-import { LinkElement } from './custom-types'
-
-/**
- * render link elem
- * @param elemNode slate elem
- * @param children children
- * @param editor editor
- * @returns vnode
- */
-function renderLink(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
-  const { url, target = '_blank' } = elemNode as LinkElement
-  const vnode = (
-    <a href={url} target={target}>
-      {children}
-    </a>
-  )
-
-  return vnode
-}
-
-const renderLinkConf = {
-  type: 'link', // 和 elemNode.type 一致
-  renderElem: renderLink,
-}
-
-export { renderLinkConf }
diff --git a/packages/basic-modules/src/modules/paragraph/custom-types.ts b/packages/basic-modules/src/modules/paragraph/custom-types.ts
deleted file mode 100644
index 950019573..000000000
--- a/packages/basic-modules/src/modules/paragraph/custom-types.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-export type ParagraphElement = {
-  type: 'paragraph'
-  children: Text[]
-}
diff --git a/packages/basic-modules/src/modules/paragraph/elem-to-html.ts b/packages/basic-modules/src/modules/paragraph/elem-to-html.ts
deleted file mode 100644
index 6c8da1481..000000000
--- a/packages/basic-modules/src/modules/paragraph/elem-to-html.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @description to html
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-
-function pToHtml(elem: Element, childrenHtml: string): string {
-  if (childrenHtml === '') {
-    return '<p><br></p>'
-  }
-  return `<p>${childrenHtml}</p>`
-}
-
-export const pToHtmlConf = {
-  type: 'paragraph',
-  elemToHtml: pToHtml,
-}
diff --git a/packages/basic-modules/src/modules/paragraph/index.ts b/packages/basic-modules/src/modules/paragraph/index.ts
deleted file mode 100644
index 80c1e64d1..000000000
--- a/packages/basic-modules/src/modules/paragraph/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @description paragraph entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderParagraphConf } from './render-elem'
-import { pToHtmlConf } from './elem-to-html'
-import { parseParagraphHtmlConf } from './parse-elem-html'
-import withParagraph from './plugin'
-
-const p: Partial<IModuleConf> = {
-  renderElems: [renderParagraphConf],
-  elemsToHtml: [pToHtmlConf],
-  parseElemsHtml: [parseParagraphHtmlConf],
-  editorPlugin: withParagraph,
-}
-
-export default p
diff --git a/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts b/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts
deleted file mode 100644
index 0743ad46a..000000000
--- a/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @description parse html
- * @author wangfupeng
- */
-
-import { Descendant, Text } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { ParagraphElement } from './custom-types'
-import $, { DOMElement } from '../../utils/dom'
-
-function parseParagraphHtml(
-  elem: DOMElement,
-  children: Descendant[],
-  editor: IDomEditor
-): ParagraphElement {
-  const $elem = $(elem)
-
-  children = children.filter(child => {
-    if (Text.isText(child)) return true
-    if (editor.isInline(child)) return true
-    return false
-  })
-
-  // 无 children ,则用纯文本
-  if (children.length === 0) {
-    children = [{ text: $elem.text().replace(/\s+/gm, ' ') }]
-  }
-
-  return {
-    type: 'paragraph',
-    // @ts-ignore
-    children,
-  }
-}
-
-export const parseParagraphHtmlConf = {
-  selector: 'p:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
-  parseElemHtml: parseParagraphHtml,
-}
diff --git a/packages/basic-modules/src/modules/paragraph/plugin.ts b/packages/basic-modules/src/modules/paragraph/plugin.ts
deleted file mode 100644
index d58af4ee2..000000000
--- a/packages/basic-modules/src/modules/paragraph/plugin.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @description editor 插件,重写 editor API
- * @author wangfupeng
- */
-
-import {
-  Editor,
-  Element as SlateElement,
-  Transforms,
-  Node as SlateNode,
-  Text as SlateText,
-} from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-
-function deleteHandler(newEditor: IDomEditor): boolean {
-  const [nodeEntry] = Editor.nodes(newEditor, {
-    match: n => newEditor.children[0] === n, // editor 第一个节点
-    mode: 'highest', // 最高层级
-  })
-  if (nodeEntry == null) return false
-
-  const n = nodeEntry[0]
-  if (!SlateElement.isElement(n)) return false
-  if (n.type === 'paragraph') return false // 命中了 paragraph ,则不再继续判断
-  if (SlateNode.string(n) !== '') return false // 未删除全部内容,则不再继续判断
-
-  const { children = [] } = n
-  if (!SlateText.isText(children[0])) return false // n.children 不是 text (如 table),则不再继续判断
-
-  // 至此,就命中了一个(非 paragraph)+(children 都是 text)+(内容为空)的顶级 node ,如 header blockQuote 等
-  // 然后,将其却换为 paragraph
-  Transforms.setNodes(newEditor, {
-    type: 'paragraph',
-  })
-  return true
-}
-
-function withParagraph<T extends IDomEditor>(editor: T): T {
-  const { deleteBackward, deleteForward, insertText, insertBreak } = editor
-  const newEditor = editor
-
-  // 删除非 p 的文本 elem(如 header blockQuote 等),删除没有内容时,切换为 p
-  newEditor.deleteBackward = unit => {
-    const res = deleteHandler(newEditor)
-    if (res) return // 命中结果,则 return
-
-    // 执行默认的删除
-    deleteBackward(unit)
-  }
-  newEditor.deleteForward = unit => {
-    const res = deleteHandler(newEditor)
-    if (res) return // 命中结果,则 return
-
-    // 执行默认的删除
-    deleteForward(unit)
-  }
-
-  // 返回 editor ,重要!
-  return newEditor
-}
-
-export default withParagraph
diff --git a/packages/basic-modules/src/modules/paragraph/render-elem.tsx b/packages/basic-modules/src/modules/paragraph/render-elem.tsx
deleted file mode 100644
index a25fea5f9..000000000
--- a/packages/basic-modules/src/modules/paragraph/render-elem.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @description render paragraph elem
- * @author wangfupeng
- */
-
-import { Element as SlateElement } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { IDomEditor } from '@wangeditor/core'
-
-/**
- * render paragraph elem
- * @param elemNode slate elem
- * @param children children
- * @param editor editor
- * @returns vnode
- */
-function renderParagraph(
-  elemNode: SlateElement,
-  children: VNode[] | null,
-  editor: IDomEditor
-): VNode {
-  const vnode = <p>{children}</p>
-  return vnode
-}
-
-export const renderParagraphConf = {
-  type: 'paragraph',
-  renderElem: renderParagraph,
-}
diff --git a/packages/basic-modules/src/modules/text-style/custom-types.ts b/packages/basic-modules/src/modules/text-style/custom-types.ts
deleted file mode 100644
index 30ee06a65..000000000
--- a/packages/basic-modules/src/modules/text-style/custom-types.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-//【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts
-
-export type StyledText = {
-  text: string
-  bold?: boolean
-  code?: boolean
-  italic?: boolean
-  through?: boolean
-  underline?: boolean
-  sup?: boolean
-  sub?: boolean
-}
diff --git a/packages/basic-modules/src/modules/text-style/helper.ts b/packages/basic-modules/src/modules/text-style/helper.ts
deleted file mode 100644
index 9d11bf4f2..000000000
--- a/packages/basic-modules/src/modules/text-style/helper.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @description helper
- * @author wangfupeng
- */
-
-import { Editor, Node } from 'slate'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-
-export function isMenuDisabled(editor: IDomEditor, mark?: string): boolean {
-  if (editor.selection == null) return true
-
-  const [match] = Editor.nodes(editor, {
-    match: n => {
-      const type = DomEditor.getNodeType(n)
-
-      if (type === 'pre') return true // 代码块
-      if (Editor.isVoid(editor, n)) return true // void node
-
-      return false
-    },
-    universal: true,
-  })
-
-  // 命中,则禁用
-  if (match) return true
-  return false
-}
-
-export function removeMarks(editor: IDomEditor, textNode: Node) {
-  // 遍历 text node 属性,清除样式
-  const keys = Object.keys(textNode as object)
-  keys.forEach(key => {
-    if (key === 'text') {
-      // 保留 text 属性,text node 必须的
-      return
-    }
-    // 其他属性,全部清除
-    Editor.removeMark(editor, key)
-  })
-}
diff --git a/packages/basic-modules/src/modules/text-style/index.ts b/packages/basic-modules/src/modules/text-style/index.ts
deleted file mode 100644
index 43cefa5e0..000000000
--- a/packages/basic-modules/src/modules/text-style/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @description text style entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderStyle } from './render-style'
-import { styleToHtml } from './style-to-html'
-import { parseStyleHtml } from './parse-style-html'
-import {
-  boldMenuConf,
-  underlineMenuConf,
-  italicMenuConf,
-  throughMenuConf,
-  codeMenuConf,
-  subMenuConf,
-  supMenuConf,
-  clearStyleMenuConf,
-} from './menu/index'
-
-const textStyle: Partial<IModuleConf> = {
-  renderStyle,
-  menus: [
-    boldMenuConf,
-    underlineMenuConf,
-    italicMenuConf,
-    throughMenuConf,
-    codeMenuConf,
-    subMenuConf,
-    supMenuConf,
-    clearStyleMenuConf,
-  ],
-  styleToHtml,
-  parseStyleHtml,
-}
-
-export default textStyle
diff --git a/packages/basic-modules/src/modules/text-style/menu/BaseMenu.ts b/packages/basic-modules/src/modules/text-style/menu/BaseMenu.ts
deleted file mode 100644
index 74c5e1a2e..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/BaseMenu.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * @description simply style base menu
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import { IButtonMenu, IDomEditor } from '@wangeditor/core'
-import { isMenuDisabled } from '../helper'
-
-abstract class BaseMenu implements IButtonMenu {
-  abstract readonly mark: string
-  protected readonly marksNeedToRemove: string[] = [] // 增加 mark 的同时,需要移除哪些 mark (互斥,不能共存的)
-  abstract readonly title: string
-  abstract readonly iconSvg: string
-  abstract readonly hotkey: string
-  readonly tag = 'button'
-
-  /**
-   * 获取:是否有 mark
-   * @param editor editor
-   */
-  getValue(editor: IDomEditor): string | boolean {
-    const mark = this.mark
-    const curMarks = Editor.marks(editor)
-
-    // 当 curMarks 存在时,说明用户手动设置,以 curMarks 为准
-    if (curMarks) {
-      return curMarks[mark]
-    } else {
-      const [match] = Editor.nodes(editor, {
-        // @ts-ignore
-        match: n => n[mark] === true,
-      })
-      return !!match
-    }
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    const isMark = this.getValue(editor)
-    return !!isMark
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    return isMenuDisabled(editor, this.mark)
-  }
-
-  /**
-   * 执行命令
-   * @param editor editor
-   * @param value 是否有 mark
-   */
-  exec(editor: IDomEditor, value: string | boolean) {
-    const { mark, marksNeedToRemove } = this
-    if (value) {
-      // 已,则取消
-      editor.removeMark(mark)
-    } else {
-      // 没有,则执行
-      editor.addMark(mark, true)
-
-      // 移除互斥、不能共存的 marks
-      if (marksNeedToRemove) {
-        marksNeedToRemove.forEach(m => editor.removeMark(m))
-      }
-    }
-  }
-}
-
-export default BaseMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts b/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts
deleted file mode 100644
index b0f982eec..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description bold menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { BOLD_SVG } from '../../../constants/icon-svg'
-
-class BoldMenu extends BaseMenu {
-  readonly mark = 'bold'
-  readonly title = t('textStyle.bold')
-  readonly iconSvg = BOLD_SVG
-  readonly hotkey = 'mod+b'
-}
-
-export default BoldMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/ClearStyleMenu.ts b/packages/basic-modules/src/modules/text-style/menu/ClearStyleMenu.ts
deleted file mode 100644
index c2373abc1..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/ClearStyleMenu.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * @description clear style menu
- * @author wangfupeng
- */
-
-import { Editor, Text } from 'slate'
-import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
-import { ERASER_SVG } from '../../../constants/icon-svg'
-import { isMenuDisabled, removeMarks } from '../helper'
-
-class ClearStyleMenu implements IButtonMenu {
-  readonly title = t('textStyle.clear')
-  readonly iconSvg = ERASER_SVG
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    return isMenuDisabled(editor)
-  }
-
-  /**
-   * 执行命令
-   * @param editor editor
-   * @param value 是否有 mark
-   */
-  exec(editor: IDomEditor, value: string | boolean) {
-    // 获取所有 text node
-    const nodeEntries = Editor.nodes(editor, {
-      match: n => Text.isText(n),
-      universal: true,
-    })
-    for (const nodeEntry of nodeEntries) {
-      // 单个 text node
-      const n = nodeEntry[0]
-      removeMarks(editor, n)
-    }
-  }
-}
-
-export default ClearStyleMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts b/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts
deleted file mode 100644
index f9bd87c64..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description code menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { CODE_SVG } from '../../../constants/icon-svg'
-
-class CodeMenu extends BaseMenu {
-  readonly mark = 'code'
-  readonly title = t('textStyle.code')
-  readonly iconSvg = CODE_SVG
-  readonly hotkey = 'mod+e'
-}
-
-export default CodeMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts b/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts
deleted file mode 100644
index 9e34abd67..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description italic menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { ITALIC_SVG } from '../../../constants/icon-svg'
-
-class ItalicMenu extends BaseMenu {
-  readonly mark = 'italic'
-  readonly title = t('textStyle.italic')
-  readonly iconSvg = ITALIC_SVG
-  readonly hotkey = 'mod+i'
-}
-
-export default ItalicMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts b/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts
deleted file mode 100644
index 870750a40..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @description sub menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { SUB_SVG } from '../../../constants/icon-svg'
-
-class SubMenu extends BaseMenu {
-  readonly mark = 'sub'
-  readonly marksNeedToRemove = ['sup'] // sub 和 sup 不能共存
-  readonly title = t('textStyle.sub')
-  readonly iconSvg = SUB_SVG
-  readonly hotkey = ''
-}
-
-export default SubMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts b/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts
deleted file mode 100644
index 2361b2e46..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @description sup menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { SUP_SVG } from '../../../constants/icon-svg'
-
-class SupMenu extends BaseMenu {
-  readonly mark = 'sup'
-  readonly marksNeedToRemove = ['sub'] // sup 和 sub 不能共存
-  readonly title = t('textStyle.sup')
-  readonly iconSvg = SUP_SVG
-  readonly hotkey = ''
-}
-
-export default SupMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts b/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts
deleted file mode 100644
index 2bd08a170..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description through menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { THROUGH_SVG } from '../../../constants/icon-svg'
-
-class ThroughMenu extends BaseMenu {
-  readonly mark = 'through'
-  readonly title = t('textStyle.through')
-  readonly iconSvg = THROUGH_SVG
-  readonly hotkey = 'mod+shift+x'
-}
-
-export default ThroughMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts b/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts
deleted file mode 100644
index f95a56c1f..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description underline menu
- * @author wangfupeng
- */
-
-import { t } from '@wangeditor/core'
-import BaseMenu from './BaseMenu'
-import { UNDER_LINE_SVG } from '../../../constants/icon-svg'
-
-class UnderlineMenu extends BaseMenu {
-  readonly mark = 'underline'
-  readonly title = t('textStyle.underline')
-  readonly iconSvg = UNDER_LINE_SVG
-  readonly hotkey = 'mod+u'
-}
-
-export default UnderlineMenu
diff --git a/packages/basic-modules/src/modules/text-style/menu/index.ts b/packages/basic-modules/src/modules/text-style/menu/index.ts
deleted file mode 100644
index 5813c568f..000000000
--- a/packages/basic-modules/src/modules/text-style/menu/index.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * @description menu entry
- * @author wangfupeng
- */
-
-import BoldMenu from './BoldMenu'
-import CodeMenu from './CodeMenu'
-import ItalicMenu from './ItalicMenu'
-import ThroughMenu from './ThroughMenu'
-import UnderlineMenu from './UnderlineMenu'
-import SubMenu from './SubMenu'
-import SupMenu from './SupMenu'
-import ClearStyleMenu from './ClearStyleMenu'
-
-export const boldMenuConf = {
-  key: 'bold',
-  factory() {
-    return new BoldMenu()
-  },
-}
-
-export const codeMenuConf = {
-  key: 'code',
-  factory() {
-    return new CodeMenu()
-  },
-}
-
-export const italicMenuConf = {
-  key: 'italic',
-  factory() {
-    return new ItalicMenu()
-  },
-}
-
-export const throughMenuConf = {
-  key: 'through',
-  factory() {
-    return new ThroughMenu()
-  },
-}
-
-export const underlineMenuConf = {
-  key: 'underline',
-  factory() {
-    return new UnderlineMenu()
-  },
-}
-
-export const supMenuConf = {
-  key: 'sup',
-  factory() {
-    return new SupMenu()
-  },
-}
-
-export const subMenuConf = {
-  key: 'sub',
-  factory() {
-    return new SubMenu()
-  },
-}
-
-export const clearStyleMenuConf = {
-  key: 'clearStyle',
-  factory() {
-    return new ClearStyleMenu()
-  },
-}
diff --git a/packages/basic-modules/src/modules/text-style/parse-style-html.ts b/packages/basic-modules/src/modules/text-style/parse-style-html.ts
deleted file mode 100644
index 164a4938d..000000000
--- a/packages/basic-modules/src/modules/text-style/parse-style-html.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * @description parse style html
- * @author wangfupeng
- */
-
-import { Descendant, Text } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { StyledText } from './custom-types'
-import $, { Dom7Array, DOMElement } from '../../utils/dom'
-
-/**
- * $text 是否匹配 tags
- * @param $text $text
- * @param selector selector 如 'b,strong' 或 'sub'
- */
-function isMatch($text: Dom7Array, selector: string): boolean {
-  if ($text.length === 0) return false
-
-  if ($text[0].matches(selector)) return true
-
-  if ($text.find(selector).length > 0) return true
-
-  return false
-}
-
-export function parseStyleHtml(
-  textElem: DOMElement,
-  node: Descendant,
-  editor: IDomEditor
-): Descendant {
-  const $text = $(textElem)
-
-  if (!Text.isText(node)) return node
-
-  const textNode = node as StyledText
-
-  // bold
-  if (isMatch($text, 'b,strong')) {
-    textNode.bold = true
-  }
-
-  // italic
-  if (isMatch($text, 'i,em')) {
-    textNode.italic = true
-  }
-
-  // underline
-  if (isMatch($text, 'u')) {
-    textNode.underline = true
-  }
-
-  // through
-  if (isMatch($text, 's,strike')) {
-    textNode.through = true
-  }
-
-  // sub
-  if (isMatch($text, 'sub')) {
-    textNode.sub = true
-  }
-
-  // sup
-  if (isMatch($text, 'sup')) {
-    textNode.sup = true
-  }
-
-  // code
-  if (isMatch($text, 'code')) {
-    textNode.code = true
-  }
-
-  return textNode
-}
diff --git a/packages/basic-modules/src/modules/text-style/render-style.tsx b/packages/basic-modules/src/modules/text-style/render-style.tsx
deleted file mode 100644
index f0efd0dbb..000000000
--- a/packages/basic-modules/src/modules/text-style/render-style.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @description render text style
- * @author wangfupeng
- */
-
-import { Descendant } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { StyledText } from './custom-types'
-
-/**
- * 添加样式
- * @param node slate text
- * @param vnode vnode
- * @returns vnode
- */
-export function renderStyle(node: Descendant, vnode: VNode): VNode {
-  const { bold, italic, underline, code, through, sub, sup } = node as StyledText
-  let styleVnode: VNode = vnode
-
-  // color bgColor 在另外的菜单
-
-  if (bold) {
-    styleVnode = <strong>{styleVnode}</strong>
-  }
-  if (code) {
-    styleVnode = <code>{styleVnode}</code>
-  }
-  if (italic) {
-    styleVnode = <em>{styleVnode}</em>
-  }
-  if (underline) {
-    styleVnode = <u>{styleVnode}</u>
-  }
-  if (through) {
-    styleVnode = <s>{styleVnode}</s>
-  }
-  if (sub) {
-    styleVnode = <sub>{styleVnode}</sub>
-  }
-  if (sup) {
-    styleVnode = <sup>{styleVnode}</sup>
-  }
-
-  return styleVnode
-}
diff --git a/packages/basic-modules/src/modules/text-style/style-to-html.ts b/packages/basic-modules/src/modules/text-style/style-to-html.ts
deleted file mode 100644
index 13988c544..000000000
--- a/packages/basic-modules/src/modules/text-style/style-to-html.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * @description text to html
- * @author wangfupeng
- */
-
-import { Text, Descendant } from 'slate'
-import { StyledText } from './custom-types'
-import $, { getOuterHTML, getTagName, isPlainText } from '../../utils/dom'
-
-//【注意】color bgColor fontSize fontFamily 在另外的菜单
-
-/**
- * 生成加了样式的 text html
- * @param textNode textNode
- * @param html text html
- */
-function genStyledHtml(textNode: Descendant, html: string): string {
-  let styledHtml = html
-  const { bold, italic, underline, code, through, sub, sup } = textNode as StyledText
-  if (bold) styledHtml = `<strong>${styledHtml}</strong>`
-  if (code) styledHtml = `<code>${styledHtml}</code>`
-  if (italic) styledHtml = `<em>${styledHtml}</em>`
-  if (underline) styledHtml = `<u>${styledHtml}</u>`
-  if (through) styledHtml = `<s>${styledHtml}</s>`
-  if (sub) styledHtml = `<sub>${styledHtml}</sub>`
-  if (sup) styledHtml = `<sup>${styledHtml}</sup>`
-  return styledHtml
-}
-
-/**
- * style to html
- * @param textNode slate text node
- * @param textHtml text html
- * @returns styled html
- */
-export function styleToHtml(textNode: Descendant, textHtml: string): string {
-  if (!Text.isText(textNode)) return textHtml
-
-  if (isPlainText(textHtml)) {
-    // textHtml 是纯文本,而不是 html tag
-    return genStyledHtml(textNode, textHtml)
-  }
-
-  // textHtml 是 html tag
-  const $text = $(textHtml)
-  const tagName = getTagName($text)
-
-  if (tagName === 'br') {
-    return genStyledHtml(textNode, '<br>')
-  }
-
-  let innerHtml = $text.html()
-  innerHtml = genStyledHtml(textNode, innerHtml)
-  $text.html(innerHtml)
-  return getOuterHTML($text)
-}
diff --git a/packages/basic-modules/src/modules/todo/custom-types.ts b/packages/basic-modules/src/modules/todo/custom-types.ts
deleted file mode 100644
index 32cc97d0b..000000000
--- a/packages/basic-modules/src/modules/todo/custom-types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
-
-export type TodoElement = {
-  type: 'todo'
-  checked: boolean
-  children: Text[]
-}
diff --git a/packages/basic-modules/src/modules/todo/elem-to-html.ts b/packages/basic-modules/src/modules/todo/elem-to-html.ts
deleted file mode 100644
index 4a255ba63..000000000
--- a/packages/basic-modules/src/modules/todo/elem-to-html.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @description to html
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-import { TodoElement } from './custom-types'
-
-function todoToHtml(elem: Element, childrenHtml: string): string {
-  const { checked } = elem as TodoElement
-  const checkedAttr = checked ? 'checked' : ''
-  return `<div data-w-e-type="todo"><input type="checkbox" disabled ${checkedAttr}>${childrenHtml}</div>`
-}
-
-export const todoToHtmlConf = {
-  type: 'todo',
-  elemToHtml: todoToHtml,
-}
diff --git a/packages/basic-modules/src/modules/todo/index.ts b/packages/basic-modules/src/modules/todo/index.ts
deleted file mode 100644
index e65f9fbf3..000000000
--- a/packages/basic-modules/src/modules/todo/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @description todo entry
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderTodoConf } from './render-elem'
-import withTodo from './plugin'
-import { todoMenuConf } from './menu/index'
-import { todoToHtmlConf } from './elem-to-html'
-import { parseHtmlConf } from './parse-elem-html'
-import { preParseHtmlConf } from './pre-parse-html'
-
-const todo: Partial<IModuleConf> = {
-  renderElems: [renderTodoConf],
-  elemsToHtml: [todoToHtmlConf],
-  preParseHtml: [preParseHtmlConf],
-  parseElemsHtml: [parseHtmlConf],
-  menus: [todoMenuConf],
-  editorPlugin: withTodo,
-}
-
-export default todo
diff --git a/packages/basic-modules/src/modules/todo/menu/Todo.ts b/packages/basic-modules/src/modules/todo/menu/Todo.ts
deleted file mode 100644
index 837466d53..000000000
--- a/packages/basic-modules/src/modules/todo/menu/Todo.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @description Todo menu
- * @author wangfupeng
- */
-
-import { Editor, Element, Transforms } from 'slate'
-import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core'
-import { CHECK_BOX_SVG } from '../../../constants/icon-svg'
-
-class TodoMenu implements IButtonMenu {
-  readonly title = t('todo.todo')
-  readonly iconSvg = CHECK_BOX_SVG
-  readonly tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    // 无需获取 val
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    return !!DomEditor.getSelectedNodeByType(editor, 'todo')
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-
-    const selectedElems = DomEditor.getSelectedElems(editor)
-    const notMatch = selectedElems.some((elem: Element) => {
-      if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem)) return true
-
-      const { type } = elem as Element
-      if (['pre', 'table', 'list-item'].includes(type)) return true
-    })
-    if (notMatch) return true
-
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    const active = this.isActive(editor)
-    Transforms.setNodes(editor, { type: active ? 'paragraph' : 'todo' })
-  }
-}
-
-export default TodoMenu
diff --git a/packages/basic-modules/src/modules/todo/menu/index.ts b/packages/basic-modules/src/modules/todo/menu/index.ts
deleted file mode 100644
index 76f4df364..000000000
--- a/packages/basic-modules/src/modules/todo/menu/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description todo menu entry
- * @author wangfupeng
- */
-
-import TodoMenu from './Todo'
-
-export const todoMenuConf = {
-  key: 'todo',
-  factory() {
-    return new TodoMenu()
-  },
-}
diff --git a/packages/basic-modules/src/modules/todo/parse-elem-html.ts b/packages/basic-modules/src/modules/todo/parse-elem-html.ts
deleted file mode 100644
index 3d449f460..000000000
--- a/packages/basic-modules/src/modules/todo/parse-elem-html.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * @description parse html
- * @author wangfupeng
- */
-
-import { Descendant, Text } from 'slate'
-import { IDomEditor } from '@wangeditor/core'
-import { TodoElement } from './custom-types'
-import $, { DOMElement } from '../../utils/dom'
-
-function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): TodoElement {
-  const $elem = $(elem)
-
-  children = children.filter(child => {
-    if (Text.isText(child)) return true
-    if (editor.isInline(child)) return true
-    return false
-  })
-
-  // 无 children ,则用纯文本
-  if (children.length === 0) {
-    children = [{ text: $elem.text().replace(/\s+/gm, ' ') }]
-  }
-
-  // 获取 checked
-  let checked = false
-  const $input = $elem.find('input[type="checkbox"]')
-  if ($input.attr('checked') != null) {
-    checked = true
-  }
-
-  return {
-    type: 'todo',
-    checked,
-    // @ts-ignore
-    children,
-  }
-}
-
-export const parseHtmlConf = {
-  selector: 'div[data-w-e-type="todo"]',
-  parseElemHtml: parseHtml,
-}
diff --git a/packages/basic-modules/src/modules/todo/plugin.ts b/packages/basic-modules/src/modules/todo/plugin.ts
deleted file mode 100644
index 37070cd85..000000000
--- a/packages/basic-modules/src/modules/todo/plugin.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @description editor 插件,重写 editor API
- * @author wangfupeng
- */
-
-import { Node, Transforms, Range } from 'slate'
-import { DomEditor, IDomEditor } from '@wangeditor/core'
-
-function withTodo<T extends IDomEditor>(editor: T): T {
-  const { deleteBackward } = editor
-  const newEditor = editor
-
-  /**
-   * 删除 todo 无内容时,变为 paragraph
-   */
-  newEditor.deleteBackward = unit => {
-    const { selection } = editor
-
-    if (selection && Range.isCollapsed(selection)) {
-      // 获取选中的 todo
-      const selectedTodo = DomEditor.getSelectedNodeByType(editor, 'todo')
-      if (selectedTodo) {
-        if (Node.string(selectedTodo).length === 0) {
-          // 当前 todo 已经没有文字,则转换为 paragraph
-          Transforms.setNodes(editor, { type: 'paragraph' }, { mode: 'highest' })
-          return
-        }
-      }
-    }
-
-    deleteBackward(unit)
-  }
-
-  return newEditor
-}
-
-export default withTodo
diff --git a/packages/basic-modules/src/modules/todo/pre-parse-html.ts b/packages/basic-modules/src/modules/todo/pre-parse-html.ts
deleted file mode 100644
index 86e6469e0..000000000
--- a/packages/basic-modules/src/modules/todo/pre-parse-html.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * @description pre parse html
- * @author wangfupeng
- */
-
-import $, { DOMElement } from '../../utils/dom'
-
-/**
- * pre-prase todo ,兼容 V4
- * @param elem elem
- */
-function preParse(elem: DOMElement): DOMElement {
-  const $elem = $(elem)
-
-  // $elem 格式如
-  // <ul class="w-e-todo"><li><span contenteditable="false"><input type="checkbox"/></span>hello <b>world</b></li></ul>
-  const $li = $elem.find('li')
-
-  const $container = $('<div data-w-e-type="todo"></div>')
-
-  // 1. 把 input 移动到 $container
-  const $input = $li.find('input[type]')
-  $container.append($input)
-
-  // 2. 删除之前包裹 input 的 span
-  const $spanForInput = $li.children()[0]
-  $spanForInput.remove()
-
-  // 3. 再把剩余的内容移动到 $container (有纯文本内容,不能用 children ,得用 innerHTML)
-  $container[0].innerHTML = $container[0].innerHTML + $li[0].innerHTML
-
-  return $container[0]
-}
-
-export const preParseHtmlConf = {
-  selector: 'ul.w-e-todo', // 匹配 v4 todo
-  preParseHtml: preParse,
-}
diff --git a/packages/basic-modules/src/modules/todo/render-elem.tsx b/packages/basic-modules/src/modules/todo/render-elem.tsx
deleted file mode 100644
index eaa7c813a..000000000
--- a/packages/basic-modules/src/modules/todo/render-elem.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * @description render todo
- * @author wangfupeng
- */
-
-import { Element as SlateElement, Transforms } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { IDomEditor, DomEditor } from '@wangeditor/core'
-import { TodoElement } from './custom-types'
-
-/**
- * render todo elem
- * @param elemNode slate elem
- * @param children children
- * @param editor editor
- * @returns vnode
- */
-function renderTodo(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
-  // 判断 disabled
-  let disabled = false
-  if (editor.isDisabled()) disabled = true
-
-  const { checked } = elemNode as TodoElement
-  const vnode = (
-    <div style={{ margin: '5px 0' }}>
-      <span contentEditable={false} style={{ marginRight: '0.5em' }}>
-        <input
-          type="checkbox"
-          checked={checked}
-          disabled={disabled}
-          on={{
-            change: event => {
-              const path = DomEditor.findPath(editor, elemNode)
-              const newProps: Partial<TodoElement> = {
-                // @ts-ignore
-                checked: event.target.checked,
-              }
-              Transforms.setNodes(editor, newProps, { at: path })
-            },
-          }}
-        />
-      </span>
-      <span>{children}</span>
-    </div>
-  )
-
-  return vnode
-}
-
-const renderTodoConf = {
-  type: 'todo', // 和 elemNode.type 一致
-  renderElem: renderTodo,
-}
-
-export { renderTodoConf }
diff --git a/packages/basic-modules/src/modules/undo-redo/index.ts b/packages/basic-modules/src/modules/undo-redo/index.ts
deleted file mode 100644
index 25e6a2b2b..000000000
--- a/packages/basic-modules/src/modules/undo-redo/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description undo redo
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { redoMenuConf, undoMenuConf } from './menu/index'
-
-const undoRedo: Partial<IModuleConf> = {
-  menus: [redoMenuConf, undoMenuConf],
-}
-
-export default undoRedo
diff --git a/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts b/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts
deleted file mode 100644
index 48af32088..000000000
--- a/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @description redo menu
- * @author wangfupeng
- */
-
-import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
-import { REDO_SVG } from '../../../constants/icon-svg'
-
-class RedoMenu implements IButtonMenu {
-  title = t('undo.redo')
-  iconSvg = REDO_SVG
-  tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (typeof editor.redo === 'function') {
-      editor.redo()
-    }
-  }
-}
-
-export default RedoMenu
diff --git a/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts b/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts
deleted file mode 100644
index e195fabf3..000000000
--- a/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @description undo menu
- * @author wangfupeng
- */
-
-import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
-import { UNDO_SVG } from '../../../constants/icon-svg'
-
-class UndoMenu implements IButtonMenu {
-  title = t('undo.undo')
-  iconSvg = UNDO_SVG
-  tag = 'button'
-
-  getValue(editor: IDomEditor): string | boolean {
-    return ''
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    return false
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-    return false
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    if (typeof editor.undo === 'function') {
-      editor.undo()
-    }
-  }
-}
-
-export default UndoMenu
diff --git a/packages/basic-modules/src/modules/undo-redo/menu/index.ts b/packages/basic-modules/src/modules/undo-redo/menu/index.ts
deleted file mode 100644
index 1e3254bf7..000000000
--- a/packages/basic-modules/src/modules/undo-redo/menu/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description menu entry
- * @author wangfupeng
- */
-
-import RedoMenu from './RedoMenu'
-import UndoMenu from './UndoMenu'
-
-export const undoMenuConf = {
-  key: 'undo',
-  factory() {
-    return new UndoMenu()
-  },
-}
-
-export const redoMenuConf = {
-  key: 'redo',
-  factory() {
-    return new RedoMenu()
-  },
-}
diff --git a/packages/basic-modules/src/utils/dom.ts b/packages/basic-modules/src/utils/dom.ts
deleted file mode 100644
index 45d61ecab..000000000
--- a/packages/basic-modules/src/utils/dom.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-/**
- * @description DOM 操作
- * @author wangfupeng
- */
-
-import $, {
-  css,
-  append,
-  prepend,
-  addClass,
-  removeClass,
-  hasClass,
-  on,
-  off,
-  focus,
-  attr,
-  hide,
-  show,
-  parents,
-  dataset,
-  val,
-  text,
-  removeAttr,
-  children,
-  html,
-  remove,
-  find,
-  width,
-  height,
-  Dom7Array,
-  filter,
-  empty,
-} from 'dom7'
-export { Dom7Array } from 'dom7'
-
-if (css) $.fn.css = css
-if (append) $.fn.append = append
-if (prepend) $.fn.prepend = prepend
-if (addClass) $.fn.addClass = addClass
-if (removeClass) $.fn.removeClass = removeClass
-if (hasClass) $.fn.hasClass = hasClass
-if (on) $.fn.on = on
-if (off) $.fn.off = off
-if (focus) $.fn.focus = focus
-if (attr) $.fn.attr = attr
-if (removeAttr) $.fn.removeAttr = removeAttr
-if (hide) $.fn.hide = hide
-if (show) $.fn.show = show
-if (parents) $.fn.parents = parents
-if (dataset) $.fn.dataset = dataset
-if (val) $.fn.val = val
-if (text) $.fn.text = text
-if (html) $.fn.html = html
-if (children) $.fn.children = children
-if (remove) $.fn.remove = remove
-if (find) $.fn.find = find
-if (width) $.fn.width = width
-if (height) $.fn.height = height
-if (filter) $.fn.filter = filter
-if (empty) $.fn.empty = empty
-
-export default $
-
-/**
- * 判断 str 是不是纯字符串,而不是 html tag
- * @param str str
- */
-export function isPlainText(str: string) {
-  const $container = $(`<div>${str}</div>`)
-
-  // 获取 children length (过滤 `<br>`)
-  const childrenLength = $container.children().filter((child: DOMElement) => {
-    if (child.tagName === 'BR') return false
-    return true
-  }).length
-
-  return childrenLength === 0
-}
-
-/**
- * 获取 outerHTML
- * @param $elem dom7 elem
- */
-export function getOuterHTML($elem: Dom7Array) {
-  if ($elem.length === 0) return ''
-  return $elem[0].outerHTML
-}
-
-/**
- * 获取 tagName lower-case
- * @param $elem $elem
- */
-export function getTagName($elem: Dom7Array): string {
-  if ($elem.length) return $elem[0].tagName.toLowerCase()
-  return ''
-}
-
-/**
- * 获取 $elem 某一个 style 值
- * @param $elem $elem
- * @param styleKey style key
- */
-export function getStyleValue($elem: Dom7Array, styleKey: string): string {
-  let res = ''
-
-  const styleStr = $elem.attr('style') || '' // 如 'line-height: 2.5; color: red;'
-  const styleArr = styleStr.split(';') // 如 ['line-height: 2.5', ' color: red', '']
-  const length = styleArr.length
-  for (let i = 0; i < length; i++) {
-    const styleItemStr = styleArr[i] // 如 'line-height: 2.5'
-    if (styleItemStr) {
-      const arr = styleItemStr.split(':') // ['line-height', ' 2.5']
-      if (arr[0].trim() === styleKey) {
-        res = arr[1].trim()
-      }
-    }
-  }
-
-  return res
-}
-
-// COMPAT: This is required to prevent TypeScript aliases from doing some very
-// weird things for Slate's types with the same name as globals. (2019/11/27)
-// https://github.com/microsoft/TypeScript/issues/35002
-import DOMNode = globalThis.Node
-import DOMComment = globalThis.Comment
-import DOMElement = globalThis.Element
-import DOMText = globalThis.Text
-import DOMRange = globalThis.Range
-import DOMSelection = globalThis.Selection
-import DOMStaticRange = globalThis.StaticRange
-export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange }
diff --git a/packages/basic-modules/src/utils/util.ts b/packages/basic-modules/src/utils/util.ts
deleted file mode 100644
index 298b3eadc..000000000
--- a/packages/basic-modules/src/utils/util.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @description 工具函数
- * @author wangfupeng
- */
-
-import { nanoid } from 'nanoid'
-
-/**
- * 获取随机数字符串
- * @param prefix 前缀
- * @returns 随机数字符串
- */
-export function genRandomStr(prefix: string = 'r'): string {
-  return `${prefix}-${nanoid()}`
-}
-
-export function replaceSymbols(str: string) {
-  return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
-}
diff --git a/packages/basic-modules/src/utils/vdom.ts b/packages/basic-modules/src/utils/vdom.ts
deleted file mode 100644
index 26808c117..000000000
--- a/packages/basic-modules/src/utils/vdom.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @description vdom utils fn
- * @author wangfupeng
- */
-
-import { VNode, VNodeStyle, Dataset } from 'snabbdom'
-
-// /**
-//  * 给 vnode 添加 dataset
-//  * @param vnode vnode
-//  * @param newDataset { key: val }
-//  */
-// export function addVnodeDataset(vnode: VNode, newDataset: Dataset) {
-//   if (vnode.data == null) vnode.data = {}
-//   const data = vnode.data
-//   if (data.dataset == null) data.dataset = {}
-
-//   Object.assign(data.dataset, newDataset)
-// }
-
-/**
- * 给 vnode 添加样式
- * @param vnode vnode
- * @param newStyle { key: val }
- */
-export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) {
-  if (vnode.data == null) vnode.data = {}
-  const data = vnode.data
-  if (data.style == null) data.style = {}
-
-  Object.assign(data.style, newStyle)
-}
diff --git a/packages/basic-modules/tsconfig.json b/packages/basic-modules/tsconfig.json
deleted file mode 100644
index 9bef938c9..000000000
--- a/packages/basic-modules/tsconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "compilerOptions": {},
-  "extends": "../../tsconfig.json",
-  "include": [
-    "./src/**/*",
-    "../custom-types.d.ts"
-  ]
-}
\ No newline at end of file
diff --git a/packages/code-highlight/CHANGELOG.md b/packages/code-highlight/CHANGELOG.md
deleted file mode 100644
index 45b59d775..000000000
--- a/packages/code-highlight/CHANGELOG.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# Change Log
-
-All notable changes to this project will be documented in this file.
-See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [1.0.3](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/code-highlight@1.0.2...@wangeditor/code-highlight@1.0.3) (2022-09-14)
-
-
-### Bug Fixes
-
-* 代码块 - 增加 lua groovy  语言 ([ef4f62a](https://github.com/wangeditor-team/wangEditor/commit/ef4f62a876e95995f7c8f6f41d8d44b2505dd5f6))
-
-
-
-
-
-## [1.0.2](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/code-highlight@1.0.1...@wangeditor/code-highlight@1.0.2) (2022-06-02)
-
-
-### Bug Fixes
-
-* issue 4308 - 自定义字号、字体无法回显 ([ad38b8c](https://github.com/wangeditor-team/wangEditor/commit/ad38b8ce6dbcff1d65785c8d6701238ad351f562))
-
-
-
-
-
-## 1.0.1 (2022-04-18)
-
-
-### Bug Fixes
-
-* 更新各包之间依赖版本 ([75c552c](https://github.com/wangeditor-team/wangEditor/commit/75c552cc8ed54765bebb86a7ec5329a7fc79e85f))
-* 修复 pnpm 安装 @wangeditor/editor 出现警告的问题 ([4087fbe](https://github.com/wangeditor-team/wangEditor/commit/4087fbee01c76bdd55e747a5e86c5e4a8d6a8353))
-* 移除了每个包下的 publishConfig directory 配置 ([16559f0](https://github.com/wangeditor-team/wangEditor/commit/16559f052545c111318be760e64291a521bdcc65))
-* 粘贴 <code> 代码块出错 ([fc44d9f](https://github.com/wangeditor-team/wangEditor/commit/fc44d9ff36cb9566d9dc5490b4be14f2e5bd3f3c))
-* rename es module filename ([1821d4e](https://github.com/wangeditor-team/wangEditor/commit/1821d4eef49e64efcb41b848849ca7a5e6472044))
-* table - elemToHtml ([e36e609](https://github.com/wangeditor-team/wangEditor/commit/e36e6092ef721723169afc8bf0560a47ac9f4dfc))
-
-
-### Features
-
-* code highlight ([42b2f8d](https://github.com/wangeditor-team/wangEditor/commit/42b2f8d192e2433593c11ad0b8424737f6cffb58))
-* i18n ([c11b244](https://github.com/wangeditor-team/wangEditor/commit/c11b2440f91b99d40bca18b675c66a22b6e160c9))
-* parse html ([2a5eace](https://github.com/wangeditor-team/wangEditor/commit/2a5eace00f33cded50b68e8164748ec2480213fd))
-* toHtml 机制 ([1c4d872](https://github.com/wangeditor-team/wangEditor/commit/1c4d8729f84aaab6a448f23064b34a20596305e9))
-* upload video ([ac8e6f8](https://github.com/wangeditor-team/wangEditor/commit/ac8e6f8b5258e593714676a6f6be359ba525833c))
diff --git a/packages/code-highlight/README.md b/packages/code-highlight/README.md
deleted file mode 100644
index 698df5d13..000000000
--- a/packages/code-highlight/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# wangEditor code highlight
-
-Code highlight module built in [wangEditor](https://www.wangeditor.com/) by default.
diff --git a/packages/code-highlight/__tests__/content.ts b/packages/code-highlight/__tests__/content.ts
deleted file mode 100644
index bbd48d922..000000000
--- a/packages/code-highlight/__tests__/content.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * @description code content
- * @author wangfupeng
- */
-
-export const text = 'const a = 100;'
-
-export const textNode = { text: text }
-
-export const language = 'javascript'
-
-export const codeNode = {
-  type: 'code',
-  language,
-  children: [textNode],
-}
-
-export const preNode = {
-  type: 'pre',
-  children: [codeNode],
-}
-
-export const content = [{ type: 'paragraph', children: [{ text: 'hello world' }] }, preNode]
-
-export const textNodePath = [1, 0, 0]
-
-export const codeLocation = {
-  anchor: { offset: text.length, path: textNodePath },
-  focus: { offset: text.length, path: textNodePath },
-}
-
-export const paragraphLocation = {
-  anchor: { offset: 0, path: [0, 0] },
-  focus: { offset: 0, path: [0, 0] },
-}
-
-describe('加一个 case 防止报错~', () => {
-  it('1 + 1 = 2', () => {
-    expect(1 + 1).toBe(2)
-  })
-})
diff --git a/packages/code-highlight/__tests__/decorate.test.ts b/packages/code-highlight/__tests__/decorate.test.ts
deleted file mode 100644
index df4c2a53b..000000000
--- a/packages/code-highlight/__tests__/decorate.test.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @description code-highlight decorate test
- * @author wangfupeng
- */
-
-import { IDomEditor } from '@wangeditor/core'
-import createEditor from '../../../tests/utils/create-editor'
-import codeHighLightDecorate from '../src/decorate/index'
-import { content, textNode, textNodePath } from './content'
-
-describe('code-highlight decorate', () => {
-  let editor: IDomEditor | null = null
-
-  beforeAll(() => {
-    // 把 content 创建到一个编辑器中
-    editor = createEditor({
-      content,
-    })
-  })
-
-  afterAll(() => {
-    // 销毁 editor
-    if (editor == null) return
-    editor.destroy()
-    editor = null
-  })
-
-  it('code-highlight decorate 拆分代码字符串', () => {
-    const ranges = codeHighLightDecorate([textNode, textNodePath])
-    expect(ranges.length).toBe(4) // 把 textNode 内容拆分为 4 段
-  })
-})
diff --git a/packages/code-highlight/__tests__/elem-to-html.test.ts b/packages/code-highlight/__tests__/elem-to-html.test.ts
deleted file mode 100644
index aa5e41cae..000000000
--- a/packages/code-highlight/__tests__/elem-to-html.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @description code-hight elem-to-html
- * @author wangfupeng
- */
-
-import { IDomEditor } from '@wangeditor/core'
-import createEditor from '../../../tests/utils/create-editor'
-import { codeToHtmlConf } from '../src/module/elem-to-html'
-import { content, codeNode, language } from './content'
-
-describe('code-highlight elem to html', () => {
-  let editor: IDomEditor | null = null
-
-  beforeAll(() => {
-    // 把 content 创建到一个编辑器中
-    editor = createEditor({
-      content,
-    })
-  })
-
-  afterAll(() => {
-    // 销毁 editor
-    if (editor == null) return
-    editor.destroy()
-    editor = null
-  })
-
-  it('codeNode to html', () => {
-    expect(codeToHtmlConf.type).toBe('code')
-
-    if (editor == null) throw new Error('editor is null')
-    const text = 'var n = 100;'
-    const html = codeToHtmlConf.elemToHtml(codeNode, text)
-    expect(html).toBe(`<code class="language-${language}">${text}</code>`)
-  })
-})
diff --git a/packages/code-highlight/__tests__/parse-html.test.ts b/packages/code-highlight/__tests__/parse-html.test.ts
deleted file mode 100644
index 32af09b8e..000000000
--- a/packages/code-highlight/__tests__/parse-html.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @description parse html test
- * @author wangfupeng
- */
-
-import { $ } from 'dom7'
-import { parseCodeStyleHtml } from '../src/module/parse-style-html'
-import createEditor from '../../../tests/utils/create-editor'
-
-describe('code highlight - parse style html', () => {
-  const editor = createEditor()
-
-  it('v5 format', () => {
-    const $code = $('<code class="language-javascript"></code>') // v5 html format
-    const code = { type: 'code', children: [{ text: 'var a = 100;' }] }
-
-    const res = parseCodeStyleHtml($code[0], code, editor)
-    expect(res).toEqual({
-      type: 'code',
-      language: 'javascript',
-      children: [{ text: 'var a = 100;' }],
-    })
-  })
-
-  it('v4 format', () => {
-    const $code = $('<code class="Javascript"></code>') // v4 html format
-    const code = { type: 'code', children: [{ text: 'var a = 100;' }] }
-
-    const res = parseCodeStyleHtml($code[0], code, editor)
-    expect(res).toEqual({
-      type: 'code',
-      language: 'javascript',
-      children: [{ text: 'var a = 100;' }],
-    })
-  })
-})
diff --git a/packages/code-highlight/__tests__/render-text-style.test.tsx b/packages/code-highlight/__tests__/render-text-style.test.tsx
deleted file mode 100644
index b3b1efa4c..000000000
--- a/packages/code-highlight/__tests__/render-text-style.test.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @description code-highlight render text style test
- * @author wangfupeng
- */
-
-import { renderStyle } from '../src/module/render-style'
-import { jsx } from 'snabbdom'
-
-describe('code-highlight render text style', () => {
-  it('code text style', () => {
-    const leafNode = { text: 'let', keyword: true } // 定义一个 keyword leaf text node
-    const vnode = <span>let</span>
-
-    // @ts-ignore 忽略 vnode 格式检查
-    const newVnode = renderStyle(leafNode, vnode)
-    expect(newVnode.data?.props?.className).toBe('token keyword')
-  })
-})
diff --git a/packages/code-highlight/__tests__/select-lang-menu.test.ts b/packages/code-highlight/__tests__/select-lang-menu.test.ts
deleted file mode 100644
index bb0142649..000000000
--- a/packages/code-highlight/__tests__/select-lang-menu.test.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * @description code-highlight select lang menu test
- * @author wangfupeng
- */
-
-import { IDomEditor } from '@wangeditor/core'
-import createEditor from '../../../tests/utils/create-editor'
-import { content, codeLocation, paragraphLocation, language } from './content'
-import SelectLangMenu from '../src/module/menu/SelectLangMenu'
-
-describe('code-highlight select lang menu', () => {
-  let editor: IDomEditor | null = null
-  let menu: SelectLangMenu | null = null
-
-  beforeAll(() => {
-    // 创建 editor
-    editor = createEditor({
-      content,
-    })
-
-    // 创建 menu
-    menu = new SelectLangMenu()
-  })
-
-  afterAll(() => {
-    // 销毁 editor
-    if (editor == null) return
-    editor.destroy()
-    editor = null
-
-    // 销毁 menu
-    menu = null
-  })
-
-  it('get langs and selected one', () => {
-    if (editor == null || menu == null) throw new Error('editor or menu is null')
-
-    // select codeNode
-    editor.select(codeLocation)
-
-    const langs = menu.getOptions(editor)
-
-    // 包括多个 lang
-    expect(langs.length).toBeGreaterThan(0)
-
-    // 其中有一个 'plain text'
-    const hasPlainText = langs.some(lang => lang.text === 'plain text' && lang.value === '')
-    expect(hasPlainText).toBeTruthy()
-
-    // 选中的语言
-    const selectedLangs = langs.filter(lang => lang.selected)
-    expect(selectedLangs.length).toBe(1)
-    const selectedLang: any = selectedLangs[0] || {}
-    expect(selectedLang.value).toBe(language)
-  })
-
-  it('menu active is always false', () => {
-    if (editor == null || menu == null) throw new Error('editor or menu is null')
-
-    expect(menu.isActive(editor)).toBeFalsy()
-  })
-
-  it('get menu value (selected lang)', () => {
-    if (editor == null || menu == null) throw new Error('editor or menu is null')
-
-    // select codeNode
-    editor.select(codeLocation)
-    expect(menu.getValue(editor)).toBe(language)
-
-    // select paragraph
-    editor.select(paragraphLocation)
-    expect(menu.getValue(editor)).toBe('')
-  })
-
-  it('menu disable', () => {
-    if (editor == null || menu == null) throw new Error('editor or menu is null')
-
-    // deselect
-    editor.deselect()
-    expect(menu.isDisabled(editor)).toBeTruthy()
-
-    // select paragraph
-    editor.select(paragraphLocation)
-    expect(menu.isDisabled(editor)).toBeTruthy()
-
-    // select codeNode
-    editor.select(codeLocation)
-    expect(menu.isDisabled(editor)).toBeFalsy()
-  })
-
-  it('menu exec (change lang)', done => {
-    if (editor == null || menu == null) throw new Error('editor or menu is null')
-
-    // select codeNode
-    editor.select(codeLocation)
-    menu.exec(editor, 'html') // change lang
-
-    setTimeout(() => {
-      if (editor == null || menu == null) return
-
-      editor.select(codeLocation)
-      expect(menu.getValue(editor)).toBe('html')
-      done()
-    })
-  })
-})
diff --git a/packages/code-highlight/package.json b/packages/code-highlight/package.json
deleted file mode 100644
index ac3798a6f..000000000
--- a/packages/code-highlight/package.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "name": "@wangeditor/code-highlight",
-  "version": "1.0.3",
-  "description": "wangEditor code-highlight module",
-  "author": "wangfupeng1988 <wangfupeng1988@163.com>",
-  "contributors": [],
-  "homepage": "https://github.com/wangeditor-team/wangEditor#readme",
-  "license": "MIT",
-  "types": "dist/code-highlight/src/index.d.ts",
-  "main": "dist/index.js",
-  "module": "dist/index.esm.js",
-  "browser": {
-    "./dist/index.js": "./dist/index.js",
-    "./dist/index.esm.js": "./dist/index.esm.js"
-  },
-  "directories": {
-    "lib": "dist",
-    "test": "__tests__"
-  },
-  "files": [
-    "dist"
-  ],
-  "publishConfig": {
-    "access": "public",
-    "registry": "https://registry.npmjs.com/"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/wangeditor-team/wangEditor.git"
-  },
-  "scripts": {
-    "test": "jest",
-    "test-c": "jest --coverage",
-    "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js",
-    "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w",
-    "build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
-    "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js",
-    "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js"
-  },
-  "bugs": {
-    "url": "https://github.com/wangeditor-team/wangEditor/issues"
-  },
-  "peerDependencies": {
-    "@wangeditor/core": "1.x",
-    "dom7": "^3.0.0",
-    "slate": "^0.72.0",
-    "snabbdom": "^3.1.0"
-  },
-  "dependencies": {
-    "prismjs": "^1.23.0"
-  },
-  "devDependencies": {
-    "@types/prismjs": "^1.16.5"
-  }
-}
diff --git a/packages/code-highlight/rollup.config.js b/packages/code-highlight/rollup.config.js
deleted file mode 100644
index 636793c53..000000000
--- a/packages/code-highlight/rollup.config.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
-import pkg from './package.json'
-
-const name = 'WangEditorCodeHighLight'
-
-const configList = []
-
-// esm
-const esmConf = createRollupConfig({
-  output: {
-    file: pkg.module,
-    format: 'esm',
-    name,
-  },
-})
-configList.push(esmConf)
-
-// umd
-const umdConf = createRollupConfig({
-  output: {
-    file: pkg.main,
-    format: 'umd',
-    name,
-  },
-})
-configList.push(umdConf)
-
-export default configList
diff --git a/packages/code-highlight/src/assets/index.less b/packages/code-highlight/src/assets/index.less
deleted file mode 100644
index 0b076ec6f..000000000
--- a/packages/code-highlight/src/assets/index.less
+++ /dev/null
@@ -1,90 +0,0 @@
-// 样式参考 https://github.com/PrismJS/prism/blob/master/themes/prism.css
-// TODO 开发 themes 主题,可以参考 prismjs 主题 https://github.com/PrismJS/prism/tree/master/themes
-
-.w-e-text-container [data-slate-editor] pre>code {
-	text-shadow: 0 1px white;
-	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
-	text-align: left;
-	white-space: pre;
-	word-spacing: normal;
-	word-break: normal;
-	word-wrap: normal;
-	line-height: 1.5;
-	tab-size: 4;
-	hyphens: none;
-
-  padding: 1em;
-	margin: .5em 0;
-	overflow: auto;
-
-  .token.comment,
-  .token.prolog,
-  .token.doctype,
-  .token.cdata {
-    color: slategray;
-  }
-
-  .token.punctuation {
-    color: #999;
-  }
-
-  .token.namespace {
-    opacity: .7;
-  }
-
-  .token.property,
-  .token.tag,
-  .token.boolean,
-  .token.number,
-  .token.constant,
-  .token.symbol,
-  .token.deleted {
-    color: #905;
-  }
-
-  .token.selector,
-  .token.attr-name,
-  .token.string,
-  .token.char,
-  .token.builtin,
-  .token.inserted {
-    color: #690;
-  }
-
-  .token.operator,
-  .token.entity,
-  .token.url,
-  .language-css .token.string,
-  .style .token.string {
-    color: #9a6e3a;
-  }
-
-  .token.atrule,
-  .token.attr-value,
-  .token.keyword {
-    color: #07a;
-  }
-
-  .token.function,
-  .token.class-name {
-    color: #DD4A68;
-  }
-
-  .token.regex,
-  .token.important,
-  .token.variable {
-    color: #e90;
-  }
-
-  .token.important,
-  .token.bold {
-    font-weight: bold;
-  }
-  .token.italic {
-    font-style: italic;
-  }
-
-  .token.entity {
-    cursor: help;
-  }
-}
diff --git a/packages/code-highlight/src/constants/svg.ts b/packages/code-highlight/src/constants/svg.ts
deleted file mode 100644
index 31b8b7132..000000000
--- a/packages/code-highlight/src/constants/svg.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @description icon svg
- * @author wangfupeng
- */
-
-/**
- * 【注意】svg 字符串的长度 ,否则会导致代码体积过大
- * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293
- * 找不到再从 iconfont.com 搜索
- */
-
-export const JS_SVG =
-  '<svg viewBox="0 0 1024 1024"><path d="M64 64v896h896V64H64z m487.6 698.8c0 87.2-51.2 127-125.8 127-67.4 0-106.4-34.8-126.4-77l68.6-41.4c13.2 23.4 25.2 43.2 54.2 43.2 27.6 0 45.2-10.8 45.2-53V475.4h84.2v287.4z m199.2 127c-78.2 0-128.8-37.2-153.4-86l68.6-39.6c18 29.4 41.6 51.2 83 51.2 34.8 0 57.2-17.4 57.2-41.6 0-28.8-22.8-39-61.4-56l-21-9c-60.8-25.8-101-58.4-101-127 0-63.2 48.2-111.2 123.2-111.2 53.6 0 92 18.6 119.6 67.4L800 580c-14.4-25.8-30-36-54.2-36-24.6 0-40.2 15.6-40.2 36 0 25.2 15.6 35.4 51.8 51.2l21 9c71.6 30.6 111.8 62 111.8 132.4 0 75.6-59.6 117.2-139.4 117.2z"></path></svg>'
diff --git a/packages/code-highlight/src/custom-types.ts b/packages/code-highlight/src/custom-types.ts
deleted file mode 100644
index 1137c9ddd..000000000
--- a/packages/code-highlight/src/custom-types.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description 自定义 element
- * @author wangfupeng
- */
-
-// 拷贝自 basic-modules/src/modules/code-block/custom-types.ts
-
-type PureText = {
-  text: string
-}
-
-export type PreElement = {
-  type: 'pre'
-  children: CodeElement[]
-}
-
-export type CodeElement = {
-  type: 'code'
-  language: string
-  children: PureText[]
-}
diff --git a/packages/code-highlight/src/decorate/index.ts b/packages/code-highlight/src/decorate/index.ts
deleted file mode 100644
index bc2b6bd53..000000000
--- a/packages/code-highlight/src/decorate/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @description code-highlight decorate
- * @author wangfupeng
- */
-
-import { Node, NodeEntry, Range, Text } from 'slate'
-import { DomEditor } from '@wangeditor/core'
-import { getPrismTokens, getPrismTokenLength } from '../vendor/prism'
-import { CodeElement } from '../custom-types'
-
-/**
- * 获取 code elem
- * @param node text node
- */
-function getCodeElem(textNode: Node): CodeElement | null {
-  if (!Text.isText(textNode)) return null // 非文本 node
-
-  const codeNode = DomEditor.getParentNode(null, textNode)
-  if (codeNode && DomEditor.getNodeType(codeNode) === 'code') {
-    const preNode = DomEditor.getParentNode(null, codeNode)
-    if (preNode && DomEditor.getNodeType(preNode) === 'pre') {
-      return codeNode as CodeElement
-    }
-  }
-  return null
-}
-
-const codeHighLightDecorate = (nodeEntry: NodeEntry): Range[] => {
-  const [n, path] = nodeEntry
-  const ranges: Range[] = []
-
-  // 节点不合法,则不处理
-  const codeElem = getCodeElem(n)
-  if (codeElem == null) return ranges
-  const { language = '' } = codeElem
-  if (!language) return ranges
-
-  const textNode = n as Text
-  const tokens = getPrismTokens(textNode, language)
-
-  let start = 0
-  for (const token of tokens) {
-    const length = getPrismTokenLength(token)
-    const end = start + length
-
-    if (typeof token !== 'string') {
-      // 遇到关键字,则拆分多个 range —— decorate 规则
-      ranges.push({
-        [token.type]: true, // 记录类型,以便 css 使用不同的颜色
-        anchor: { path, offset: start },
-        focus: { path, offset: end },
-      })
-    }
-
-    start = end
-  }
-
-  return ranges
-}
-
-export default codeHighLightDecorate
diff --git a/packages/code-highlight/src/index.ts b/packages/code-highlight/src/index.ts
deleted file mode 100644
index b2f121fcc..000000000
--- a/packages/code-highlight/src/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * @description code-highlight
- * @author wangfupeng
- */
-
-import './assets/index.less'
-
-// 配置多语言
-import './locale/index'
-
-import wangEditorCodeHighlightModule from './module/index'
-import wangEditorCodeHighLightDecorate from './decorate'
-
-export { wangEditorCodeHighlightModule, wangEditorCodeHighLightDecorate }
diff --git a/packages/code-highlight/src/locale/en.ts b/packages/code-highlight/src/locale/en.ts
deleted file mode 100644
index d79d010bd..000000000
--- a/packages/code-highlight/src/locale/en.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @description i18n en
- * @author wangfupeng
- */
-
-export default {
-  highLightModule: {
-    selectLang: 'Language',
-  },
-}
diff --git a/packages/code-highlight/src/locale/index.ts b/packages/code-highlight/src/locale/index.ts
deleted file mode 100644
index 22f6ec6cb..000000000
--- a/packages/code-highlight/src/locale/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @description i18n entry
- * @author wangfupeng
- */
-
-import { i18nAddResources } from '@wangeditor/core'
-import enResources from './en'
-import zhResources from './zh-CN'
-
-i18nAddResources('en', enResources)
-i18nAddResources('zh-CN', zhResources)
diff --git a/packages/code-highlight/src/locale/zh-CN.ts b/packages/code-highlight/src/locale/zh-CN.ts
deleted file mode 100644
index 2da1d89ca..000000000
--- a/packages/code-highlight/src/locale/zh-CN.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @description i18n zh-CN
- * @author wangfupeng
- */
-
-export default {
-  highLightModule: {
-    selectLang: '选择语言',
-  },
-}
diff --git a/packages/code-highlight/src/module/elem-to-html.ts b/packages/code-highlight/src/module/elem-to-html.ts
deleted file mode 100644
index 0b88c88fd..000000000
--- a/packages/code-highlight/src/module/elem-to-html.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @description to html
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-import { CodeElement } from '../custom-types'
-
-function codeToHtml(elem: Element, childrenHtml: string): string {
-  const { language = '' } = elem as CodeElement
-
-  const cssClass = language
-    ? `class="language-${language}"` // prism.js 根据 language 代码高亮
-    : ''
-
-  return `<code ${cssClass}>${childrenHtml}</code>`
-}
-
-// 覆盖 basic-module 中的 code to html
-export const codeToHtmlConf = {
-  type: 'code',
-  elemToHtml: codeToHtml,
-}
diff --git a/packages/code-highlight/src/module/index.ts b/packages/code-highlight/src/module/index.ts
deleted file mode 100644
index 0e314b56f..000000000
--- a/packages/code-highlight/src/module/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @description code highlight module
- * @author wangfupeng
- */
-
-import { IModuleConf } from '@wangeditor/core'
-import { renderStyle } from './render-style'
-import { parseCodeStyleHtml } from './parse-style-html'
-import { selectLangMenuConf } from './menu/index'
-import { codeToHtmlConf } from './elem-to-html'
-
-const codeHighlightModule: Partial<IModuleConf> = {
-  renderStyle,
-  parseStyleHtml: parseCodeStyleHtml,
-  menus: [selectLangMenuConf],
-  elemsToHtml: [codeToHtmlConf],
-}
-
-export default codeHighlightModule
diff --git a/packages/code-highlight/src/module/menu/SelectLangMenu.ts b/packages/code-highlight/src/module/menu/SelectLangMenu.ts
deleted file mode 100644
index 4ae55b740..000000000
--- a/packages/code-highlight/src/module/menu/SelectLangMenu.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * @description code-highlight select lang
- * @author wangfupeng
- */
-
-import { Transforms, Element } from 'slate'
-import { ISelectMenu, IDomEditor, IOption, DomEditor, t } from '@wangeditor/core'
-import { JS_SVG } from '../../constants/svg'
-import { CodeElement } from '../../custom-types'
-
-class SelectLangMenu implements ISelectMenu {
-  readonly title = t('highLightModule.selectLang')
-  readonly iconSvg = JS_SVG
-  readonly tag = 'select'
-  readonly width = 95
-  readonly selectPanelWidth = 115
-
-  getOptions(editor: IDomEditor): IOption[] {
-    const options: IOption[] = []
-
-    // 获取配置,参考 './config.ts'
-    const { codeLangs = [] } = editor.getMenuConfig('codeSelectLang') // 第二个参数 menu key
-
-    options.push({
-      text: 'plain text',
-      value: '', // getValue 默认会返回 ''
-    })
-    codeLangs.forEach((lang: { text: string; value: string }) => {
-      const { text, value } = lang
-      options.push({ text, value })
-    })
-
-    // 设置 selected
-    const curValue = this.getValue(editor)
-    options.forEach(opt => {
-      if (opt.value === curValue) {
-        opt.selected = true
-      } else {
-        delete opt.selected
-      }
-    })
-
-    return options
-  }
-
-  isActive(editor: IDomEditor): boolean {
-    // select menu 会显示 selected value ,用不到 active
-    return false
-  }
-
-  /**
-   * 获取语言类型
-   * @param editor editor
-   */
-  getValue(editor: IDomEditor): string | boolean {
-    const elem = this.getSelectCodeElem(editor)
-    if (elem == null) return ''
-    if (!Element.isElement(elem)) return ''
-
-    const lang = elem.language.toString()
-
-    // 当前 elem.language 是否在已配置的 langs 中?
-    const { codeLangs = [] } = editor.getMenuConfig('codeSelectLang')
-    const hasLang = codeLangs.some(item => item.value === lang)
-
-    if (hasLang) return lang
-    return ''
-  }
-
-  isDisabled(editor: IDomEditor): boolean {
-    if (editor.selection == null) return true
-    const elem = this.getSelectCodeElem(editor)
-    if (elem) return false
-    return true
-  }
-
-  exec(editor: IDomEditor, value: string | boolean) {
-    const elem = this.getSelectCodeElem(editor)
-    if (elem == null) return
-
-    // 设置语言
-    const props: Partial<CodeElement> = { language: value.toString() }
-    Transforms.setNodes(editor, props, {
-      match: n => DomEditor.checkNodeType(n, 'code'),
-    })
-  }
-
-  private getSelectCodeElem(editor: IDomEditor): CodeElement | null {
-    const codeNode = DomEditor.getSelectedNodeByType(editor, 'code')
-    if (codeNode == null) return null
-    const preNode = DomEditor.getParentNode(editor, codeNode)
-    if (!Element.isElement(preNode)) return null
-    if (preNode.type !== 'pre') return null
-
-    return codeNode as CodeElement
-  }
-}
-
-export default SelectLangMenu
diff --git a/packages/code-highlight/src/module/menu/config.ts b/packages/code-highlight/src/module/menu/config.ts
deleted file mode 100644
index 81aa51ed5..000000000
--- a/packages/code-highlight/src/module/menu/config.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @description menu config
- * @author wangfupeng
- */
-
-export function genCodeLangs() {
-  // 1. text value 对应关系参考 prism 官网 https://prismjs.com/#supported-languages
-  // 2. 要加入一个新语言时,要引入相应的 js 模块(代码在 `vender/prism.ts`),例如 `import 'prismjs/components/prism-php'`
-
-  return [
-    { text: 'CSS', value: 'css' },
-    { text: 'HTML', value: 'html' },
-    { text: 'XML', value: 'xml' },
-    { text: 'Javascript', value: 'javascript' },
-    { text: 'Typescript', value: 'typescript' },
-    { text: 'JSX', value: 'jsx' },
-    { text: 'Go', value: 'go' },
-    { text: 'PHP', value: 'php' },
-    { text: 'C', value: 'c' },
-    { text: 'Python', value: 'python' },
-    { text: 'Java', value: 'java' },
-    { text: 'C++', value: 'cpp' },
-    { text: 'C#', value: 'csharp' },
-    { text: 'Visual Basic', value: 'visual-basic' },
-    { text: 'SQL', value: 'sql' },
-    { text: 'Ruby', value: 'ruby' },
-    { text: 'Swift', value: 'swift' },
-    { text: 'Bash', value: 'bash' },
-    { text: 'Lua', value: 'lua' },
-    { text: 'Groovy', value: 'groovy' },
-    { text: 'Markdown', value: 'markdown' },
-  ]
-}
diff --git a/packages/code-highlight/src/module/menu/index.ts b/packages/code-highlight/src/module/menu/index.ts
deleted file mode 100644
index 9043602b3..000000000
--- a/packages/code-highlight/src/module/menu/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @description code-highlight menu
- * @author wangfupeng
- */
-
-import SelectLangMenu from './SelectLangMenu'
-import { genCodeLangs } from './config'
-
-export const selectLangMenuConf = {
-  key: 'codeSelectLang',
-  factory() {
-    return new SelectLangMenu()
-  },
-  config: {
-    codeLangs: genCodeLangs(),
-  },
-}
diff --git a/packages/code-highlight/src/module/parse-style-html.ts b/packages/code-highlight/src/module/parse-style-html.ts
deleted file mode 100644
index 1d9791b43..000000000
--- a/packages/code-highlight/src/module/parse-style-html.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @description parse style html
- * @author wangfupeng
- */
-
-import $, { DOMElement } from '../utils/dom'
-import { Descendant, Element } from 'slate'
-import { DomEditor, IDomEditor } from '@wangeditor/core'
-import { CodeElement } from '../custom-types'
-
-export function parseCodeStyleHtml(
-  elem: DOMElement,
-  node: Descendant,
-  editor: IDomEditor
-): Descendant {
-  const $elem = $(elem)
-
-  if (!Element.isElement(node)) return node
-  if (DomEditor.getNodeType(node) !== 'code') return node // 只针对 pre/code 元素
-
-  const elemNode = node as CodeElement
-
-  const langAttr = $elem.attr('class') || ''
-  if (langAttr.indexOf('language-') === 0) {
-    // V5 版本,格式如 class="language-javascript"
-    elemNode.language = langAttr.split('-')[1] || '' // 获取 'javascript'
-  } else {
-    // 兼容 V4 版本,格式如 class="Javascript"
-    elemNode.language = langAttr.toLowerCase()
-  }
-
-  return elemNode
-}
diff --git a/packages/code-highlight/src/module/render-style.tsx b/packages/code-highlight/src/module/render-style.tsx
deleted file mode 100644
index 102d998da..000000000
--- a/packages/code-highlight/src/module/render-style.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @description render code highlight style
- * @author wangfupeng
- */
-
-import { Text as SlateText, Descendant } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { addVnodeClassName } from '../utils/vdom'
-import { prismTokenTypes } from '../vendor/prism'
-
-/**
- * 添加样式
- * @param node slate text
- * @param vnode vnode
- * @returns vnode
- */
-export function renderStyle(node: Descendant, vnode: VNode): VNode {
-  const leafNode = node as SlateText & { [key: string]: string }
-  let styleVnode: VNode = vnode
-
-  let className = ''
-  prismTokenTypes.forEach(type => {
-    if (leafNode[type]) className = type
-  })
-
-  if (className) {
-    className = `token ${className}` // 如 'token keyword' - prismjs 渲染的规则
-    addVnodeClassName(styleVnode, className)
-  }
-
-  return styleVnode
-}
diff --git a/packages/code-highlight/src/utils/dom.ts b/packages/code-highlight/src/utils/dom.ts
deleted file mode 100644
index 103f36c39..000000000
--- a/packages/code-highlight/src/utils/dom.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @description DOM 操作
- * @author wangfupeng
- */
-
-import $, { attr } from 'dom7'
-
-if (attr) $.fn.attr = attr
-
-export { Dom7Array } from 'dom7'
-
-export default $
-
-// COMPAT: This is required to prevent TypeScript aliases from doing some very
-// weird things for Slate's types with the same name as globals. (2019/11/27)
-// https://github.com/microsoft/TypeScript/issues/35002
-import DOMNode = globalThis.Node
-import DOMComment = globalThis.Comment
-import DOMElement = globalThis.Element
-import DOMText = globalThis.Text
-import DOMRange = globalThis.Range
-import DOMSelection = globalThis.Selection
-import DOMStaticRange = globalThis.StaticRange
-export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange }
diff --git a/packages/code-highlight/src/utils/vdom.ts b/packages/code-highlight/src/utils/vdom.ts
deleted file mode 100644
index 3a6cecad0..000000000
--- a/packages/code-highlight/src/utils/vdom.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @description vdom utils fn
- * @author wangfupeng
- */
-
-import { VNode, VNodeStyle } from 'snabbdom'
-
-/**
- * 给 vnode 添加 className
- * @param vnode vnode
- * @param className css class
- */
-export function addVnodeClassName(vnode: VNode, className: string) {
-  if (vnode.data == null) vnode.data = {}
-  const data = vnode.data
-  if (data.props == null) data.props = {}
-
-  Object.assign(data.props, { className })
-}
-
-/**
- * 给 vnode 添加样式
- * @param vnode vnode
- * @param newStyle { key: val }
- */
-export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) {
-  if (vnode.data == null) vnode.data = {}
-  const data = vnode.data
-  if (data.style == null) data.style = {}
-
-  Object.assign(data.style, newStyle)
-}
diff --git a/packages/code-highlight/src/vendor/prism.ts b/packages/code-highlight/src/vendor/prism.ts
deleted file mode 100644
index ffaed3de1..000000000
--- a/packages/code-highlight/src/vendor/prism.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * @description prismjs
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-
-import Prism from 'prismjs'
-import 'prismjs/components/prism-jsx'
-import 'prismjs/components/prism-typescript'
-import 'prismjs/components/prism-markup'
-import 'prismjs/components/prism-go'
-import 'prismjs/components/prism-php'
-import 'prismjs/components/prism-c'
-import 'prismjs/components/prism-python'
-import 'prismjs/components/prism-java'
-import 'prismjs/components/prism-cpp'
-import 'prismjs/components/prism-csharp'
-import 'prismjs/components/prism-visual-basic'
-import 'prismjs/components/prism-sql'
-import 'prismjs/components/prism-ruby'
-import 'prismjs/components/prism-swift'
-import 'prismjs/components/prism-bash'
-import 'prismjs/components/prism-markdown'
-import 'prismjs/components/prism-lua'
-import 'prismjs/components/prism-groovy'
-// 语言模块,参考 https://github.com/PrismJS/prism/tree/master/components
-
-// prismjs 的 token 类型汇总
-export const prismTokenTypes = [
-  'comment',
-  'prolog',
-  'doctype',
-  'cdata',
-  'punctuation',
-  'namespace',
-  'property',
-  'tag',
-  'boolean',
-  'number',
-  'constant',
-  'symbol',
-  'deleted',
-  'selector',
-  'attr-name',
-  'string',
-  'builtin',
-  'inserted',
-  'operator',
-  'entity',
-  'url',
-  'string',
-  'atrule',
-  'attr-value',
-  'keyword',
-  'function',
-  'class-name',
-  'regex',
-  'important',
-  'variable',
-  'bold',
-  'italic',
-  'entity',
-  'char',
-]
-
-/**
- * 获取 prism token 的字符串长度
- * @param token prism token
- */
-export function getPrismTokenLength(token: any) {
-  if (typeof token === 'string') {
-    return token.length
-  } else if (typeof token.content === 'string') {
-    return token.content.length
-  } else {
-    // 累加 length
-    return token.content.reduce(
-      // @ts-ignore
-      (l, t) => l + getPrismTokenLength(t),
-      0
-    )
-  }
-}
-
-/**
- * 获取 prism 解析的 token 列表
- * @param textNode text node
- * @param language 代码语言
- */
-export function getPrismTokens(textNode: Text, language: string) {
-  if (!language) return []
-
-  const langGrammar = Prism.languages[language]
-  if (!langGrammar) return []
-
-  return Prism.tokenize(textNode.text, langGrammar)
-
-  // tokens 即 Prism 对整个字符串的拆分,有普通文字也有高亮的关键字
-  // 例如 `const a = 100;` 的 tokens 是一个数组 [ token, ' a ', token, ' ', token ] ,有对象有字符串,对象就表示关键字
-  // 如数组第一个 token 是 { type: "keyword", content: "const" } 。关键字类型不同 type 也不同
-}
diff --git a/packages/code-highlight/tsconfig.json b/packages/code-highlight/tsconfig.json
deleted file mode 100644
index 9bef938c9..000000000
--- a/packages/code-highlight/tsconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "compilerOptions": {},
-  "extends": "../../tsconfig.json",
-  "include": [
-    "./src/**/*",
-    "../custom-types.d.ts"
-  ]
-}
\ No newline at end of file
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
deleted file mode 100644
index 59a8b9005..000000000
--- a/packages/core/CHANGELOG.md
+++ /dev/null
@@ -1,404 +0,0 @@
-# Change Log
-
-All notable changes to this project will be documented in this file.
-See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [1.1.19](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.18...@wangeditor/core@1.1.19) (2022-11-14)
-
-
-### Bug Fixes
-
-* **font family menu:** 处理 setHtml 的时候字体样式回显失败的问题 ([b941bab](https://github.com/wangeditor-team/wangEditor/commit/b941babbdc6bd5bf7da0cce826803a8fde011e07))
-* **fontFamily menu:** fix font-family value quote symbol ([2c25231](https://github.com/wangeditor-team/wangEditor/commit/2c25231a088de14edbf7516fc448a6483125e3ed))
-
-
-
-
-
-## [1.1.18](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.17...@wangeditor/core@1.1.18) (2022-10-18)
-
-
-### Bug Fixes
-
-* mousedown事件添加passive的默认值 ([60229cc](https://github.com/wangeditor-team/wangEditor/commit/60229cc2f9647a5f17dc0fd85c4bb1dc396a5e9c))
-* **video menu:** fix invoke clear api can not clear video node when insert video ([68c1f8e](https://github.com/wangeditor-team/wangEditor/commit/68c1f8ee68ab2cb7b202b6d9b4d4db192a927725))
-
-
-
-
-
-## [1.1.17](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.16...@wangeditor/core@1.1.17) (2022-10-04)
-
-
-### Bug Fixes
-
-* 修复 compositionend 时错误修改dom的问题 ([1187154](https://github.com/wangeditor-team/wangEditor/commit/1187154aa077594f55211307c00e3493d1ab5676))
-* 修复设置 maxlength 后粘贴异常的问题 ([14003d0](https://github.com/wangeditor-team/wangEditor/commit/14003d0ba01eeb9a264d15fac514dd4b4bd89ff7))
-
-
-
-
-
-## [1.1.16](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.15...@wangeditor/core@1.1.16) (2022-09-27)
-
-
-### Bug Fixes
-
-* list-item - 遇到 style 是 toHtml 出错 ([9854308](https://github.com/wangeditor-team/wangEditor/commit/98543083a1cb09207aceb2a4d8f3c1ce020b106d))
-
-
-
-
-
-## [1.1.15](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.14...@wangeditor/core@1.1.15) (2022-09-27)
-
-**Note:** Version bump only for package @wangeditor/core
-
-
-
-
-
-## [1.1.14](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.13...@wangeditor/core@1.1.14) (2022-09-16)
-
-**Note:** Version bump only for package @wangeditor/core
-
-
-
-
-
-## [1.1.13](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.12...@wangeditor/core@1.1.13) (2022-09-15)
-
-
-### Bug Fixes
-
-* focus table 时 isFocused 异常 ([5c52bf3](https://github.com/wangeditor-team/wangEditor/commit/5c52bf33e91b1a4677e7bbc04c5d80698abfeeab))
-* snabbdom 增加 attributesModule ([2c597b6](https://github.com/wangeditor-team/wangEditor/commit/2c597b6a52ffa96c820128d63fd84b903a6faebf))
-
-
-
-
-
-## [1.1.12](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.11...@wangeditor/core@1.1.12) (2022-08-30)
-
-
-### Bug Fixes
-
-* fix https://github.com/wangeditor-team/wangEditor/issues/4754 ([e0216b9](https://github.com/wangeditor-team/wangEditor/commit/e0216b98b0ea9ebf4f9cc8a8fd820d68fcd230d3))
-
-
-
-
-
-## [1.1.11](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.10...@wangeditor/core@1.1.11) (2022-07-27)
-
-**Note:** Version bump only for package @wangeditor/core
-
-
-
-
-
-## [1.1.10](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.9...@wangeditor/core@1.1.10) (2022-07-27)
-
-
-### Bug Fixes
-
-* setHtml 支持空字符串 ([d438157](https://github.com/wangeditor-team/wangEditor/commit/d43815766320d9cb0548bae0415c54ce7b147efb))
-* upload file callback error ([bf20e07](https://github.com/wangeditor-team/wangEditor/commit/bf20e07f12ed242b0ab4bb2290d876153a822972))
-
-
-
-
-
-## [1.1.9](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.8...@wangeditor/core@1.1.9) (2022-07-22)
-
-
-### Bug Fixes
-
-* 粘贴 HTML <a> bug ([b935ef6](https://github.com/wangeditor-team/wangEditor/commit/b935ef622b9d4f8f3a9954d26a41c89d4e8042bd))
-
-
-
-
-
-## [1.1.8](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.7...@wangeditor/core@1.1.8) (2022-07-18)
-
-
-### Bug Fixes
-
-* 粘贴文字报错 ([a11ea56](https://github.com/wangeditor-team/wangEditor/commit/a11ea56af4f7976f5664232e80a164cd37d84d8c))
-
-
-
-
-
-## [1.1.7](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.6...@wangeditor/core@1.1.7) (2022-07-16)
-
-
-### Bug Fixes
-
-* setHtml() 多一个空行 ([994954f](https://github.com/wangeditor-team/wangEditor/commit/994954fcbae72808e3488e0936a5f82253b603f4))
-* 图片受 indent 影响 ([3d737f1](https://github.com/wangeditor-team/wangEditor/commit/3d737f11e457c46e1aeee40ebd834a2470198dfd))
-
-
-
-
-
-## [1.1.6](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.5...@wangeditor/core@1.1.6) (2022-07-14)
-
-
-### Bug Fixes
-
-* 粘贴网页 HTML 报错 ([939cb22](https://github.com/wangeditor-team/wangEditor/commit/939cb2229a11eea827e1bea4420f7502db1e7eb6))
-
-
-
-
-
-## [1.1.5](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.4...@wangeditor/core@1.1.5) (2022-07-13)
-
-
-### Bug Fixes
-
-* setHtml 问题 - table 后面 p 格式错误 ([b525b4a](https://github.com/wangeditor-team/wangEditor/commit/b525b4aaa69b834204232774971367beba7db975))
-
-
-
-
-
-## [1.1.4](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.3...@wangeditor/core@1.1.4) (2022-07-12)
-
-**Note:** Version bump only for package @wangeditor/core
-
-
-
-
-
-## [1.1.3](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.2...@wangeditor/core@1.1.3) (2022-07-11)
-
-
-### Bug Fixes
-
-* scroll 滚动问题 ([bc133e1](https://github.com/wangeditor-team/wangEditor/commit/bc133e1e4ca89ab5042cbc0971578ad144499805))
-* 修复选中内容输入时,出现光标位置不对或者输入重复内容的问题 ([9596a4c](https://github.com/wangeditor-team/wangEditor/commit/9596a4ccaca2e2c4eed7ffc16fc4b042f73cef5d))
-
-
-
-
-
-## [1.1.2](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.1...@wangeditor/core@1.1.2) (2022-07-11)
-
-
-### Bug Fixes
-
-* editor.focus() 参数语法错误 ([334fa21](https://github.com/wangeditor-team/wangEditor/commit/334fa217d43fdaa95454e7c85a53526b7b777fda))
-* focus blur 问题 ([4a1997b](https://github.com/wangeditor-team/wangEditor/commit/4a1997b9f19cdce9d6aa6ff4e8e13d439b12af05))
-* 单词之间空格问题 issue 4403 ([2f1d6f5](https://github.com/wangeditor-team/wangEditor/commit/2f1d6f5275c8a9e106b66213bb276c58a70aff79))
-
-
-
-
-
-## [1.1.1](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.1.0...@wangeditor/core@1.1.1) (2022-06-02)
-
-
-### Bug Fixes
-
-* issue 4308 - 自定义字号、字体无法回显 ([ad38b8c](https://github.com/wangeditor-team/wangEditor/commit/ad38b8ce6dbcff1d65785c8d6701238ad351f562))
-
-
-
-
-
-# [1.1.0](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/core@1.0.1...@wangeditor/core@1.1.0) (2022-05-25)
-
-
-### Bug Fixes
-
-* 修复 readonly 模式下,特定内容下editor初始化报错的问题 ([f3bc8b8](https://github.com/wangeditor-team/wangEditor/commit/f3bc8b8d485765cfa8fa7d19e530aa1a1b4bc4e2))
-* 粘贴 HTML 后 font-size font-family line-height 不显示 ([2281957](https://github.com/wangeditor-team/wangEditor/commit/2281957020a30de9cda1c5e9d5e20c6668b7f592))
-
-
-### Features
-
-* editVideoSize ([375eecb](https://github.com/wangeditor-team/wangEditor/commit/375eecba826eac681268c55c47bcd922f7157d63))
-* setHtml ([f4f91b8](https://github.com/wangeditor-team/wangEditor/commit/f4f91b883298091e3679ca6b206ae0d796003772))
-
-
-
-
-
-## 1.0.1 (2022-04-18)
-
-
-### Bug Fixes
-
-* 部分菜单 disabled ([87f1233](https://github.com/wangeditor-team/wangEditor/commit/87f12332a087072406c1988dc5cef2eae8335375))
-* 错别字 alwaysEnable ([82c5136](https://github.com/wangeditor-team/wangEditor/commit/82c5136f8496be420dfa26b0f30522e19924a907))
-* 弹出 modal 时 blur ([53454ef](https://github.com/wangeditor-team/wangEditor/commit/53454ef74b0775391aecf2d745561c9281715934))
-* 点击编辑器区域,未关闭 dropPanel ([b23123b](https://github.com/wangeditor-team/wangEditor/commit/b23123bb361ac2acadcacdfeaa78dd7bf878f86e))
-* 多余的空行 ([4af6c64](https://github.com/wangeditor-team/wangEditor/commit/4af6c648861c2c56db62fae28e9dfa0d27ca5d51))
-* 多余的空行 ([9dde85c](https://github.com/wangeditor-team/wangEditor/commit/9dde85cec5a27be21e0b89c24288d418e1f6d2de))
-* 更新各包之间依赖版本 ([75c552c](https://github.com/wangeditor-team/wangEditor/commit/75c552cc8ed54765bebb86a7ec5329a7fc79e85f))
-* 获取 activeElement 兼容 Document 和 ShadowRoot ([d904e5d](https://github.com/wangeditor-team/wangEditor/commit/d904e5dc263ce670362779b0cfa51ca9f7a8bd86))
-* 拼音输入 bug we-2021/issues/47 ([20b7429](https://github.com/wangeditor-team/wangEditor/commit/20b74298509d9463d6aa1aaffabc21bd33bd7857))
-* 拼音隐藏 placeholder ([aec1a9f](https://github.com/wangeditor-team/wangEditor/commit/aec1a9f62af8944b7894beeca953076ec73545d5))
-* 全屏边距 ([1acb129](https://github.com/wangeditor-team/wangEditor/commit/1acb12974848af28e2d0f574f85a59145675cdbc))
-* 全选 ([3cb8f42](https://github.com/wangeditor-team/wangEditor/commit/3cb8f428a0b94c280b63d42f46c148a9f0e2d9fd))
-* 上传图片 - base64 仍触发上传 + 超出 maxSize 的报错提醒 ([a1d469a](https://github.com/wangeditor-team/wangEditor/commit/a1d469accb7f87f8ea0282a1699d002aaaa4e79a))
-* 使用了 ts 类型空间导入方式优化 ([5d7b509](https://github.com/wangeditor-team/wangEditor/commit/5d7b5094e561af138b2569c669fd4daad2808f73))
-* 图片上传,提示 ([3754012](https://github.com/wangeditor-team/wangEditor/commit/37540129dff1212c5ebfd4ca3f4d4e8def735e73))
-* 完善了 isDOMEventHandled ([745f1d7](https://github.com/wangeditor-team/wangEditor/commit/745f1d7b949eb8839cbdb0fb1690c33c386b697f))
-* 完善了 metaWithUrl 类型声明 ([3542834](https://github.com/wangeditor-team/wangEditor/commit/3542834b9aa65eba5b1c352d106f6623e5fcdc06))
-* 修复 firefox 上全选编辑器内容使用拼音输入异常 ([87dafcb](https://github.com/wangeditor-team/wangEditor/commit/87dafcbe4c51d588ac97d3825a9389571fa16404))
-* 修复 modal 中的 input 没有被 focus ([484c51e](https://github.com/wangeditor-team/wangEditor/commit/484c51e4629defe9eac3f2acaf83ccb62a669d5d))
-* 修复 modal close 时没有恢复选区的问题 ([16f5a57](https://github.com/wangeditor-team/wangEditor/commit/16f5a57b2815026741249e8b4ef9e7222071353f))
-* 修复回车超过视口后没有自动滚动的问题 ([f088b52](https://github.com/wangeditor-team/wangEditor/commit/f088b52ff8c9386ba9efc2d7d3e97f76c702b26d))
-* 修复了使用拼音输入法在 safari 上光标位置没有正常更新的问题 ([cb4cf12](https://github.com/wangeditor-team/wangEditor/commit/cb4cf12bcb6448e5964c47674281f37db96069fa))
-* 修复连续输入空格滚动条不滚动的bug ([3bd358d](https://github.com/wangeditor-team/wangEditor/commit/3bd358d83969a53f1ed4f3fd349eb186750f9461))
-* 修复内容重复和编辑器内容拖动的一些 bug ([5a9c9d0](https://github.com/wangeditor-team/wangEditor/commit/5a9c9d0b0880dc006180a5c4e5828f54cd1905da))
-* 修复行间距过小无效 ([5f13a5b](https://github.com/wangeditor-team/wangEditor/commit/5f13a5b3dc859a45ad25f88ad363f408d23bcee1))
-* 修复选中内容中文输入时光标定位问题 ([51596a8](https://github.com/wangeditor-team/wangEditor/commit/51596a8b0b920dc1d1a9e39fff7c3624c0aa6f52))
-* 修复用户自定义change事件获取html时tabal报错 ([5204f8e](https://github.com/wangeditor-team/wangEditor/commit/5204f8ebf63abdf8a7093e202411b63ce86c2964))
-* 修复在 Chrome 和 Safari 中删除内容时,内联空节点被选中 ([a47c73f](https://github.com/wangeditor-team/wangEditor/commit/a47c73fc5fa008096165d5ac9c55d01f4a6b045b))
-* 修复在 Safari 下,即使 contenteditable 元素非聚焦状态,并不会删除所选内容 ([3e8ca3c](https://github.com/wangeditor-team/wangEditor/commit/3e8ca3c86074454a75054e5ded03154f6b6544ea))
-* 修复在代码块中中文输入会有多余字符的问题 ([a138c3f](https://github.com/wangeditor-team/wangEditor/commit/a138c3f0a2f25d9f89afb912cff45596f99e6b05))
-* 修复在destory可能出现editor not find的问题 ([ce60416](https://github.com/wangeditor-team/wangEditor/commit/ce604165527435952b5ac4b011842714ec8cd5dd))
-* 修复Safari上table内空行输入报错的问题 ([dae6dc5](https://github.com/wangeditor-team/wangEditor/commit/dae6dc544f714f195989a05970cb6bf272f6eb8b))
-* 修复ua正则不支持100+的问题 ([c488ba0](https://github.com/wangeditor-team/wangEditor/commit/c488ba09183cbfcabef223709464c42fac53aea0))
-* 选择图片会滚动 ([d2a8762](https://github.com/wangeditor-team/wangEditor/commit/d2a87629cedc3533e268a31ca822f414082bf48d))
-* 选中内容输入中文报错 ([890cc68](https://github.com/wangeditor-team/wangEditor/commit/890cc686e566be68227641d5f31b42de66351126))
-* 移除了每个包下的 publishConfig directory 配置 ([16559f0](https://github.com/wangeditor-team/wangEditor/commit/16559f052545c111318be760e64291a521bdcc65))
-* 优化插入新文本的滚动交互 ([71131a4](https://github.com/wangeditor-team/wangEditor/commit/71131a4355d24b805052fa9bcf1515432e4351ad))
-* 优化当父元素有滚动条,插入新文本的滚动交互 ([9275090](https://github.com/wangeditor-team/wangEditor/commit/9275090399f068db14854f2794b9aab996bee22e))
-* 优化了 core 类型声明 ([5b5ee1e](https://github.com/wangeditor-team/wangEditor/commit/5b5ee1ee34300748460cedab6fcd46463820f8ef))
-* 优化了 deleteFragment  函数调用传参 ([8d8145c](https://github.com/wangeditor-team/wangEditor/commit/8d8145c5e496a28e2d586722101d217ba1be7079))
-* 优化了 normalizeDOMPoint 函数 ([31b9999](https://github.com/wangeditor-team/wangEditor/commit/31b99992bdc5bc2cc239320200da7d5ba7d6cfc0))
-* 优化了当编辑失焦编辑区域滚动到顶部的问题 ([ebb966b](https://github.com/wangeditor-team/wangEditor/commit/ebb966bce81023c79727bae846920323f733008d))
-* 优化了浏览器是否支持 beforeinput 事件的兼容性判断 ([ea221bb](https://github.com/wangeditor-team/wangEditor/commit/ea221bb3e176ace7a99854673fd727dedc0b3ba7))
-* 优化选中代码块不应该展示 hoverbar 的交互 ([33dcbd6](https://github.com/wangeditor-team/wangEditor/commit/33dcbd6560dccfbe77e18cfbce8c9f077f19f6cd))
-* 在移动 word 之前折叠展开选区 ([6b9b0f3](https://github.com/wangeditor-team/wangEditor/commit/6b9b0f3c9755c1950b0645c34166bd043a9d05f0))
-* 增加 EXTEND_CONF 配置扩展能力 ([ff75a16](https://github.com/wangeditor-team/wangEditor/commit/ff75a16643b26d2d0e7a92cfdd827d5f0f56a849))
-* 重复创建 ([3682c53](https://github.com/wangeditor-team/wangEditor/commit/3682c53b181b89d2c16b5d9845b381a4813c9e3c))
-* autoFucos ([fea2faf](https://github.com/wangeditor-team/wangEditor/commit/fea2faf0af83a3eec67ee7bc7d76328409d2d703))
-* beforeinput support ([60e6efc](https://github.com/wangeditor-team/wangEditor/commit/60e6efc3b3d6c31c4834e3b40e02fc8bc4ceaea6))
-* blockquote & header insertBreak ([06678c9](https://github.com/wangeditor-team/wangEditor/commit/06678c963e8c8421ecded448de7510b254117550))
-* button 增加 type ([37b3390](https://github.com/wangeditor-team/wangEditor/commit/37b33903e0ae5ffe95ab907791ab484facd052d9))
-* chrome 链接后输入拼音,js 错误 ([6c04fab](https://github.com/wangeditor-team/wangEditor/commit/6c04fabb2c5ec78e13c1e1583685cf726887dcae))
-* clear API ([c188b56](https://github.com/wangeditor-team/wangEditor/commit/c188b567379ae32abcfa879620c995c8d45818c4))
-* code-block 选择语言 - 点击拖拽滚动条 ([b8c75e7](https://github.com/wangeditor-team/wangEditor/commit/b8c75e7dc5332c9da622433380802886dedc4344))
-* composition-end ([082561d](https://github.com/wangeditor-team/wangEditor/commit/082561dc341b45791933757e2cf6102190004674))
-* create - 判断 content length ([c0eadc9](https://github.com/wangeditor-team/wangEditor/commit/c0eadc9bf03edc7576c1d3e957babede4c0b546f))
-* dangerouslyInsertHtml - 兼容异常情况 ([8b549f4](https://github.com/wangeditor-team/wangEditor/commit/8b549f480434782107eda3412bf6530d0d7eb9ba))
-* droplist 过长 ([1de2a76](https://github.com/wangeditor-team/wangEditor/commit/1de2a76ac802b80c1b45537c129e5833b4d73d33))
-* dropPanel 定位 ([e76310a](https://github.com/wangeditor-team/wangEditor/commit/e76310a1c6d4aafb2385faebb005bdddd38f9838))
-* editor.blur() api 无效 ([48cbff3](https://github.com/wangeditor-team/wangEditor/commit/48cbff3142d961ff2eaf2f76a3182488de2e5b93))
-* firefox下全选输入出现多余字符 ([659b107](https://github.com/wangeditor-team/wangEditor/commit/659b1078e3395ff00ddc0d1792fbf9c4d448ca41))
-* fix https://github.com/wangeditor-team/wangEditor-v5/issues/457 ([1d8a46a](https://github.com/wangeditor-team/wangEditor/commit/1d8a46a1b5402c2ecb418db24d9d22532d152cea))
-* fullScreen 隐藏 hoverbar ([ec463d3](https://github.com/wangeditor-team/wangEditor/commit/ec463d302cdc527987741ae6208a625af91ea61c))
-* getElems 增加 id ([1dcedd9](https://github.com/wangeditor-team/wangEditor/commit/1dcedd9392d2eecef29f9c93e8915a2f2f83b8a5))
-* getHtml 死循环 ([4614bfb](https://github.com/wangeditor-team/wangEditor/commit/4614bfb5c3a2658348a59749dd800a349e6c33a9))
-* getHtml API ([c0b60cf](https://github.com/wangeditor-team/wangEditor/commit/c0b60cf47d8eaae4292265906fbe07875e1564c9))
-* group-menu 考虑 excludeKeys ([ecc29f3](https://github.com/wangeditor-team/wangEditor/commit/ecc29f3b24992c8dc0adf006d81b0d4a252683c5))
-* hotkey mod ([d480c20](https://github.com/wangeditor-team/wangEditor/commit/d480c206fd83ecc8d12f36147c210208aa6d6ab3))
-* hoverbar - 处于网页下部 ([6cfb3e2](https://github.com/wangeditor-team/wangEditor/commit/6cfb3e2d364f4532cbafe5c8c6e4b3bc13fa2d78))
-* hoverbar 被点击多次隐藏 ([bf4fc19](https://github.com/wangeditor-team/wangEditor/commit/bf4fc193847e8caba3a67c8dd152eae4f1950c4f))
-* hoverbar active ([ceb3f41](https://github.com/wangeditor-team/wangEditor/commit/ceb3f41deafd8fc2cb8d3e8a498cb8d90ad1c73f))
-* hoverbar modal 重复创建 ([70d2b61](https://github.com/wangeditor-team/wangEditor/commit/70d2b618a0662c88cd5e6691f513009726ce1b9b))
-* hoverbar show/hide ([c96bc83](https://github.com/wangeditor-team/wangEditor/commit/c96bc8378939fecd78807fea4f2b7e1eec2a9ea0))
-* hoverbarKeys - text ([59b4840](https://github.com/wangeditor-team/wangEditor/commit/59b48406b4c373ef029a5f5bdb0d15d925a91a0f))
-* html 特殊字符 ([b3eb81b](https://github.com/wangeditor-team/wangEditor/commit/b3eb81bc9c4aa15c2ff7451c173de15d6c4552bc))
-* i18n - 获取多语言配置 ([9f81597](https://github.com/wangeditor-team/wangEditor/commit/9f815970f8c3c6dddb6bf846ecb672325e80444b))
-* i18n 切换语言 ([b3b4642](https://github.com/wangeditor-team/wangEditor/commit/b3b4642c6e72ab0b13b05657745abb87e71c633d))
-* insertHtml - maxLength ([8c7dc8b](https://github.com/wangeditor-team/wangEditor/commit/8c7dc8b8efe1705af9989b040b04e2f98932cb77))
-* insertHtml - maxLength ([52d72ec](https://github.com/wangeditor-team/wangEditor/commit/52d72ec4778a7a6c6f31a7e95d82fb91c9384ae8))
-* insertHtml - maxLength ([b573359](https://github.com/wangeditor-team/wangEditor/commit/b5733597966b16d876b0c0e18509f04638e1c4df))
-* insertKeys ([0a89420](https://github.com/wangeditor-team/wangEditor/commit/0a8942050bd0b39afb5bbc55ca7842461a5b98eb))
-* link, text hoverbar 选区问题 ([e0b7438](https://github.com/wangeditor-team/wangEditor/commit/e0b7438c89a347f1b0b940d9c11150b72d595529))
-* maxLength - 拼音 + 粘贴 ([3ac4db6](https://github.com/wangeditor-team/wangEditor/commit/3ac4db6d78cbe7a8d1fe19747deb0a17edd9b552))
-* maxLength 对于拼音输入无效 ([117faa6](https://github.com/wangeditor-team/wangEditor/commit/117faa635e99667c4762b58757f045c80f949323))
-* menu 点击多次才能生效 ([6497e39](https://github.com/wangeditor-team/wangEditor/commit/6497e39225a993c4d87f9ffddf20086446a4fbc2))
-* min-height ([460fad5](https://github.com/wangeditor-team/wangEditor/commit/460fad56001e83842786629b1d1f8ed6411f4fd4))
-* modal close ([dbfb3b4](https://github.com/wangeditor-team/wangEditor/commit/dbfb3b42504ae97aa0f641ff7fe5eba208b43580))
-* normalize when create editor ([2b51962](https://github.com/wangeditor-team/wangEditor/commit/2b5196244a93ad7beb316bfa42e557221967d063))
-* parse html - 有些 elem children 需要过滤 ([63cbb80](https://github.com/wangeditor-team/wangEditor/commit/63cbb804c8c7a778a4ee1f4ba8717a11b4b6b5a3))
-* parse-html - space 160 ([54e72bc](https://github.com/wangeditor-team/wangEditor/commit/54e72bcb5ed38b8dc77e957ebd5d35881466b5b3))
-* parse-html - sub sup ([2c15a5f](https://github.com/wangeditor-team/wangEditor/commit/2c15a5f9c9c2de8b34770a6bebfe765d203a03f6))
-* parseHtml - 多空格文本 ([5d4479c](https://github.com/wangeditor-team/wangEditor/commit/5d4479c5d11fc23233ea63f0b69c845fa2ab8630))
-* placeholder - 全选输入中文 ([fe4dd2a](https://github.com/wangeditor-team/wangEditor/commit/fe4dd2a85d54d64e2411c3dfc6cb90ac18003e28))
-* placeHolder elem ([7d577ac](https://github.com/wangeditor-team/wangEditor/commit/7d577ac4d6003d1b4c8575be1c014cfa6632d248))
-* readOnly 时菜单还可操作 ([0d4a29b](https://github.com/wangeditor-team/wangEditor/commit/0d4a29bb5ba8b62ac11a09d3f814abcb1fcf46be))
-* readOnly 依然可以 insertText ([096eeaf](https://github.com/wangeditor-team/wangEditor/commit/096eeafd0fc62edf196ed3a9549c04ce19b6b159))
-* rename es module filename ([1821d4e](https://github.com/wangeditor-team/wangEditor/commit/1821d4eef49e64efcb41b848849ca7a5e6472044))
-* shadowDOM 节点支持问题 ([5eb41f1](https://github.com/wangeditor-team/wangEditor/commit/5eb41f1048ad110003b2ef95e0f22e26b7fd757c))
-* shadowDOM 在失焦状态下元素获取失败 ([98aeccc](https://github.com/wangeditor-team/wangEditor/commit/98aeccc5be85513d577397642a9a2d2f730a0406))
-* table - 粘贴合并单元格的表格 ([56ecb63](https://github.com/wangeditor-team/wangEditor/commit/56ecb6392510d433e092653f0f08183361778a3d))
-* table - elemToHtml ([e36e609](https://github.com/wangeditor-team/wangEditor/commit/e36e6092ef721723169afc8bf0560a47ac9f4dfc))
-* table-cell 全选 ([1ef4872](https://github.com/wangeditor-team/wangEditor/commit/1ef48729e6d99e7414bc89bc4ef0d66c172fc566))
-* tableCell 中 br 报错 ([8604db7](https://github.com/wangeditor-team/wangEditor/commit/8604db751b622c01fa5391af59328236cf13effc))
-* td th 中换行不起作用 ([89c6032](https://github.com/wangeditor-team/wangEditor/commit/89c6032a1c41100b7adaf9927e6bc9c06d0228db))
-* textarea height ([873b04a](https://github.com/wangeditor-team/wangEditor/commit/873b04a65a7140afdc2427ac07fce57b3e2c423e))
-* tooltip ([7e066d1](https://github.com/wangeditor-team/wangEditor/commit/7e066d1368f1bfaaca21e3385647be2dee6837f9))
-* upload progress 0 ([9e660be](https://github.com/wangeditor-team/wangEditor/commit/9e660be126adb969dd8a80166b60d6f62be17b2a))
-* url 后面中文输入异常 ([3bcebc7](https://github.com/wangeditor-team/wangEditor/commit/3bcebc78352e05cfec92eed92ee0b05d233feaef))
-* void node - 不清理 text ([1bc891c](https://github.com/wangeditor-team/wangEditor/commit/1bc891c46318f5c5ab969752b3ddb8d75ee1faf7))
-* vue 组件增加 customPaste ([e764248](https://github.com/wangeditor-team/wangEditor/commit/e76424870c75e09ab6267b604a951444b2e847c5))
-* w-e-menu-tooltip 和 v4 冲突 ([762403b](https://github.com/wangeditor-team/wangEditor/commit/762403b2c4e860b3855cbc0caa883b1443d3c862))
-* z-index ([02ec2d5](https://github.com/wangeditor-team/wangEditor/commit/02ec2d54605e747b7d4e1377a58fc9e14c9bba7c))
-
-
-### Features
-
-* 增加 API ([63d6fe8](https://github.com/wangeditor-team/wangEditor/commit/63d6fe85f17fea31c95fec727126799a979ec2f9))
-* 增加 enable disable API(删除 setConfig setMenuConfig API) ([984fc50](https://github.com/wangeditor-team/wangEditor/commit/984fc50520061fc34ea08f4136bdeb93dee46564))
-* 支持 nodejs 环境 ([484f18c](https://github.com/wangeditor-team/wangEditor/commit/484f18c3abc70d19e51c556f48491c18d390b1e1))
-* API - getElemsByType + move + moveReverse ([748ad71](https://github.com/wangeditor-team/wangEditor/commit/748ad710b55d26ade4df1d8caa0a6ea5d2f6f8c7))
-* basic text paste ([f0a5b98](https://github.com/wangeditor-team/wangEditor/commit/f0a5b980c95fa1e2fc59a898c6e0d0723c276c28))
-* basic text style module ([005b343](https://github.com/wangeditor-team/wangEditor/commit/005b343573ba98f2d0b8480d034ff6807a499aa3))
-* bold & header ([8130c23](https://github.com/wangeditor-team/wangEditor/commit/8130c23ad84485a68cf9ca4b53d52fab1cec4e96))
-* clear color ([93b1a18](https://github.com/wangeditor-team/wangEditor/commit/93b1a189395ba113dfe9f793c69e136607f9a28f))
-* clear editor api ([01b07f2](https://github.com/wangeditor-team/wangEditor/commit/01b07f2a2250661ef121919192d40a4852d50a91))
-* clearStyle menu ([8002f70](https://github.com/wangeditor-team/wangEditor/commit/8002f707ed04b914180ec36fdca0edf48c815e01))
-* close modal ([b5106f4](https://github.com/wangeditor-team/wangEditor/commit/b5106f4428813cf794c468034c80824b0a4f08db))
-* code highlight ([42b2f8d](https://github.com/wangeditor-team/wangEditor/commit/42b2f8d192e2433593c11ad0b8424737f6cffb58))
-* code-block - part ([a8bcd63](https://github.com/wangeditor-team/wangEditor/commit/a8bcd63d882832ac05a32878df0f767d145e0fa7))
-* create editor ([12d98e4](https://github.com/wangeditor-team/wangEditor/commit/12d98e4bee179e9d277ec3ec2ecb827962ed0e75))
-* customPaste ([0f25f5c](https://github.com/wangeditor-team/wangEditor/commit/0f25f5cae3a2cd5ae5832f3fc1026b3ab6d047e0))
-* dangerouslyInsertHtml ([4dc3d0c](https://github.com/wangeditor-team/wangEditor/commit/4dc3d0cb403d751ae067a541868e77083c8ce74c))
-* drag resize image ([cd72028](https://github.com/wangeditor-team/wangEditor/commit/cd72028f1786e2e53079ad5cbef1b8569731ca79))
-* editor 生命周期,自定义事件 ([00e9bc2](https://github.com/wangeditor-team/wangEditor/commit/00e9bc2cfcb8b622764db1c76394491d72ffd93e))
-* editor with-selection plugin ([9f0a39f](https://github.com/wangeditor-team/wangEditor/commit/9f0a39fecf6d92888d2a97929820d3be038efb31))
-* editor.alert ([f147c8f](https://github.com/wangeditor-team/wangEditor/commit/f147c8f234510959c770860ac2f194e8d720f177))
-* editor.isSelectedAll ([960c845](https://github.com/wangeditor-team/wangEditor/commit/960c8455f85a6bc7350f9944be80b3997bc1fea1))
-* editor.showProgressBar ([51761d4](https://github.com/wangeditor-team/wangEditor/commit/51761d466ab3ef7c99e872954d4724ab51d8e28c))
-* focus支持focus到文档末尾 ([628830e](https://github.com/wangeditor-team/wangEditor/commit/628830ef06ff85b3e67001ce30dd9e0557b0aa28))
-* font-size + font-family ([cc649e0](https://github.com/wangeditor-team/wangEditor/commit/cc649e0918ce58e78b4d5ee49a400197b9d04b70))
-* fullScreen ([e7ccd88](https://github.com/wangeditor-team/wangEditor/commit/e7ccd88a7dd58f64b7bd484de428e3a76cc994f7))
-* getElemsByTypePrefix (删掉 getHeaders) ([c18834b](https://github.com/wangeditor-team/wangEditor/commit/c18834b3ebfd97fb36ccbe0faa84e6fe8c30eb67))
-* getHeaders & editor.srcollToElem ([2bfb813](https://github.com/wangeditor-team/wangEditor/commit/2bfb813e4957f080c6676ec38f8f051275cdf44a))
-* getSelectionText + maxLength ([58f6648](https://github.com/wangeditor-team/wangEditor/commit/58f66489b65f857238d96b93120f6de7e2750c81))
-* groupButton disabled ([8ffd44c](https://github.com/wangeditor-team/wangEditor/commit/8ffd44c9a44758e951ca7bd02dd46746fcac1c03))
-* hover bar ([107356e](https://github.com/wangeditor-team/wangEditor/commit/107356eff7bfaf53ce25e39244f8133c80518375))
-* i18n ([c11b244](https://github.com/wangeditor-team/wangEditor/commit/c11b2440f91b99d40bca18b675c66a22b6e160c9))
-* image menu - width 50% 100% ([f9b4c68](https://github.com/wangeditor-team/wangEditor/commit/f9b4c68dff3232b50491b07949c20eb4c18baa6b))
-* image menu config ([bb18774](https://github.com/wangeditor-team/wangEditor/commit/bb187740e9703b4a76cde4f5e4d32ac714aa793a))
-* image menus & position ([bf5beba](https://github.com/wangeditor-team/wangEditor/commit/bf5beba7b3014d63f0b9fe0063530c8b101a5011))
-* indent menu + groupMenu ([08db901](https://github.com/wangeditor-team/wangEditor/commit/08db901cd3a3f2ddb2173cc4b36d471e4e68237e))
-* insert link ([b04242f](https://github.com/wangeditor-team/wangEditor/commit/b04242ffa252d4088f5360c3de45c24d6f493552))
-* list menu ([fe6c083](https://github.com/wangeditor-team/wangEditor/commit/fe6c0830b2c43e335e5972f85096f490694bbe19))
-* menu color - part ([3a6cc86](https://github.com/wangeditor-team/wangEditor/commit/3a6cc86a7f9133d0862310c408abafb30c531734))
-* menu color & dropPanel & menu config ([5d0d41b](https://github.com/wangeditor-team/wangEditor/commit/5d0d41b9a765a7deb583393f129925414c36ef35))
-* menu hotkey ([fee05f1](https://github.com/wangeditor-team/wangEditor/commit/fee05f189434d1e57a32ff0dea1a57db6830318a))
-* modal appendTo body ([fc0ab06](https://github.com/wangeditor-team/wangEditor/commit/fc0ab06d5c7177eceb04643234a8c301ca4de396))
-* onBlur onFocus ([590ab4a](https://github.com/wangeditor-team/wangEditor/commit/590ab4a990048bb22cf15787a5fd4615db5b9ef6))
-* parse html ([2a5eace](https://github.com/wangeditor-team/wangEditor/commit/2a5eace00f33cded50b68e8164748ec2480213fd))
-* placeholder ([a3e4cdc](https://github.com/wangeditor-team/wangEditor/commit/a3e4cdcd474063e4f436327aaf4074bb2126d941))
-* react 组件 ([448fc83](https://github.com/wangeditor-team/wangEditor/commit/448fc838d64dbef52cbcddde0e98eb021d8a9122))
-* scroll config ([b4942b4](https://github.com/wangeditor-team/wangEditor/commit/b4942b4334f255b3d537389be3dacf1642dd5441))
-* selectList ([b7366ab](https://github.com/wangeditor-team/wangEditor/commit/b7366ab2dafd379145d85881052d6f400bd13c85))
-* text and toolbar ([3ae5d0c](https://github.com/wangeditor-team/wangEditor/commit/3ae5d0c4138fec7397ac8629e0012affe6b7dfa4))
-* toHtml 机制 ([1c4d872](https://github.com/wangeditor-team/wangEditor/commit/1c4d8729f84aaab6a448f23064b34a20596305e9))
-* toolbar config - insertKeys ([a2f3c4b](https://github.com/wangeditor-team/wangEditor/commit/a2f3c4be3762831723495bbc9d50eb6c9b05d195))
-* toolbar excludeKeys ([09bd196](https://github.com/wangeditor-team/wangEditor/commit/09bd196ea24c19b04e5e7e38227ca94332847bf8))
-* tooltip ([994d875](https://github.com/wangeditor-team/wangEditor/commit/994d875fee81cf01271c2e440c1df202aa067d0e))
-* updateLink + unLink + viewLink ([254d554](https://github.com/wangeditor-team/wangEditor/commit/254d55466b3c8527dd9f0bf34681abd801c8c8ce))
-* vue2 组件 ([fd7847a](https://github.com/wangeditor-team/wangEditor/commit/fd7847a72db661bbf29cf636d454c075fd331224))
diff --git a/packages/core/README.md b/packages/core/README.md
deleted file mode 100644
index 482a97e94..000000000
--- a/packages/core/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# wangEditor core
-
-[wangEditor](https://www.wangeditor.com/) core.
-
-## Main Functionalities
-- View( model -> vdom -> DOM ) + Selection
-- Menus + toolbar + hoverbar
-- Core editor APIs and events
-- Register third-party modules (menus, plugins...)
-
-## Main dependencies
-- [slate.js](https://docs.slatejs.org/)
-- [snabbdom.js](https://github.com/snabbdom/snabbdom)
diff --git a/packages/core/__tests__/config/editor-config.test.ts b/packages/core/__tests__/config/editor-config.test.ts
deleted file mode 100644
index 8b976bcad..000000000
--- a/packages/core/__tests__/config/editor-config.test.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-/**
- * @description editor config test
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-
-describe('editor config', () => {
-  function getStartLocation(editor) {
-    return Editor.start(editor, [])
-  }
-
-  it('if set placeholder option, it will show placeholder element when editor content is empty', () => {
-    const container = document.createElement('div')
-    createCoreEditor({
-      selector: container,
-      config: {
-        placeholder: 'editor placeholder',
-      },
-    })
-    const el = container.querySelector('.w-e-text-placeholder')
-    expect(el!.textContent).toBe('editor placeholder')
-  })
-
-  it('if set placeholder option, it will hide placeholder element when editor content is not empty', () => {
-    const container = document.createElement('div')
-    createCoreEditor({
-      selector: container,
-      config: {
-        placeholder: 'editor placeholder',
-      },
-      content: [{ type: 'paragraph', children: [{ text: '123' }] }],
-    })
-    const el = container.querySelector('.w-e-text-placeholder')
-    expect(el).toBeNull()
-  })
-
-  it('if set readOnly option, isDisabled return true', () => {
-    const editor = createCoreEditor({
-      config: {
-        readOnly: true,
-      },
-    })
-    expect(editor.isDisabled()).toBeTruthy()
-  })
-
-  it('if set readOnly option, can not insert text to editor', () => {
-    const editor = createCoreEditor({
-      config: {
-        readOnly: true,
-      },
-    })
-
-    editor.select(getStartLocation(editor))
-    editor.insertText('xxx') // readOnly 时无法插入文本
-    expect(editor.getText()).toBe('')
-  })
-
-  it('if set maxLength option, the editor can not update content when text length is equal to maxLength', done => {
-    const editor = createCoreEditor({
-      config: {
-        maxLength: 10,
-        onMaxLength: () => {
-          done() // 触发回调,才能完成该测试
-        },
-      },
-    })
-    editor.select(getStartLocation(editor))
-
-    // 插入 9 个字符,小于 maxLength
-    editor.insertText('123456789')
-    expect(editor.getText()).toBe('123456789')
-
-    // 再插入其他字符,则只能插入一个
-    editor.insertText('abc')
-    expect(editor.getText()).toBe('123456789a')
-  })
-
-  it('if set onCreated option, it will be called when created editor', done => {
-    const fn = jest.fn()
-
-    createCoreEditor({
-      config: {
-        onCreated: fn,
-      },
-    })
-
-    setTimeout(() => {
-      expect(fn).toHaveBeenCalled()
-      done()
-    })
-  })
-
-  it('if set onChange option, it will be called when change editor selection', done => {
-    const fn = jest.fn()
-
-    const editor = createCoreEditor({
-      config: {
-        onChange: fn,
-      },
-    })
-
-    editor.select(getStartLocation(editor)) // 选区变化,触发 onchange
-    setTimeout(() => {
-      expect(fn).toHaveBeenCalledWith(editor)
-      done()
-    })
-  })
-
-  it('if set onChange option, it will be called when change editor content', done => {
-    const fn = jest.fn()
-
-    const editor = createCoreEditor({
-      config: {
-        onChange: fn,
-      },
-    })
-
-    editor.select(getStartLocation(editor))
-
-    // 避免选区干扰
-    setTimeout(() => {
-      editor.insertText('123')
-    }, 50)
-    setTimeout(() => {
-      expect(fn).toHaveBeenCalledTimes(2)
-      done()
-    }, 80)
-  })
-
-  it('if set onDestroyed option, it will be called when destroy editor', done => {
-    const fn = jest.fn()
-    const editor = createCoreEditor({
-      config: {
-        onDestroyed: fn,
-      },
-    })
-
-    setTimeout(() => {
-      editor.destroy()
-    })
-
-    setTimeout(() => {
-      expect(fn).toHaveBeenCalledWith(editor)
-      done()
-    }, 20)
-  })
-})
diff --git a/packages/core/__tests__/config/menu-config.test.ts b/packages/core/__tests__/config/menu-config.test.ts
deleted file mode 100644
index d67fb9d5c..000000000
--- a/packages/core/__tests__/config/menu-config.test.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @description menu config test
- * @author wangfupeng
- */
-
-import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-import { registerGlobalMenuConf } from '../../src/config/register'
-
-describe('menu config', () => {
-  it('set and get', () => {
-    // 先注册一下菜单 key ,再设置配置(专为单元测试,用户使用时不涉及)
-    registerGlobalMenuConf('bold', {})
-
-    const menuKey = 'bold' // 必须是一个存在的 menu key
-    const menuConfig = {
-      x: 100,
-    }
-
-    const editor = createCoreEditor({
-      config: {
-        MENU_CONF: {
-          [menuKey]: menuConfig,
-        },
-      },
-    })
-
-    expect(editor.getMenuConfig(menuKey)).toEqual(menuConfig)
-  })
-})
diff --git a/packages/core/__tests__/config/toolbar-config.test.ts b/packages/core/__tests__/config/toolbar-config.test.ts
deleted file mode 100644
index 2fec87bdd..000000000
--- a/packages/core/__tests__/config/toolbar-config.test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @description toolbar config test
- * @author wangfupeng
- */
-
-import createCoreEditor from '../create-core-editor'
-import { IDomEditor } from '../../src/editor/interface'
-import createToolbarForSrc from '../../src/create/create-toolbar'
-
-// 注册几个菜单,测试用
-import '../menus/register-menus/index'
-
-// 创建 toolbar
-function createToolbar(editor: IDomEditor, customConfig = {}) {
-  const container = document.createElement('div')
-  document.body.appendChild(container)
-  return createToolbarForSrc(editor, {
-    selector: container,
-    config: {
-      toolbarKeys: ['myButtonMenu', 'mySelectMenu', 'myModalMenu'], // 已注册的菜单 key
-      ...customConfig,
-    },
-  })
-}
-
-describe('toolbar config', () => {
-  const editor = createCoreEditor()
-
-  it('default config', () => {
-    const toolbar = createToolbar(editor)
-    const defaultConfig = toolbar.getConfig()
-    const { excludeKeys = [], toolbarKeys = [] } = defaultConfig
-    expect(excludeKeys.length).toBe(0)
-    expect(toolbarKeys.length).toBeGreaterThan(0)
-  })
-
-  it('toolbarKeys', () => {
-    const keys = ['mySelectMenu', 'myModalMenu']
-
-    const toolbar = createToolbar(editor, {
-      toolbarKeys: keys,
-    })
-
-    const { toolbarKeys = [] } = toolbar.getConfig()
-    expect(toolbarKeys).toEqual(keys)
-  })
-
-  it('excludeKeys', () => {
-    const keys = ['myButtonMenu', 'mySelectMenu']
-    const toolbar = createToolbar(editor, {
-      excludeKeys: keys,
-    })
-    const { excludeKeys = [] } = toolbar.getConfig()
-    expect(excludeKeys).toEqual(keys)
-  })
-
-  it('insertKeys', () => {
-    const toolbarKeys = ['mySelectMenu', 'myModalMenu']
-    const insertKeysInfo = {
-      index: 0,
-      keys: ['myButtonMenu'],
-    }
-    const toolbar = createToolbar(editor, {
-      toolbarKeys,
-      insertKeys: insertKeysInfo,
-    })
-    const { insertKeys = {} } = toolbar.getConfig()
-    expect(insertKeys).toEqual(insertKeysInfo)
-  })
-})
diff --git a/packages/core/__tests__/create-core-editor.ts b/packages/core/__tests__/create-core-editor.ts
deleted file mode 100644
index 2d5285ddb..000000000
--- a/packages/core/__tests__/create-core-editor.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * @description create editor - 用于 packages/core 单元测试
- * @author wangfupeng
- */
-
-import createEditor from '../src/create/create-editor'
-
-export default function (options: any = {}) {
-  const container = document.createElement('div')
-  document.body.appendChild(container)
-
-  return createEditor({
-    selector: container,
-    ...options,
-  })
-}
diff --git a/packages/core/__tests__/create/content-to-html.test.ts b/packages/core/__tests__/create/content-to-html.test.ts
deleted file mode 100644
index 9f57f0398..000000000
--- a/packages/core/__tests__/create/content-to-html.test.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * @description convert to html test
- * @author wangfupeng
- */
-
-import createEditor from '../../src/create/create-editor'
-
-describe('convert to html or text', () => {
-  let container = document.createElement('div')
-
-  beforeEach(() => {
-    container = document.createElement('div')
-    document.body.appendChild(container)
-  })
-
-  afterEach(() => {
-    document.body.removeChild(container)
-  })
-
-  it('convert to html if give selector option', () => {
-    const editor = createEditor({
-      selector: container,
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    expect(editor.getHtml()).toBe('<div>hello</div>')
-  })
-
-  it('convert to html if not give selector option', () => {
-    const editor = createEditor({
-      // 不传入 selector ,只有 content
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    expect(editor.getHtml()).toBe('<div>hello</div>')
-  })
-
-  it('convert to text if give selector option', () => {
-    const editor = createEditor({
-      selector: container,
-      content: [
-        { type: 'paragraph', children: [{ text: 'hello' }] },
-        { type: 'paragraph', children: [{ text: 'world' }] },
-      ],
-    })
-    expect(editor.getText()).toBe('hello\nworld')
-  })
-
-  it('convert to text if not give selector option', () => {
-    const editor = createEditor({
-      // 不传入 selector ,只有 content
-      content: [
-        { type: 'paragraph', children: [{ text: 'hello' }] },
-        { type: 'paragraph', children: [{ text: 'world' }] },
-      ],
-    })
-    expect(editor.getText()).toBe('hello\nworld')
-  })
-})
diff --git a/packages/core/__tests__/editor/dom-editor.test.ts b/packages/core/__tests__/editor/dom-editor.test.ts
deleted file mode 100644
index e046dc923..000000000
--- a/packages/core/__tests__/editor/dom-editor.test.ts
+++ /dev/null
@@ -1,253 +0,0 @@
-/**
- * @description core editor test
- * @author luochao
- */
-
-import { Editor, Range as SlateRange } from 'slate'
-import { DomEditor } from '../../src/editor/dom-editor'
-import { IDomEditor } from '../../src/editor/interface'
-import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-import { Key } from '../../src/utils/key'
-import { NODE_TO_KEY } from '../../src/utils/weak-maps'
-
-let editor: IDomEditor
-
-describe('Core DomEditor', () => {
-  function genStartLocation() {
-    return Editor.start(editor, [])
-  }
-
-  beforeEach(() => {
-    editor = createCoreEditor()
-    editor.select(genStartLocation())
-  })
-
-  afterEach(() => {
-    editor.destroy()
-  })
-
-  test('DomEditor getWindow should throw Error', () => {
-    try {
-      DomEditor.getWindow(editor)
-    } catch (err) {
-      expect(err.message).toBe('Unable to find a host window element for this editor')
-    }
-  })
-
-  test('DomEditor findKey should return Key for a node', () => {
-    editor.apply({
-      type: 'insert_text',
-      path: [0, 0],
-      text: 'test123',
-      offset: 0,
-    })
-
-    const node = editor.children[0]
-
-    expect(DomEditor.findKey(editor, node) instanceof Key).toBeTruthy()
-  })
-
-  test('DomEditor findKey should return unique Key for different node', () => {
-    editor.apply({
-      type: 'insert_node',
-      path: [0, 0],
-      node: {
-        type: 'paragraph',
-        children: [{ text: 'test123' }],
-      },
-    })
-
-    editor.apply({
-      type: 'insert_node',
-      path: [0, 1],
-      node: {
-        type: 'header1',
-        children: [{ text: 'test456' }],
-      },
-    })
-
-    const [node1, node2] = (editor.children[0] as any).children
-    const keyId1 = DomEditor.findKey(editor, node1).id
-    const keyId2 = DomEditor.findKey(editor, node2).id
-
-    expect(keyId1).not.toBe(keyId2)
-  })
-
-  test('DomEditor findKey should generate new key if node do not exist in NODE_TO_KEY', () => {
-    const node = {
-      type: 'header2',
-      children: [{ text: '123' }],
-    }
-
-    // 防卫断言
-    expect(NODE_TO_KEY.get(node)).toBeUndefined()
-
-    const newKey = DomEditor.findKey(editor, node)
-    expect(NODE_TO_KEY.get(node)).toEqual(newKey)
-  })
-
-  test('DomEditor setNewKey should set new value to NODE_TO_KEY', () => {
-    const node = {
-      type: 'header2',
-      children: [{ text: '123' }],
-    }
-
-    expect(NODE_TO_KEY.get(node)).toBeUndefined()
-
-    DomEditor.setNewKey(node)
-
-    expect(NODE_TO_KEY.get(node)).not.toBeUndefined()
-  })
-
-  test('findPath', () => {
-    const p = editor.children[0]
-    // @ts-ignore
-    const textNode = p.children[0]
-
-    const path = DomEditor.findPath(null, textNode)
-    expect(path).toEqual([0, 0])
-  })
-
-  test('findDocumentOrShadowRoot', () => {
-    const doc = DomEditor.findDocumentOrShadowRoot(editor)
-    expect(doc).toBe(document)
-  })
-
-  test('getParentNode', () => {
-    const p = editor.children[0]
-    // @ts-ignore
-    const textNode = p.children[0]
-
-    expect(DomEditor.getParentNode(null, textNode)).toBe(p)
-    expect(DomEditor.getParentNode(null, p)).toBe(editor)
-  })
-
-  test('getParentsNodes', () => {
-    const p = editor.children[0]
-    // @ts-ignore
-    const textNode = p.children[0]
-
-    const parents = DomEditor.getParentsNodes(editor, textNode)
-    expect(parents[0]).toBe(p)
-    expect(parents[1]).toBe(editor)
-  })
-
-  test('getTopNode', () => {
-    const p = editor.children[0]
-    // @ts-ignore
-    const textNode = p.children[0]
-
-    const topNode = DomEditor.getTopNode(editor, textNode)
-    expect(topNode).toBe(p)
-  })
-
-  test('toDOMNode', () => {
-    const p = editor.children[0]
-
-    const key = DomEditor.findKey(editor, p)
-
-    const domNode = DomEditor.toDOMNode(editor, p)
-    expect(domNode.tagName).toBe('DIV')
-    expect(domNode.id).toBe(`w-e-element-${key.id}`)
-  })
-
-  test('hasDOMNode', () => {
-    const p = editor.children[0]
-    const domNode = DomEditor.toDOMNode(editor, p)
-
-    const res = DomEditor.hasDOMNode(editor, domNode)
-    expect(res).toBeTruthy()
-  })
-
-  // TODO 待写...
-  // test('toDOMRange', () => {})
-
-  // TODO 待写...
-  // test('toDOMPoint', () => {})
-
-  test('toSlateNode', () => {
-    const p = editor.children[0]
-    const domNode = DomEditor.toDOMNode(editor, p)
-
-    const slateNode = DomEditor.toSlateNode(null, domNode)
-    expect(slateNode).toBe(p)
-  })
-
-  // TODO 待写...
-  // test('findEventRange', () => {})
-
-  // TODO 待写...
-  // test('toSlateRange', () => {})
-
-  // TODO 待写...
-  // test('toSlatePoint', () => {})
-
-  test('hasRange', () => {
-    editor.insertText('hello')
-    editor.selectAll()
-
-    const res = DomEditor.hasRange(editor, editor.selection as SlateRange)
-    expect(res).toBeTruthy()
-
-    // expect(1).toBe(1)
-  })
-
-  test('getNodeType', () => {
-    const p = editor.children[0]
-    // @ts-ignore
-    const textNode = p.children[0]
-
-    expect(DomEditor.getNodeType(p)).toBe('paragraph')
-    expect(DomEditor.getNodeType(textNode)).toBe('')
-  })
-
-  test('checkNodeType', () => {
-    const p = editor.children[0]
-    expect(DomEditor.checkNodeType(p, 'paragraph')).toBeTruthy()
-  })
-
-  test('getSelectedElems', () => {
-    editor.insertNode({
-      type: 'some-elem',
-      children: [{ text: 'hello' }],
-    })
-    editor.selectAll()
-
-    const selectedElems = DomEditor.getSelectedElems(editor)
-
-    expect(selectedElems.length).toBe(2)
-    expect(selectedElems[1].type).toBe('some-elem')
-  })
-
-  test('getSelectedNodeByType', () => {
-    const p = editor.children[0]
-    const selectedNode = DomEditor.getSelectedNodeByType(editor, 'paragraph')
-    expect(selectedNode).toBe(p)
-  })
-
-  test('getSelectedTextNode', () => {
-    const p = editor.children[0]
-    // @ts-ignore
-    const textNode = p.children[0]
-
-    const selectedTextNode = DomEditor.getSelectedTextNode(editor)
-    expect(selectedTextNode).toBe(textNode)
-  })
-
-  test('isNodeSelected', () => {
-    const p = editor.children[0]
-    // @ts-ignore
-    const textNode = p.children[0]
-
-    expect(DomEditor.isNodeSelected(editor, p)).toBeTruthy()
-    expect(DomEditor.isNodeSelected(editor, textNode)).toBeTruthy()
-  })
-
-  test('isSelectionAtLineEnd', () => {
-    editor.insertText('hello')
-    expect(DomEditor.isSelectionAtLineEnd(editor, [0])).toBeTruthy() // 在第一行的末尾
-
-    editor.select(genStartLocation()) // 选中开始
-    expect(DomEditor.isSelectionAtLineEnd(editor, [0])).toBeFalsy() // 在第一行的开头
-  })
-})
diff --git a/packages/core/__tests__/editor/plugins/with-config.test.ts b/packages/core/__tests__/editor/plugins/with-config.test.ts
deleted file mode 100644
index 55573c055..000000000
--- a/packages/core/__tests__/editor/plugins/with-config.test.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @description config API test
- * @author wangfupeng
- */
-
-import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-import { withConfig } from '../../../src/editor/plugins/with-config'
-
-function createEditor(...args) {
-  return withConfig(createCoreEditor(...args))
-}
-
-describe('editor config API', () => {
-  it('get config', () => {
-    const editor = createEditor()
-    const defaultConfig = editor.getConfig()
-    expect(defaultConfig).not.toBeNull()
-    expect(defaultConfig.autoFocus).toBeTruthy()
-    expect(defaultConfig.readOnly).toBeFalsy()
-    // 其他 props 不一一写了
-  })
-
-  it('get menu config', () => {
-    const editor = createEditor()
-    const insertLinkConfig = editor.getMenuConfig('insertLink')
-    expect(insertLinkConfig).not.toBeNull()
-  })
-
-  it('get all menus', () => {
-    const editor = createEditor()
-    const menuKeys = editor.getAllMenuKeys()
-    expect(Array.isArray(menuKeys)).toBeTruthy()
-  })
-})
diff --git a/packages/core/__tests__/editor/plugins/with-content.test.ts b/packages/core/__tests__/editor/plugins/with-content.test.ts
deleted file mode 100644
index 7cad49000..000000000
--- a/packages/core/__tests__/editor/plugins/with-content.test.ts
+++ /dev/null
@@ -1,371 +0,0 @@
-/**
- * @description content API test
- * @author wangfupeng
- */
-
-import { Editor, Transforms, Node, Selection } from 'slate'
-import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-import { withContent } from '../../../src/editor/plugins/with-content'
-import { IDomEditor } from '../../../src/editor/interface'
-
-function createEditor(...args) {
-  return withContent(createCoreEditor(...args))
-}
-
-let editor: IDomEditor
-
-function setEditorSelection(
-  editor: IDomEditor,
-  selection: Selection = {
-    anchor: { path: [0, 0], offset: 0 },
-    focus: { path: [0, 0], offset: 0 },
-  }
-) {
-  editor.selection = selection
-}
-
-const ignoreTag = [
-  'doctype',
-  '!doctype',
-  'meta',
-  'script',
-  'style',
-  'link',
-  'frame',
-  'iframe',
-  'title',
-  'svg',
-]
-
-describe('editor content API', () => {
-  function getStartLocation(editor) {
-    return Editor.start(editor, [])
-  }
-
-  it('handleTab', () => {
-    const editor = createEditor()
-    editor.select(getStartLocation(editor))
-    editor.handleTab()
-    expect(editor.getText().length).toBe(4) // 默认 tab 键,输入 4 空格
-  })
-
-  it('getHtml', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-
-    const html = editor.getHtml()
-    expect(html).toBe('<div>hello</div>')
-  })
-
-  it('getHtml with void element', () => {
-    const editor = createEditor({
-      content: [
-        { type: 'paragraph', children: [{ text: 'hello' }] },
-        { type: 'image', children: [{ text: '' }], src: 'test.jpg' },
-      ],
-    })
-
-    const html = editor.getHtml()
-    expect(html).toBe('<div>hello</div><div></div>')
-  })
-
-  it('getText', () => {
-    const editor = createEditor({
-      content: [
-        { type: 'paragraph', children: [{ text: 'hello' }] },
-        { type: 'paragraph', children: [{ text: 'world' }] },
-      ],
-    })
-    const text = editor.getText()
-    expect(text).toBe('hello\nworld')
-  })
-
-  it('isEmpty', () => {
-    const editor1 = createEditor()
-    expect(editor1.isEmpty()).toBeTruthy()
-
-    const editor2 = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    expect(editor2.isEmpty()).toBeFalsy()
-  })
-
-  it('getSelectionText', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-    expect(editor.getSelectionText()).toBe('')
-
-    editor.select([]) // 全选
-    expect(editor.getSelectionText()).toBe('hello')
-  })
-
-  it('getElemsByTypePrefix', () => {
-    const editor = createEditor({
-      content: [
-        { type: 'header1', children: [{ text: 'a' }] },
-        { type: 'header2', children: [{ text: 'b' }] },
-        { type: 'paragraph', children: [{ text: 'c' }] },
-      ],
-    })
-    const headers = editor.getElemsByTypePrefix('header')
-    expect(headers.length).toBe(2)
-    const pList = editor.getElemsByTypePrefix('paragraph')
-    expect(pList.length).toBe(1)
-    const images = editor.getElemsByTypePrefix('image')
-    expect(images.length).toBe(0)
-  })
-
-  it('getElemsByType', () => {
-    const editor = createEditor({
-      content: [
-        { type: 'header1', children: [{ text: 'a' }] },
-        { type: 'header2', children: [{ text: 'b' }] },
-        { type: 'paragraph', children: [{ text: 'c' }] },
-      ],
-    })
-    const headers = editor.getElemsByType('header')
-    expect(headers.length).toBe(0)
-    const pList = editor.getElemsByType('paragraph')
-    expect(pList.length).toBe(1)
-    const images = editor.getElemsByType('image')
-    expect(images.length).toBe(0)
-  })
-
-  it('deleteBackward with character', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-    Transforms.move(editor, { distance: 2, unit: 'character' }) // 光标移动 2 个字符
-
-    editor.deleteBackward('character') // 向后删除
-    expect(editor.getText()).toBe('hllo')
-  })
-
-  it('deleteBackward with word', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello world' }] }],
-    })
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-    Transforms.move(editor, { distance: 1, unit: 'word' }) // 光标移动 1 个单词
-
-    editor.deleteBackward('word') // 向后删除
-    expect(editor.getText()).toBe(' world')
-  })
-
-  it('deleteForward with character', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-    Transforms.move(editor, { distance: 1, unit: 'character' }) // 光标移动 1 个字符
-
-    editor.deleteForward('character') // 向前删除
-    expect(editor.getText()).toBe('hllo')
-  })
-
-  it('deleteForward with word', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello world' }] }],
-    })
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-    Transforms.move(editor, { distance: 1, unit: 'word' }) // 光标移动 1 个 word
-
-    editor.deleteForward('word') // 向前删除
-    expect(editor.getText()).toBe('hello')
-  })
-
-  it('deleteForward with line', () => {
-    const editor = createEditor({
-      content: [
-        { type: 'paragraph', children: [{ text: 'hello' }] },
-        { type: 'paragraph', children: [{ text: 'world' }] },
-      ],
-    })
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-
-    editor.deleteForward('line') // 向前删除
-    expect(editor.getText()).toBe('\nworld')
-  })
-
-  it('getFragment', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    // 选中 'hel'lo
-    editor.select({
-      anchor: {
-        path: [0, 0],
-        offset: 0,
-      },
-      focus: {
-        path: [0, 0],
-        offset: 3,
-      },
-    })
-
-    const fragment = editor.getFragment() // 获取选中内容
-    expect(Node.string(fragment[0])).toBe('hel')
-  })
-
-  it('deleteFragment', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    // 选中 'hel'lo
-    editor.select({
-      anchor: {
-        path: [0, 0],
-        offset: 0,
-      },
-      focus: {
-        path: [0, 0],
-        offset: 3,
-      },
-    })
-
-    editor.deleteFragment() // 删除选中内容
-    expect(editor.getText()).toBe('lo')
-  })
-
-  it('insertBreak', () => {
-    const editor = createEditor()
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-
-    editor.insertBreak()
-    const pList = editor.getElemsByTypePrefix('paragraph')
-    expect(pList.length).toBe(2)
-  })
-
-  it('insertText', () => {
-    const editor = createEditor()
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-    editor.insertText('xxx')
-    expect(editor.getText()).toBe('xxx')
-  })
-
-  it('clear', () => {
-    const editor = createEditor({
-      content: [{ type: 'paragraph', children: [{ text: 'hello' }] }],
-    })
-    editor.clear()
-    expect(editor.getText()).toBe('')
-  })
-
-  it('undo', () => {
-    const editor = createEditor()
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-
-    editor.insertText('hello')
-
-    // @ts-ignore
-    editor.undo()
-    expect(editor.getText()).toBe('')
-  })
-
-  it('redo', () => {
-    const editor = createEditor()
-    editor.select(getStartLocation(editor)) // 光标在开始位置
-
-    editor.insertText('hello')
-
-    // @ts-ignore
-    editor.undo()
-    // @ts-ignore
-    editor.redo()
-    expect(editor.getText()).toBe('hello')
-  })
-
-  describe('dangerouslyInsertHtml API', () => {
-    beforeEach(() => {
-      editor = createEditor()
-    })
-
-    // 现在使用的是 packages/core 的 createEditor ,创建的 editor 没有内置各种 module
-    // 所以 dangerouslyInsertHtml 在此测试基本功能即可。其他 tag 在各自的 module 中测试
-
-    test('dangerouslyInsertHtml should insert text with no blank to editor', () => {
-      // insertText 必须要设置 selection 才能生效
-      setEditorSelection(editor)
-
-      const htmlString = '<div>wangEditor!</div>'
-      editor.dangerouslyInsertHtml(htmlString)
-
-      expect(editor.getText().indexOf('wangEditor')).toBeGreaterThan(-1)
-    })
-
-    ignoreTag.forEach(tag => {
-      test(`insert html string with ${tag} element should to be ignore`, () => {
-        setEditorSelection(editor)
-        const htmlString = `<${tag}></${tag}>`
-        editor.dangerouslyInsertHtml(htmlString)
-
-        expect(editor.getHtml().indexOf(tag)).toBe(-1)
-      })
-    })
-  })
-
-  it('getParentNode', () => {
-    const textNode = { text: 'hello' }
-    const p = { type: 'paragraph', children: [textNode] }
-    const editor = createEditor({
-      content: [p],
-    })
-
-    const parentNode = editor.getParentNode(textNode) as any
-    expect(parentNode).not.toBeNull()
-    expect(parentNode.type).toBe('paragraph')
-  })
-
-  it('insertNode', () => {
-    const editor = createEditor()
-    editor.select(getStartLocation(editor))
-
-    const p = { type: 'paragraph', children: [{ text: 'hello' }] }
-    editor.insertNode(p)
-
-    const pList = editor.getElemsByTypePrefix('paragraph')
-    expect(pList.length).toBe(2)
-  })
-
-  describe('setHtml', () => {
-    it('setHtml normal', () => {
-      const editor = createEditor({ html: '<div>hello</div>' })
-      editor.select(getStartLocation(editor))
-
-      const newHtml = '<div>world</div>'
-      editor.setHtml(newHtml)
-
-      expect(editor.getHtml()).toBe(newHtml)
-    })
-
-    it('setHtml blur', () => {
-      const editor = createEditor({
-        html: '<div>hello</div>',
-        autoFocus: false,
-      })
-      expect(editor.isFocused()).toBe(false)
-
-      const newHtml = '<div>world</div>'
-      editor.setHtml(newHtml)
-
-      expect(editor.getHtml()).toBe(newHtml)
-      expect(editor.isFocused()).toBe(false)
-    })
-
-    it('setHtml disabled', () => {
-      const editor = createEditor({ html: '<div>hello</div>' })
-      editor.disable()
-      expect(editor.isDisabled()).toBe(true)
-
-      const newHtml = '<div>world</div>'
-      editor.setHtml(newHtml)
-
-      expect(editor.getHtml()).toBe(newHtml)
-      expect(editor.isDisabled()).toBe(true)
-    })
-  })
-})
diff --git a/packages/core/__tests__/editor/plugins/with-dom.test.ts b/packages/core/__tests__/editor/plugins/with-dom.test.ts
deleted file mode 100644
index 493b4ebca..000000000
--- a/packages/core/__tests__/editor/plugins/with-dom.test.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @description editor DOM API test
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-import { withDOM } from '../../../src/editor/plugins/with-dom'
-
-function createEditor(...args) {
-  return withDOM(createCoreEditor(...args))
-}
-
-describe('editor DOM API', () => {
-  function getStartLocation(editor) {
-    return Editor.start(editor, [])
-  }
-
-  it('editor id', () => {
-    const editor = createEditor()
-    expect(editor.id).not.toBeNull()
-  })
-
-  it('isFullScreen fullScreen unFullScreen', done => {
-    const editor = createEditor()
-
-    expect(editor.isFullScreen).toBeFalsy()
-
-    editor.fullScreen()
-    expect(editor.isFullScreen).toBeTruthy()
-
-    editor.unFullScreen()
-    setTimeout(() => {
-      expect(editor.isFullScreen).toBeFalsy()
-      done()
-    }, 1000)
-  })
-
-  // TODO focus blur isFocused 用 jest 测试异常,以及 editor-config.test.ts 中的 `onFocus` `onBlur`
-
-  it('disable isDisabled enable', () => {
-    const editor = createEditor()
-    editor.select(getStartLocation(editor))
-
-    expect(editor.isDisabled()).toBeFalsy()
-    editor.insertText('123')
-    expect(editor.getText().length).toBe(3)
-
-    editor.disable()
-    expect(editor.isDisabled()).toBeTruthy()
-    editor.insertText('123') // disabled ,不会插入
-    expect(editor.getText().length).toBe(3)
-
-    editor.enable()
-    expect(editor.isDisabled()).toBeFalsy()
-    editor.insertText('123') // enable ,可以插入
-    expect(editor.getText().length).toBe(6)
-  })
-
-  it('destroy', done => {
-    const editor = createEditor()
-    expect(editor.isDestroyed).toBeFalsy()
-
-    setTimeout(() => {
-      editor.destroy()
-      expect(editor.isDestroyed).toBeTruthy()
-      done()
-    })
-  })
-
-  it('toDOMNode', done => {
-    const p = { type: 'paragraph', children: [{ text: 'hello' }] }
-    const editor = createEditor({
-      content: [p],
-    })
-
-    setTimeout(() => {
-      const domNode = editor.toDOMNode(p)
-      expect(domNode.tagName).toBe('DIV')
-      done()
-    })
-  })
-})
diff --git a/packages/core/__tests__/editor/plugins/with-emitter.test.ts b/packages/core/__tests__/editor/plugins/with-emitter.test.ts
deleted file mode 100644
index 424aca8b9..000000000
--- a/packages/core/__tests__/editor/plugins/with-emitter.test.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * @description editor eventBus API test
- * @author wangfupeng
- */
-
-import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-import { withEmitter } from '../../../src/editor/plugins/with-emitter'
-
-function createEditor(...args) {
-  return withEmitter(createCoreEditor(...args))
-}
-
-describe('eventBus API', () => {
-  it('bind and emit', () => {
-    const editor = createEditor()
-
-    const fn1 = jest.fn() // jest mock function
-    const fn2 = jest.fn()
-    const fn3 = jest.fn()
-
-    editor.on('key1', fn1)
-    editor.on('key1', fn2)
-    editor.on('xxxx', fn3)
-
-    editor.emit('key1', 10, 20)
-
-    expect(fn1).toBeCalledWith(10, 20)
-    expect(fn2).toBeCalledWith(10, 20)
-    expect(fn3).not.toBeCalled()
-  })
-
-  it('off single event', () => {
-    const editor = createEditor()
-
-    const fn1 = jest.fn()
-    const fn2 = jest.fn()
-
-    editor.on('key1', fn1)
-    editor.on('key1', fn2)
-
-    editor.off('key1', fn1)
-
-    editor.emit('key1', 10, 20)
-
-    expect(fn1).not.toBeCalled()
-    expect(fn2).toBeCalledWith(10, 20)
-  })
-
-  it('once', () => {
-    const editor = createEditor()
-
-    let n = 1
-
-    const fn1 = jest.fn(() => n++)
-    const fn2 = jest.fn(() => n++)
-
-    editor.once('key1', fn1)
-    editor.once('key1', fn2)
-
-    // 无论 emit 多少次,只有一次生效
-    editor.emit('key1')
-    editor.emit('key1')
-    editor.emit('key1')
-    editor.emit('key1')
-    editor.emit('key1')
-
-    expect(n).toBe(3)
-  })
-})
diff --git a/packages/core/__tests__/editor/plugins/with-selection.test.ts b/packages/core/__tests__/editor/plugins/with-selection.test.ts
deleted file mode 100644
index e630e7b89..000000000
--- a/packages/core/__tests__/editor/plugins/with-selection.test.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * @description selection API test
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
-import { withSelection } from '../../../src/editor/plugins/with-selection'
-
-function createEditor(...args) {
-  return withSelection(createCoreEditor(...args))
-}
-
-describe('editor selection API', () => {
-  function getStartLocation(editor) {
-    return Editor.start(editor, [])
-  }
-  function genParagraph() {
-    return { type: 'paragraph', children: [{ text: 'hello' }] }
-  }
-
-  // selection select deselect move 是 slate 自带 API 或属性,不测试
-
-  // // TODO 运行报错,看源码有使用 focus ,可能和这个相关???
-  // it('restoreSelection', () => {
-  //   const editor = createEditor()
-  //   editor.select(getStartLocation(editor))
-
-  //   editor.deselect()
-  //   expect(editor.selection).toBeNull()
-
-  //   editor.restoreSelection()
-  //   expect(editor.selection).not.toBeNull()
-  //   // console.log(111, JSON.stringify(editor.selection))
-  // })
-
-  it('isSelectedAll', () => {
-    const p = genParagraph()
-    const editor = createEditor({ content: [p] })
-    expect(editor.isSelectedAll()).toBeFalsy()
-
-    editor.select(getStartLocation(editor))
-    expect(editor.isSelectedAll()).toBeFalsy()
-
-    editor.select([])
-    expect(editor.isSelectedAll()).toBeTruthy()
-  })
-})
diff --git a/packages/core/__tests__/i18n/index.test.ts b/packages/core/__tests__/i18n/index.test.ts
deleted file mode 100644
index 6b8a5fb84..000000000
--- a/packages/core/__tests__/i18n/index.test.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @description i18n test
- * @author wangfupeng
- */
-
-import i18next, { i18nAddResources, i18nChangeLanguage, t } from '../../src/i18n'
-
-describe('i18n', () => {
-  // 添加语言项
-  i18nAddResources('en', {
-    module1: {
-      hello: 'hello',
-    },
-  })
-  i18nAddResources('zh-CN', {
-    module1: {
-      hello: '你好',
-    },
-  })
-
-  it('default lang', () => {
-    expect(i18next.language).toBe('zh-CN')
-    expect(t('module1.hello')).toBe('你好')
-  })
-
-  it('change lang', () => {
-    i18nChangeLanguage('en')
-    expect(i18next.language).toBe('en')
-    expect(t('module1.hello')).toBe('hello')
-  })
-})
diff --git a/packages/core/__tests__/menus/README.md b/packages/core/__tests__/menus/README.md
deleted file mode 100644
index 4cf57a71d..000000000
--- a/packages/core/__tests__/menus/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# menus test
-
-TODO 各个 modules 中没有这块代码的测试,待编写...
diff --git a/packages/core/__tests__/menus/register-menus/index.ts b/packages/core/__tests__/menus/register-menus/index.ts
deleted file mode 100644
index 35d2bb4fb..000000000
--- a/packages/core/__tests__/menus/register-menus/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * @description 注册菜单,入口
- * @author wangfupeng
- */
-
-import './register-button-menu'
-import './register-select-menu'
-import './register-modal-menu'
diff --git a/packages/core/__tests__/menus/register-menus/register-button-menu.ts b/packages/core/__tests__/menus/register-menus/register-button-menu.ts
deleted file mode 100644
index 38f843419..000000000
--- a/packages/core/__tests__/menus/register-menus/register-button-menu.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @description 注册菜单 - button menu
- * @author wangfupeng
- */
-
-import { registerMenu, IButtonMenu } from '../../../src/menus/index'
-import { IDomEditor } from '../../../src/editor/interface'
-
-class MyButtonMenu implements IButtonMenu {
-  readonly title = 'My Button Menu'
-  readonly tag = 'button'
-  getValue(editor: IDomEditor) {
-    return ''
-  }
-  isActive(editor: IDomEditor) {
-    return false
-  }
-  isDisabled(editor: IDomEditor) {
-    return false
-  }
-  exec(editor: IDomEditor, value: string | boolean) {
-    console.log('do..')
-  }
-}
-
-registerMenu({
-  key: 'myButtonMenu',
-  factory() {
-    return new MyButtonMenu()
-  },
-})
diff --git a/packages/core/__tests__/menus/register-menus/register-modal-menu.ts b/packages/core/__tests__/menus/register-menus/register-modal-menu.ts
deleted file mode 100644
index d03c98264..000000000
--- a/packages/core/__tests__/menus/register-menus/register-modal-menu.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @description 注册菜单 - modal menu
- * @author wangfupeng
- */
-
-import { registerMenu, IModalMenu } from '../../../src/menus/index'
-import { IDomEditor } from '../../../src/editor/interface'
-
-class MyModalMenu implements IModalMenu {
-  readonly title = 'My Modal Menu'
-  readonly tag = 'button'
-  readonly showModal = true
-  readonly modalWidth = 300
-  getValue(editor: IDomEditor) {
-    return ''
-  }
-  isActive(editor: IDomEditor) {
-    return false
-  }
-  isDisabled(editor: IDomEditor) {
-    return false
-  }
-  exec(editor: IDomEditor, value: string | boolean) {
-    console.log('do..')
-  }
-  getModalContentElem(editor: IDomEditor) {
-    return document.createElement('div')
-  }
-  getModalPositionNode(editor: IDomEditor) {
-    return null
-  }
-}
-
-registerMenu({
-  key: 'myModalMenu',
-  factory() {
-    return new MyModalMenu()
-  },
-})
diff --git a/packages/core/__tests__/menus/register-menus/register-select-menu.ts b/packages/core/__tests__/menus/register-menus/register-select-menu.ts
deleted file mode 100644
index 36cc7c509..000000000
--- a/packages/core/__tests__/menus/register-menus/register-select-menu.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @description 注册菜单 - select menu
- * @author wangfupeng
- */
-
-import { registerMenu, ISelectMenu, IOption } from '../../../src/menus/index'
-import { IDomEditor } from '../../../src/editor/interface'
-
-class MySelectMenu implements ISelectMenu {
-  readonly title = 'My Select Menu'
-  readonly tag = 'select'
-  getValue(editor: IDomEditor) {
-    return ''
-  }
-  isActive(editor: IDomEditor) {
-    return false
-  }
-  isDisabled(editor: IDomEditor) {
-    return false
-  }
-  exec(editor: IDomEditor, value: string | boolean) {
-    console.log('do..')
-  }
-  getOptions(): IOption[] {
-    return [
-      { value: 'a', text: 'a' },
-      { value: 'b', text: 'b' },
-    ]
-  }
-}
-
-registerMenu({
-  key: 'mySelectMenu',
-  factory() {
-    return new MySelectMenu()
-  },
-})
diff --git a/packages/core/__tests__/parse-html/README.md b/packages/core/__tests__/parse-html/README.md
deleted file mode 100644
index 953109e09..000000000
--- a/packages/core/__tests__/parse-html/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# parse-html test
-
-各个 module `parseHtml` 已经测试了该模块的代码。
diff --git a/packages/core/__tests__/render/README.md b/packages/core/__tests__/render/README.md
deleted file mode 100644
index 6275310ea..000000000
--- a/packages/core/__tests__/render/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# render test
-
-各个 module `renderElem` 已经测试了该模块的代码。
diff --git a/packages/core/__tests__/to-html/README.md b/packages/core/__tests__/to-html/README.md
deleted file mode 100644
index 7b4298124..000000000
--- a/packages/core/__tests__/to-html/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# to-html test
-
-各个 module 中的 `editor.getHtml()` API 会测试到这部分代码。
diff --git a/packages/core/__tests__/upload/uploader.test.ts b/packages/core/__tests__/upload/uploader.test.ts
deleted file mode 100644
index afe56fa50..000000000
--- a/packages/core/__tests__/upload/uploader.test.ts
+++ /dev/null
@@ -1,216 +0,0 @@
-/**
- * @description uploader test
- * @author wangfupeng
- */
-
-import createUploader from '../../src/upload/createUploader'
-import { IUploadConfig } from '../../src/upload/interface'
-import nock from 'nock'
-
-const server = 'https://fake-endpoint.wangeditor-v5.com'
-
-describe('uploader', () => {
-  test('if should return Uppy object if invoke createUploader function', () => {
-    const uppy = createUploader({
-      server: '/upload',
-      fieldName: 'file1',
-      metaWithUrl: true,
-      meta: {
-        token: 'xxx',
-      },
-      onSuccess: (file, res) => {},
-      onFailed: (file, res) => {},
-      onError: (file, err, res) => {},
-    })
-    expect(uppy).not.toBeNull()
-  })
-
-  test('it should throw can not get address error if not pass server option', () => {
-    try {
-      createUploader({
-        fieldName: 'file1',
-        metaWithUrl: false,
-        onSuccess: (file, res) => {},
-        onFailed: (file, res) => {},
-        onError: (file, err, res) => {},
-      } as IUploadConfig)
-    } catch (err: unknown) {
-      expect((err as Error).message).toBe('Cannot get upload server address\n没有配置上传地址')
-    }
-  })
-
-  test('it should throw can not get fileName error if not pass fileName option', () => {
-    try {
-      createUploader({
-        server: '/upload',
-        metaWithUrl: false,
-        onSuccess: (file, res) => {},
-        onFailed: (file, res) => {},
-        onError: (file, err, res) => {},
-      } as IUploadConfig)
-    } catch (err: unknown) {
-      expect((err as Error).message).toBe('Cannot get fieldName\n没有配置 fieldName')
-    }
-  })
-
-  test('it should invoke success callback if file be uploaded successfully', () => {
-    nock(server)
-      .defaultReplyHeaders({
-        'access-control-allow-method': 'POST',
-        'access-control-allow-origin': '*',
-      })
-      .options('/')
-      .reply(200, {})
-      .post('/')
-      .reply(200, {})
-
-    const fn = jest.fn()
-    const uppy = createUploader({
-      server,
-      fieldName: 'file1',
-      metaWithUrl: false,
-      onSuccess: fn,
-      onFailed: (file, res) => {},
-      onError: (file, err, res) => {},
-    })
-
-    // reference https://github.com/transloadit/uppy/blob/main/packages/%40uppy/xhr-upload/src/index.test.js
-    uppy.addFile({
-      source: 'jest',
-      name: 'foo.jpg',
-      type: 'image/jpeg',
-      data: new Blob([Buffer.alloc(8192)]),
-    })
-
-    return uppy.upload().then(() => {
-      expect(fn).toBeCalled()
-    })
-  })
-
-  test('it should invoke onProgress callback if file be uploaded successfully', () => {
-    nock(server)
-      .defaultReplyHeaders({
-        'access-control-allow-method': 'POST',
-        'access-control-allow-origin': '*',
-      })
-      .options('/')
-      .reply(200, {})
-      .post('/')
-      .reply(200, {})
-
-    const fn = jest.fn()
-    const uppy = createUploader({
-      server,
-      fieldName: 'file1',
-      metaWithUrl: false,
-      onSuccess: () => {},
-      onProgress: fn,
-      onFailed: (file, res) => {},
-      onError: (file, err, res) => {},
-    })
-
-    // reference https://github.com/transloadit/uppy/blob/main/packages/%40uppy/xhr-upload/src/index.test.js
-    uppy.addFile({
-      source: 'jest',
-      name: 'foo.jpg',
-      type: 'image/jpeg',
-      data: new Blob([Buffer.alloc(8192)]),
-    })
-
-    return uppy.upload().then(() => {
-      expect(fn).toBeCalled()
-    })
-  })
-
-  test('it should invoke error callback if file be uploaded failed', () => {
-    nock(server)
-      .defaultReplyHeaders({
-        'access-control-allow-method': 'POST',
-        'access-control-allow-origin': '*',
-      })
-      .options('/')
-      .reply(200, {})
-      .post('/')
-      .reply(400, {})
-
-    const fn = jest.fn()
-    const uppy = createUploader({
-      server,
-      fieldName: 'file1',
-      metaWithUrl: false,
-      onSuccess: () => {},
-      onFailed: (file, res) => {},
-      onError: fn,
-    })
-
-    // reference https://github.com/transloadit/uppy/blob/main/packages/%40uppy/xhr-upload/src/index.test.js
-    uppy.addFile({
-      source: 'jest',
-      name: 'foo.jpg',
-      type: 'image/jpeg',
-      data: new Blob([Buffer.alloc(8192)]),
-    })
-
-    return uppy.upload().catch(() => {
-      expect(fn).toBeCalled()
-    })
-  })
-
-  test('it should invoke console.error method if file be uploaded failed and not pass onError option', () => {
-    nock(server)
-      .defaultReplyHeaders({
-        'access-control-allow-method': 'POST',
-        'access-control-allow-origin': '*',
-      })
-      .options('/')
-      .reply(200, {})
-      .post('/')
-      .reply(400, {})
-
-    const fn = jest.fn()
-    console.error = fn
-    const uppy = createUploader({
-      server,
-      fieldName: 'file1',
-      metaWithUrl: false,
-      onSuccess: () => {},
-      onFailed: (file, res) => {},
-    } as any)
-
-    // reference https://github.com/transloadit/uppy/blob/main/packages/%40uppy/xhr-upload/src/index.test.js
-    uppy.addFile({
-      source: 'jest',
-      name: 'foo.jpg',
-      type: 'image/jpeg',
-      data: new Blob([Buffer.alloc(8192)]),
-    })
-
-    return uppy.upload().catch(() => {
-      expect(fn).toBeCalled()
-    })
-  })
-
-  test('it should invoke error callback if file size over max size', () => {
-    const fn = jest.fn()
-    const uppy = createUploader({
-      server,
-      fieldName: 'file1',
-      metaWithUrl: false,
-      onSuccess: () => {},
-      onFailed: (file, res) => {},
-      onError: fn,
-      maxFileSize: 5,
-    })
-
-    try {
-      uppy.addFile({
-        source: 'jest',
-        name: 'foo.jpg',
-        type: 'image/jpeg',
-        data: new Blob([Buffer.alloc(8192)]),
-      })
-    } catch (err) {
-      expect(fn).toBeCalled()
-    }
-  })
-})
diff --git a/packages/core/__tests__/utils/util.test.ts b/packages/core/__tests__/utils/util.test.ts
deleted file mode 100644
index 9fd193c0c..000000000
--- a/packages/core/__tests__/utils/util.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * @description util fns test
- * @author wangfupeng
- */
-
-import {
-  genRandomStr,
-  addQueryToUrl,
-  replaceHtmlSpecialSymbols,
-  deReplaceHtmlSpecialSymbols,
-} from '../../src/utils/util'
-
-describe('utils', () => {
-  it('gen random', () => {
-    const r1 = genRandomStr()
-    const r2 = genRandomStr()
-    expect(r1).not.toBe(r2)
-  })
-
-  it('add query to url', () => {
-    const params = { a: 10, b: 'hello' }
-
-    const url1 = 'https://wangeditor.com/'
-    expect(addQueryToUrl(url1, params)).toBe('https://wangeditor.com/?a=10&b=hello')
-
-    const url2 = 'https://wangeditor.com/?x=1#123'
-    expect(addQueryToUrl(url2, params)).toBe('https://wangeditor.com/?x=1&a=10&b=hello#123')
-  })
-
-  it('replace html symbol', () => {
-    const html = '<p>hello  world</p>'
-    const res = replaceHtmlSpecialSymbols(html)
-    expect(res).toBe('&lt;p&gt;hello &nbsp;world&lt;/p&gt;')
-  })
-
-  it('replace html symbol', () => {
-    const html = '&lt;p&gt;hello &nbsp;world&lt;/p&gt;'
-    const res = deReplaceHtmlSpecialSymbols(html)
-    expect(res).toBe('<p>hello  world</p>')
-  })
-
-  it('decode html quote symbol', () => {
-    const html = '<p style="font-family:&quot;Times New Roman&quot;;">hello world</p>'
-    const res = deReplaceHtmlSpecialSymbols(html)
-    expect(res).toBe('<p style="font-family:"Times New Roman";">hello world</p>')
-  })
-})
diff --git a/packages/core/__tests__/utils/vdom.test.ts b/packages/core/__tests__/utils/vdom.test.ts
deleted file mode 100644
index 75ac0d6db..000000000
--- a/packages/core/__tests__/utils/vdom.test.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * @description vdom util fns test
- * @author wangfupeng
- */
-
-import { h, VNode } from 'snabbdom'
-import {
-  normalizeVnodeData,
-  addVnodeProp,
-  addVnodeDataset,
-  addVnodeStyle,
-} from '../../src/utils/vdom'
-
-describe('vdom util fns', () => {
-  it('normalize vnode data', () => {
-    const vnode = h(
-      'div',
-      {
-        key: 'someKey',
-        id: 'div1',
-        className: 'someClassName',
-        'data-custom-name': 'someCustomName',
-      },
-      [
-        h(
-          'p',
-          {
-            id: 'p1',
-          },
-          ['hello']
-        ),
-      ]
-    )
-
-    normalizeVnodeData(vnode)
-
-    // 转换 div 自身
-    const { data = {}, children = [] } = vnode
-    expect(data.key).toBe('someKey')
-    const { props = {}, dataset = {} } = data
-    expect(props.id).toBe('div1')
-    expect(props.className).toBe('someClassName')
-    expect(dataset.customName).toBe('someCustomName')
-
-    // 转换 div 子节点 p
-    const pVNode = (children[0] || {}) as VNode
-    const { props: pProps = {} } = pVNode.data || {}
-    expect(pProps.id).toBe('p1')
-  })
-
-  it('add vnode props', () => {
-    const vnode = h('div', {})
-    addVnodeProp(vnode, { k1: 'v1' })
-
-    const { props = {} } = vnode.data || {}
-    expect(props.k1).toBe('v1')
-  })
-
-  it('add vnode dataset', () => {
-    const vnode = h('div', {})
-    addVnodeDataset(vnode, { k1: 'v1' })
-
-    const { dataset = {} } = vnode.data || {}
-    expect(dataset.k1).toBe('v1')
-  })
-
-  it('add vnode style', () => {
-    const vnode = h('div', {})
-    addVnodeStyle(vnode, { k1: 'v1' })
-
-    const { style = {} } = vnode.data || {}
-    expect(style.k1).toBe('v1')
-  })
-})
diff --git a/packages/core/package.json b/packages/core/package.json
deleted file mode 100644
index 9d1f07c47..000000000
--- a/packages/core/package.json
+++ /dev/null
@@ -1,70 +0,0 @@
-{
-  "name": "@wangeditor/core",
-  "version": "1.1.19",
-  "description": "wangEditor core",
-  "author": "wangfupeng1988 <wangfupeng1988@163.com>",
-  "contributors": [],
-  "homepage": "https://github.com/wangeditor-team/wangEditor#readme",
-  "license": "MIT",
-  "types": "dist/core/src/index.d.ts",
-  "main": "dist/index.js",
-  "module": "dist/index.esm.js",
-  "browser": {
-    "./dist/index.js": "./dist/index.js",
-    "./dist/index.esm.js": "./dist/index.esm.js"
-  },
-  "directories": {
-    "lib": "dist",
-    "test": "__tests__"
-  },
-  "files": [
-    "dist"
-  ],
-  "publishConfig": {
-    "access": "public",
-    "registry": "https://registry.npmjs.com/"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/wangeditor-team/wangEditor.git"
-  },
-  "scripts": {
-    "test": "jest",
-    "test-c": "jest --coverage",
-    "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js",
-    "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w",
-    "build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
-    "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js",
-    "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js"
-  },
-  "bugs": {
-    "url": "https://github.com/wangeditor-team/wangEditor/issues"
-  },
-  "peerDependencies": {
-    "@uppy/core": "^2.1.1",
-    "@uppy/xhr-upload": "^2.0.3",
-    "dom7": "^3.0.0",
-    "is-hotkey": "^0.2.0",
-    "lodash.camelcase": "^4.3.0",
-    "lodash.clonedeep": "^4.5.0",
-    "lodash.debounce": "^4.0.8",
-    "lodash.foreach": "^4.5.0",
-    "lodash.isequal": "^4.5.0",
-    "lodash.throttle": "^4.1.1",
-    "lodash.toarray": "^4.4.0",
-    "nanoid": "^3.2.0",
-    "slate": "^0.72.0",
-    "snabbdom": "^3.1.0"
-  },
-  "dependencies": {
-    "@types/event-emitter": "^0.3.3",
-    "event-emitter": "^0.3.5",
-    "html-void-elements": "^2.0.0",
-    "i18next": "^20.4.0",
-    "scroll-into-view-if-needed": "^2.2.28",
-    "slate-history": "^0.66.0"
-  },
-  "devDependencies": {
-    "@types/is-hotkey": "^0.1.2"
-  }
-}
diff --git a/packages/core/rollup.config.js b/packages/core/rollup.config.js
deleted file mode 100644
index 6cc2173be..000000000
--- a/packages/core/rollup.config.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
-import pkg from './package.json'
-
-const name = 'WangEditorCore'
-
-const configList = []
-
-// esm
-const esmConf = createRollupConfig({
-  output: {
-    file: pkg.module,
-    format: 'esm',
-    name,
-  },
-})
-configList.push(esmConf)
-
-// umd
-const umdConf = createRollupConfig({
-  output: {
-    file: pkg.main,
-    format: 'umd',
-    name,
-  },
-})
-configList.push(umdConf)
-
-export default configList
diff --git a/packages/core/src/assets/bar-item.less b/packages/core/src/assets/bar-item.less
deleted file mode 100644
index 20c425bfc..000000000
--- a/packages/core/src/assets/bar-item.less
+++ /dev/null
@@ -1,142 +0,0 @@
-@import "../../../vars.less"; // var and mixin
-
-.w-e-bar-divider {
-  display: inline-flex;
-  width: 1px;
-  height: @toolbar-height;
-  background-color: @toolbar-border-color; // 分割线 bgColor
-  margin: 0 5px;
-}
-
-.w-e-bar-item {
-  position: relative;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  text-align: center;
-  padding: 4px;
-  height: @toolbar-height;
-
-  button {
-    border: none;
-    background: transparent;
-    height: calc(@toolbar-height - 8px);
-    padding: 0 8px;
-    cursor: pointer;
-    display: inline-flex;
-    justify-content: center;
-    align-items: center;
-    color: @toolbar-color;
-    white-space: nowrap; /* 不换行 */
-    overflow: hidden;
-
-    &:hover {
-      background-color: @toolbar-active-bg-color;
-      color: @toolbar-active-color;
-    }
-
-    .title {
-      margin-left: 5px;
-    }
-  }
-
-  .active {
-    background-color: @toolbar-active-bg-color;
-    color: @toolbar-active-color;
-  }
-
-  .disabled {
-    color: @toolbar-disabled-color;
-    cursor: not-allowed;
-
-    svg {
-      fill: @toolbar-disabled-color;
-    }
-
-    &:hover {
-      background-color: @toolbar-bg-color;
-      color: @toolbar-disabled-color;
-  
-      svg {
-        fill: @toolbar-disabled-color;
-      }
-    }
-  }
-}
-
-// ------------------------------------- 分割线 -------------------------------------
-
-// tooltip - bottom
-.w-e-menu-tooltip-v5 {
-  &:before {
-    content: attr(data-tooltip);
-    position: absolute;
-    background-color: @toolbar-active-color; // tooltip 颜色反转,黑底白字
-    color: @toolbar-bg-color;  // tooltip 颜色反转,黑底白字
-    text-align: center;
-    padding: 5px 10px;
-    border-radius: 5px;
-    z-index: 1;
-    opacity: 0;
-    transition: opacity 0.6s;
-    font-size: 0.75em;
-    visibility: hidden;
-    top: @toolbar-height;
-    white-space: pre;
-  }
-  // arrow
-  &:after {
-    content: "";
-    position: absolute;
-    border-width: 5px;
-    border-style: solid;
-    opacity: 0;
-    transition: opacity 0.6s;
-    border-color:  transparent transparent @toolbar-active-color transparent;
-    visibility: hidden;
-    top: 30px;
-  }
-  &:hover:before,
-  &:hover:after {
-    opacity: 1;
-    visibility: visible;
-  }
-}
-// tooltip - right
-.w-e-menu-tooltip-v5.tooltip-right {
-  &:before {
-    left: 100%;
-    top: 10px;
-  }
-  // arrow
-  &:after {
-    left: 100%;
-    margin-left: -10px;
-    top: 16px;
-    border-color:  transparent @toolbar-active-color transparent transparent;
-  }
-}
-
-// ------------------------------------- 分割线 -------------------------------------
-
-// barItem group
-.w-e-bar-item-group {
-  .w-e-bar-item-menus-container {
-    display: none; /* 默认隐藏 */
-
-    z-index: 1;
-    background-color: @toolbar-bg-color;
-    position: absolute;
-    top: 0;
-    left: 0;
-    margin-top: @toolbar-height;
-    .shadowBordered(10px);
-  }
-  &:hover {
-    /* hover 时显示下级菜单 */
-    .w-e-bar-item-menus-container {
-      display: block;
-    }
-  }
-}
-
diff --git a/packages/core/src/assets/bar.less b/packages/core/src/assets/bar.less
deleted file mode 100644
index daee23548..000000000
--- a/packages/core/src/assets/bar.less
+++ /dev/null
@@ -1,30 +0,0 @@
-@import "../../../vars.less"; // var and mixin
-
-.w-e-bar {
-  background-color: @toolbar-bg-color;
-  padding: 0 5px;
-  font-size: @size;
-  color: @toolbar-color;
-
-  svg {
-    width: @size;
-    height: @size;
-    fill: @toolbar-color;
-  }
-}
-.w-e-bar-show {
-  display: flex;
-}
-.w-e-bar-hidden {
-  display: none;
-}
-
-.w-e-hover-bar {
-  position: absolute;
-  .shadowBordered();
-}
-
-.w-e-toolbar {
-  flex-wrap: wrap;
-  position: relative;
-}
diff --git a/packages/core/src/assets/common.less b/packages/core/src/assets/common.less
deleted file mode 100644
index 25f85dcbe..000000000
--- a/packages/core/src/assets/common.less
+++ /dev/null
@@ -1,17 +0,0 @@
-.w-e-text-container *,
-.w-e-toolbar * {
-  padding: 0;
-  margin: 0;
-  box-sizing: border-box;
-  outline: none;
-}
-
-.w-e-text-container {
-  p, li, td, th, blockquote {
-    line-height: 1.5;
-  }
-}
-
-.w-e-toolbar * {
-  line-height: 1.5;
-}
\ No newline at end of file
diff --git a/packages/core/src/assets/drop-panel.less b/packages/core/src/assets/drop-panel.less
deleted file mode 100644
index 37b878aa4..000000000
--- a/packages/core/src/assets/drop-panel.less
+++ /dev/null
@@ -1,20 +0,0 @@
-@import "../../../vars.less"; // var and mixin
-
-.w-e-drop-panel {
-  z-index: 1;
-  background-color: @toolbar-bg-color;
-  position: absolute;
-  top: 0;
-  .shadowBordered(10px);
-  margin-top: @toolbar-height;
-  min-width: 200px;
-  padding: 10px;
-}
-
-// 当 bar 处于页面下方,则 dropPanel 要显示在 bar 上方
-.w-e-bar-bottom .w-e-drop-panel {
-  top: inherit;
-  bottom: 0;
-  margin-top: 0;
-  margin-bottom: @toolbar-height;
-}
diff --git a/packages/core/src/assets/full-screen.less b/packages/core/src/assets/full-screen.less
deleted file mode 100644
index b3a28b985..000000000
--- a/packages/core/src/assets/full-screen.less
+++ /dev/null
@@ -1,20 +0,0 @@
-.w-e-full-screen-container {
-  position: fixed;
-  margin: 0 !important;
-  padding: 0 !important;
-  top: 0 !important;
-  left: 0 !important;
-  right: 0 !important;
-  bottom: 0 !important;
-  height: 100% !important;
-  width: 100% !important;
-  display: flex !important;
-  flex-direction: column !important;
-
-  // [data-w-e-toolbar="true"] {
-  // }
-
-  [data-w-e-textarea="true"] {
-    flex: 1 !important;
-  }
-}
diff --git a/packages/core/src/assets/index.less b/packages/core/src/assets/index.less
deleted file mode 100644
index c49f54100..000000000
--- a/packages/core/src/assets/index.less
+++ /dev/null
@@ -1,9 +0,0 @@
-@import "common.less";
-@import "textarea.less";
-@import "bar.less";
-@import "bar-item.less";
-@import "select-list.less";
-@import "drop-panel.less";
-@import "modal.less";
-@import "progress.less";
-@import "full-screen.less";
diff --git a/packages/core/src/assets/modal.less b/packages/core/src/assets/modal.less
deleted file mode 100644
index 17a1bc609..000000000
--- a/packages/core/src/assets/modal.less
+++ /dev/null
@@ -1,86 +0,0 @@
-@import "../../../vars.less"; // var and mixin
-
-.w-e-modal {
-  z-index: 1;
-  background-color: @toolbar-bg-color;
-  position: absolute;
-  padding: 20px 15px 0 15px;
-  min-width: 100px;
-  min-height: 40px;
-  color: @toolbar-color;
-  text-align: left;
-  font-size: @size;
-  .shadowBordered(10px);
-
-  .btn-close {
-    position: absolute;
-    right: 8px;
-    top: 7px;
-    cursor: pointer;
-    padding: 5px;
-    line-height: 1;
-
-    svg {
-      width: 10px;
-      height: 10px;
-      fill: @toolbar-color;
-    }
-  }
-
-  .babel-container {
-    display: block;
-    margin-bottom: 15px;
-
-    span {
-      display: block;
-      margin-bottom: 10px;
-    }
-  }
-
-  .button-container {
-    margin-bottom: 15px;
-  }
-
-  button {
-    font-weight: 400;
-    white-space: nowrap;
-    cursor: pointer;
-    transition: all .3s cubic-bezier(.645,.045,.355,1);
-    user-select: none;
-    touch-action: manipulation;
-    height: 32px;
-    padding: 4.5px 15px;
-    color: @toolbar-color;
-    background-color: @modal-button-bg-color;
-    text-align: center;
-    border: 1px solid @modal-button-border-color;
-    border-radius: 4px;
-  }
-
-  textarea,
-  input[type="text"],
-  input[type="number"] {
-    font-variant: tabular-nums;
-    font-feature-settings: "tnum";
-    padding: 4.5px 11px;
-    color: @toolbar-color;
-    background-color: @toolbar-bg-color;
-    border: 1px solid @modal-button-border-color;
-    border-radius: 4px;
-    transition: all .3s;
-    width: 100%;
-  }
-
-  textarea {
-    min-height: 60px;
-  }
-}
-
-// modal 有可能直接 append 到 <body> 下面
-body .w-e-modal {
-  box-sizing: border-box;
-
-  * {
-    box-sizing: border-box;
-  }
-}
diff --git a/packages/core/src/assets/progress.less b/packages/core/src/assets/progress.less
deleted file mode 100644
index 54c16025e..000000000
--- a/packages/core/src/assets/progress.less
+++ /dev/null
@@ -1,9 +0,0 @@
-@import "../../../vars.less";
-
-.w-e-progress-bar {
-  position: absolute;
-  width: 0;
-  height: 1px;
-  background-color: @textarea-handler-bg-color;
-  transition: width 0.3s;
-}
diff --git a/packages/core/src/assets/select-list.less b/packages/core/src/assets/select-list.less
deleted file mode 100644
index 1c14c088b..000000000
--- a/packages/core/src/assets/select-list.less
+++ /dev/null
@@ -1,52 +0,0 @@
-@import "../../../vars.less"; // var and mixin
-
-
-.w-e-select-list {
-  z-index: 1;
-  position: absolute;
-  left: 0;
-  top: 0;
-  background-color: @toolbar-bg-color;
-  margin-top: @toolbar-height;
-  min-width: 100px;
-  .shadowBordered(10px);
-
-  max-height: 350px;
-  overflow-y: auto;
-
-  ul {
-    list-style: none;
-    line-height: 1;
-
-    .selected {
-      background-color: @toolbar-active-bg-color;
-    }
-    li {
-      cursor: pointer;
-      padding: 7px 0 7px 25px;
-      position: relative;
-      text-align: left;
-      white-space: nowrap; /* 不换行 */
-
-      &:hover {
-        background-color: @toolbar-active-bg-color;
-      }
-
-      svg {
-        position: absolute;
-        left: 0;
-        margin-left: 5px;
-        top: 50%;
-        margin-top: -7px;
-      }
-    }
-  }
-}
-
-// 当 bar 处于页面下方,则 selectList 要显示在 bar 上方
-.w-e-bar-bottom .w-e-select-list {
-  top: inherit;
-  bottom: 0;
-  margin-top: 0;
-  margin-bottom: @toolbar-height;
-}
diff --git a/packages/core/src/assets/textarea.less b/packages/core/src/assets/textarea.less
deleted file mode 100644
index f232f500e..000000000
--- a/packages/core/src/assets/textarea.less
+++ /dev/null
@@ -1,67 +0,0 @@
-@import "../../../vars.less"; // var and mixin
-
-.w-e-text-container {
-  color: @textarea-color;
-  background-color: @textarea-bg-color;
-  position: relative;
-  height: 100%;
-}
-
-.w-e-text-container .w-e-scroll {
-  height: 100%;
-  // overflow-y: auto; // 在 js 中设置,根据 config 判断是否增加 scroll
-  -webkit-overflow-scrolling: touch;
-}
-
-.w-e-text-container [data-slate-editor] {
-  outline: 0;
-  white-space: pre-wrap; /* 【重要】可以显示空格,在连续多空格的情况下 */
-  word-wrap: break-word;
-  padding: 0 10px;
-  border-top: 1px solid transparent; // 防止 margin-top 溢出
-  min-height: 100%;
-
-  p {
-    margin: 15px 0;
-  }
-  h1,h2,h3,h4,h5 {
-    margin: 20px 0 20px 0;
-  }
-
-  img {
-    max-width: 100%;
-    min-width: 20px;
-    min-height: 20px;
-    cursor: default;
-    display: inline !important;
-  }
-
-  span {
-    text-indent: 0; // issues#4536
-  }
-
-  // 选中的节点
-  [data-selected="true"] {
-    box-shadow: 0 0 0 2px @textarea-selected-border-color;
-  }
-}
-
-.w-e-text-placeholder {
-  color: @textarea-slight-color;
-  position: absolute;
-  font-style: italic;
-  width: 90%;
-  left: 10px;
-  top: 17px;
-  pointer-events: none; // 忽略鼠标行为,重要
-  user-select: none;
-}
-
-.w-e-max-length-info {
-  position: absolute;
-  color: @textarea-slight-color;
-  bottom: 0.5em;
-  right: 1em;
-  pointer-events: none; // 忽略鼠标行为,重要
-  user-select: none;
-}
diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts
deleted file mode 100644
index a3324660f..000000000
--- a/packages/core/src/config/index.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * @description editor config
- * @author wangfupeng
- */
-
-import forEach from 'lodash.foreach'
-import cloneDeep from 'lodash.clonedeep'
-import { IEditorConfig, IMenuConfig, IToolbarConfig } from './interface'
-import { GLOBAL_MENU_CONF } from './register'
-
-/**
- * 生成编辑器默认配置
- */
-export function genEditorConfig(userConfig: Partial<IEditorConfig> = {}): IEditorConfig {
-  const defaultMenuConf = cloneDeep(GLOBAL_MENU_CONF)
-  const newMenuConf: IMenuConfig = {}
-
-  // 单独处理 menuConf
-  const { MENU_CONF: userMenuConf = {} } = userConfig
-  forEach(defaultMenuConf, (menuConf, menuKey) => {
-    // 生成新的 menu config
-    newMenuConf[menuKey] = {
-      ...menuConf,
-      ...(userMenuConf[menuKey] || {}),
-    }
-  })
-  delete userConfig.MENU_CONF // 处理完,则删掉 menuConf ,以防下面 merge 时造成干扰
-
-  return {
-    // 默认配置
-    scroll: true,
-    readOnly: false,
-    autoFocus: true,
-    decorate: () => [],
-    maxLength: 0, // 默认不限制
-    MENU_CONF: newMenuConf,
-    hoverbarKeys: {
-      // 'link': { menuKeys: ['editLink', 'unLink', 'viewLink'] },
-    },
-    customAlert(info: string, type: string) {
-      window.alert(`${type}:\n${info}`)
-    },
-
-    // 合并用户配置
-    ...userConfig,
-  }
-}
-
-/**
- * 生成 toolbar 默认配置
- */
-export function genToolbarConfig(userConfig?: Partial<IToolbarConfig>): IToolbarConfig {
-  return {
-    // 默认配置
-    toolbarKeys: [],
-    excludeKeys: [],
-    insertKeys: { index: 0, keys: [] },
-    modalAppendToBody: false,
-
-    // 合并用户配置
-    ...(userConfig || {}),
-  }
-}
diff --git a/packages/core/src/config/interface.ts b/packages/core/src/config/interface.ts
deleted file mode 100644
index 3c7178982..000000000
--- a/packages/core/src/config/interface.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * @description config interface
- * @author wangfupeng
- */
-
-import { Range, NodeEntry, Node } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import { IMenuGroup } from '../menus/interface'
-
-interface IHoverbarConf {
-  // key 即 element type
-  [key: string]: {
-    match?: (editor: IDomEditor, n: Node) => boolean // 自定义匹配函数,优先级高于“key 即 element type”
-    menuKeys: string[]
-  }
-}
-
-export type AlertType = 'success' | 'info' | 'warning' | 'error'
-
-export interface ISingleMenuConfig {
-  [key: string]: any
-}
-
-export interface IMenuConfig {
-  [key: string]: ISingleMenuConfig
-}
-
-/**
- * editor config
- */
-export interface IEditorConfig {
-  //【注意】如增加 onXxx 回调函数时,要同步到 vue2/vue3 组件
-  customAlert: (info: string, type: AlertType) => void
-
-  onCreated?: (editor: IDomEditor) => void
-  onChange?: (editor: IDomEditor) => void
-  onDestroyed?: (editor: IDomEditor) => void
-
-  onMaxLength?: (editor: IDomEditor) => void
-  onFocus?: (editor: IDomEditor) => void
-  onBlur?: (editor: IDomEditor) => void
-
-  /**
-   * 自定义粘贴。返回 true 则继续粘贴,返回 false 则自行实现粘贴,阻止默认粘贴
-   */
-  customPaste?: (editor: IDomEditor, e: ClipboardEvent) => boolean
-
-  // edit state
-  scroll: boolean
-  placeholder?: string
-  readOnly: boolean
-  autoFocus: boolean
-  decorate?: (nodeEntry: NodeEntry) => Range[]
-  maxLength?: number
-
-  // 各个 menu 的配置汇总,可以通过 key 获取单个 menu 的配置
-  MENU_CONF?: IMenuConfig
-
-  // 悬浮菜单栏 menu
-  hoverbarKeys?: IHoverbarConf
-
-  // 自由扩展其他配置
-  EXTEND_CONF?: any
-}
-
-/**
- * toolbar config
- */
-export interface IToolbarConfig {
-  toolbarKeys: Array<string | IMenuGroup>
-  insertKeys: { index: number; keys: string | Array<string | IMenuGroup> }
-  excludeKeys: Array<string> // 排除哪些菜单
-  modalAppendToBody: boolean // modal append 到 body ,而非 $textAreaContainer 内
-}
diff --git a/packages/core/src/config/register.ts b/packages/core/src/config/register.ts
deleted file mode 100644
index 25c20b0e8..000000000
--- a/packages/core/src/config/register.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @description config register
- * @author wangfupeng
- */
-
-import { IMenuConfig, ISingleMenuConfig } from '../config/interface'
-
-// 全局的菜单配置
-export const GLOBAL_MENU_CONF: IMenuConfig = {}
-
-/**
- * 注册全局菜单配置
- * @param key menu key
- * @param config config
- */
-export function registerGlobalMenuConf(key: string, config?: ISingleMenuConfig) {
-  if (config == null) return
-  GLOBAL_MENU_CONF[key] = config
-}
diff --git a/packages/core/src/constants/index.ts b/packages/core/src/constants/index.ts
deleted file mode 100644
index cee25f9c5..000000000
--- a/packages/core/src/constants/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export const IGNORE_TAGS = new Set([
-  'doctype',
-  '!doctype',
-  'meta',
-  'script',
-  'style',
-  'link',
-  'frame',
-  'iframe',
-  'title',
-  'svg', // TODO 暂时忽略
-])
diff --git a/packages/core/src/constants/svg.ts b/packages/core/src/constants/svg.ts
deleted file mode 100644
index a5dcf531f..000000000
--- a/packages/core/src/constants/svg.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @description svg tag
- * @author wangfupeng
- */
-
-/**
- * 【注意】svg 字符串的长度 ,否则会导致代码体积过大
- * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293
- * 找不到再从 iconfont.com 搜索
- */
-
-// 对号
-export const SVG_CHECK_MARK =
-  '<svg viewBox="0 0 1446 1024"><path d="M574.116299 786.736392 1238.811249 48.517862C1272.390222 11.224635 1329.414799 7.827718 1366.75664 41.450462 1403.840015 74.840484 1406.731043 132.084741 1373.10189 169.433699L655.118888 966.834607C653.072421 969.716875 650.835807 972.514337 648.407938 975.210759 615.017957 1012.29409 558.292155 1015.652019 521.195664 982.250188L72.778218 578.493306C35.910826 545.297758 32.859041 488.584019 66.481825 451.242134 99.871807 414.158803 156.597563 410.800834 193.694055 444.202665L574.116299 786.736392Z"></path></svg>'
-
-// 向下的箭头
-export const SVG_DOWN_ARROW =
-  '<svg viewBox="0 0 1024 1024"><path d="M498.7 655.8l-197.6-268c-8.1-10.9-0.3-26.4 13.3-26.4h395.2c13.6 0 21.4 15.4 13.3 26.4l-197.6 268c-6.6 9-20 9-26.6 0z"></path></svg>'
-
-// 关闭
-export const SVG_CLOSE =
-  '<svg viewBox="0 0 1024 1024"><path d="M1024 896.1024l-128 128L512 640 128 1024 0 896 384 512 0 128 128 0 512 384 896.1024 0l128 128L640 512z"></path></svg>'
diff --git a/packages/core/src/create/bind-node-relation.ts b/packages/core/src/create/bind-node-relation.ts
deleted file mode 100644
index e5f012d83..000000000
--- a/packages/core/src/create/bind-node-relation.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @description 绑定 node 的关系
- * @author wangfupeng
- */
-
-import { Element, Editor, Node, Ancestor } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'
-
-/**
- * createEditor 未传递 selector 时,绑定 node 的关系( NODE_TO_PARENT, NODE_TO_INDEX 等 )
- * @param node node
- * @param index index
- * @param parent parent node
- * @param editor editor
- */
-function bindNodeRelation(node: Node, index: number, parent: Ancestor, editor: IDomEditor) {
-  // 设置相关 weakMap 信息
-  NODE_TO_INDEX.set(node, index)
-  NODE_TO_PARENT.set(node, parent)
-
-  if (Element.isElement(node)) {
-    const { children = [] } = node
-    children.forEach((child: Node, i: number) => bindNodeRelation(child, i, node, editor)) // 递归子节点
-
-    const isVoid = Editor.isVoid(editor, node)
-    if (isVoid) {
-      const [[text]] = Node.texts(node)
-      // 记录 text 相关 weakMap
-      NODE_TO_INDEX.set(text, 0)
-      NODE_TO_PARENT.set(text, node)
-    }
-  }
-}
-
-export default bindNodeRelation
diff --git a/packages/core/src/create/create-editor.ts b/packages/core/src/create/create-editor.ts
deleted file mode 100644
index 30fa80f58..000000000
--- a/packages/core/src/create/create-editor.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-/**
- * @description create editor
- * @author wangfupeng
- */
-
-import { createEditor, Descendant } from 'slate'
-import { withHistory } from 'slate-history'
-import { withDOM } from '../editor/plugins/with-dom'
-import { withConfig } from '../editor/plugins/with-config'
-import { withContent } from '../editor/plugins/with-content'
-import { withEventData } from '../editor/plugins/with-event-data'
-import { withEmitter } from '../editor/plugins/with-emitter'
-import { withSelection } from '../editor/plugins/with-selection'
-import { withMaxLength } from '../editor/plugins/with-max-length'
-import TextArea from '../text-area/TextArea'
-import HoverBar from '../menus/bar/HoverBar'
-import { genEditorConfig } from '../config/index'
-import { IDomEditor } from '../editor/interface'
-import { DomEditor } from '../editor/dom-editor'
-import { IEditorConfig } from '../config/interface'
-import { promiseResolveThen } from '../utils/util'
-import { isRepeatedCreateTextarea, genDefaultContent, htmlToContent } from './helper'
-import type { DOMElement } from '../utils/dom'
-import {
-  EDITOR_TO_TEXTAREA,
-  TEXTAREA_TO_EDITOR,
-  EDITOR_TO_CONFIG,
-  HOVER_BAR_TO_EDITOR,
-  EDITOR_TO_HOVER_BAR,
-} from '../utils/weak-maps'
-import bindNodeRelation from './bind-node-relation'
-import $ from '../utils/dom'
-
-type PluginFnType = <T extends IDomEditor>(editor: T) => T
-
-interface ICreateOption {
-  selector: string | DOMElement
-  config: Partial<IEditorConfig>
-  content?: Descendant[]
-  html?: string
-  plugins: PluginFnType[]
-}
-
-/**
- * 创建编辑器
- */
-export default function (option: Partial<ICreateOption>) {
-  const { selector = '', config = {}, content, html, plugins = [] } = option
-
-  // 创建实例 - 使用插件
-  let editor = withHistory(
-    withMaxLength(
-      withEmitter(withSelection(withContent(withConfig(withDOM(withEventData(createEditor()))))))
-    )
-  )
-  if (selector) {
-    // 检查是否对同一个 DOM 重复创建
-    if (isRepeatedCreateTextarea(editor, selector)) {
-      throw new Error(`Repeated create editor by selector '${selector}'`)
-    }
-  }
-
-  // 处理配置
-  const editorConfig = genEditorConfig(config)
-  EDITOR_TO_CONFIG.set(editor, editorConfig)
-  const { hoverbarKeys = {} } = editorConfig
-
-  // 注册第三方插件
-  plugins.forEach(plugin => {
-    editor = plugin(editor)
-  })
-
-  // 初始化内容(要在 config 和 plugins 后面)
-  if (html != null) {
-    // 传入 html ,转换为 JSON content
-    editor.children = htmlToContent(editor, html)
-  }
-  if (content && content.length) {
-    editor.children = content // 传入 JSON content
-  }
-  if (editor.children.length === 0) {
-    editor.children = genDefaultContent() // 默认内容
-  }
-  DomEditor.normalizeContent(editor) // 格式化,用户输入的 content 可能不规范(如两个相连的 text 没有合并)
-
-  if (selector) {
-    // 传入了 selector ,则创建 textarea DOM
-    const textarea = new TextArea(selector)
-    EDITOR_TO_TEXTAREA.set(editor, textarea)
-    TEXTAREA_TO_EDITOR.set(textarea, editor)
-    textarea.changeViewState() // 初始化时触发一次,以便能初始化 textarea DOM 和 selection
-
-    // 判断 textarea 最小高度,并给出提示
-    promiseResolveThen(() => {
-      const $scroll = textarea.$scroll
-      if ($scroll == null) return
-      if ($scroll.height() < 300) {
-        let info = '编辑区域高度 < 300px 这可能会导致 modal hoverbar 定位异常'
-        info += '\nTextarea height < 300px . This may be cause modal and hoverbar position error'
-        console.warn(info, $scroll)
-      }
-    })
-
-    // 创建 hoverbar DOM
-    let hoverbar: HoverBar | null
-    if (Object.keys(hoverbarKeys).length > 0) {
-      hoverbar = new HoverBar()
-      HOVER_BAR_TO_EDITOR.set(hoverbar, editor)
-      EDITOR_TO_HOVER_BAR.set(editor, hoverbar)
-    }
-
-    // 隐藏 panel and modal
-    editor.on('change', () => {
-      editor.hidePanelOrModal()
-    })
-    editor.on('scroll', () => {
-      editor.hidePanelOrModal()
-    })
-  } else {
-    // 未传入 selector ,则遍历 content ,绑定一些 WeakMap 关系 ( NODE_TO_PARENT, NODE_TO_INDEX 等 )
-    editor.children.forEach((node, i) => bindNodeRelation(node, i, editor, editor))
-  }
-
-  // 触发生命周期
-  const { onCreated, onDestroyed } = editorConfig
-  if (onCreated) {
-    editor.on('created', () => onCreated(editor))
-  }
-  if (onDestroyed) {
-    editor.on('destroyed', () => onDestroyed(editor))
-  }
-
-  // 创建完毕,异步触发 created
-  promiseResolveThen(() => editor.emit('created'))
-
-  return editor
-}
diff --git a/packages/core/src/create/create-toolbar.ts b/packages/core/src/create/create-toolbar.ts
deleted file mode 100644
index adcd64154..000000000
--- a/packages/core/src/create/create-toolbar.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @description create toolbar
- * @author wangfupeng
- */
-
-import { IDomEditor } from '../editor/interface'
-import Toolbar from '../menus/bar/Toolbar'
-import { IToolbarConfig } from '../config/interface'
-import { genToolbarConfig } from '../config/index'
-import { isRepeatedCreateToolbar } from './helper'
-import { DOMElement } from '../utils/dom'
-import { TOOLBAR_TO_EDITOR, EDITOR_TO_TOOLBAR } from '../utils/weak-maps'
-
-interface ICreateOption {
-  selector: string | DOMElement
-  config?: Partial<IToolbarConfig>
-}
-
-export default function (editor: IDomEditor | null, option: ICreateOption): Toolbar {
-  if (editor == null) {
-    throw new Error(`Cannot create toolbar, because editor is null`)
-  }
-  const { selector, config = {} } = option
-
-  // 避免重复创建
-  if (isRepeatedCreateToolbar(editor, selector)) {
-    // 对同一个 DOM 重复创建
-    throw new Error(`Repeated create toolbar by selector '${selector}'`)
-  }
-
-  // 处理配置
-  const toolbarConfig = genToolbarConfig(config)
-
-  // 创建 toolbar ,并记录和 editor 关系
-  const toolbar = new Toolbar(selector, toolbarConfig)
-  TOOLBAR_TO_EDITOR.set(toolbar, editor)
-  EDITOR_TO_TOOLBAR.set(editor, toolbar)
-
-  return toolbar
-}
diff --git a/packages/core/src/create/helper.ts b/packages/core/src/create/helper.ts
deleted file mode 100644
index 1438a7584..000000000
--- a/packages/core/src/create/helper.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * @description create helper
- * @author wangfupeng
- */
-
-import { Descendant } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import parseElemHtml from '../parse-html/parse-elem-html'
-import $, { DOMElement } from '../utils/dom'
-
-function isRepeatedCreate(
-  editor: IDomEditor,
-  attrKey: string,
-  selector: string | DOMElement
-): boolean {
-  // @ts-ignore
-  const $elem = $(selector)
-  if ($elem.attr(attrKey)) {
-    return true // 有属性,说明已经创建过
-  }
-
-  // 至此,说明未创建过,则记录
-  $elem.attr(attrKey, 'true')
-
-  // 销毁时删除属性
-  editor.on('destroyed', () => {
-    $elem.removeAttr(attrKey)
-  })
-
-  return false
-}
-
-/**
- * 检查是否重复创建 textarea
- */
-export function isRepeatedCreateTextarea(
-  editor: IDomEditor,
-  selector: string | DOMElement
-): boolean {
-  return isRepeatedCreate(editor, 'data-w-e-textarea', selector)
-}
-
-/**
- * 检查是否重复创建 toolbar
- */
-export function isRepeatedCreateToolbar(
-  editor: IDomEditor,
-  selector: string | DOMElement
-): boolean {
-  return isRepeatedCreate(editor, 'data-w-e-toolbar', selector)
-}
-
-/**
- * 生成默认 content
- */
-export function genDefaultContent() {
-  return [
-    {
-      type: 'paragraph',
-      children: [{ text: '' }],
-    },
-  ]
-}
-
-/**
- * html 字符串 -> content
- * @param editor editor
- * @param html html 字符串
- */
-export function htmlToContent(editor: IDomEditor, html: string = ''): Descendant[] {
-  const res: Descendant[] = []
-
-  // 空白内容
-  if (html === '') html = '<p><br></p>'
-
-  // 非 HTML 格式,文本格式,用 <p> 包裹
-  if (html.indexOf('<') !== 0) {
-    html = html
-      .split(/\n/)
-      .map(line => `<p>${line}</p>`)
-      .join('')
-  }
-
-  const $content = $(`<div>${html}</div>`)
-  const list = Array.from($content.children())
-  list.forEach(child => {
-    const $child = $(child)
-    const parsedRes = parseElemHtml($child, editor)
-
-    if (Array.isArray(parsedRes)) {
-      parsedRes.forEach(el => res.push(el))
-    } else {
-      res.push(parsedRes)
-    }
-  })
-
-  return res
-}
diff --git a/packages/core/src/create/index.ts b/packages/core/src/create/index.ts
deleted file mode 100644
index 27068dfe9..000000000
--- a/packages/core/src/create/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * @description create entry
- * @author wangfupeng
- */
-
-import coreCreateEditor from './create-editor'
-import coreCreateToolbar from './create-toolbar'
-
-export { coreCreateEditor, coreCreateToolbar }
diff --git a/packages/core/src/editor/dom-editor.ts b/packages/core/src/editor/dom-editor.ts
deleted file mode 100644
index f7a32f80e..000000000
--- a/packages/core/src/editor/dom-editor.ts
+++ /dev/null
@@ -1,817 +0,0 @@
-/**
- * @description 扩展 slate Editor(参考 slate-react react-editor.ts )
- * @author wangfupeng
- */
-
-import toArray from 'lodash.toarray'
-import { Editor, Node, Element, Path, Point, Range, Ancestor, Text } from 'slate'
-import type { IDomEditor } from './interface'
-import { Key } from '../utils/key'
-import TextArea from '../text-area/TextArea'
-import Toolbar from '../menus/bar/Toolbar'
-import HoverBar from '../menus/bar/HoverBar'
-import {
-  EDITOR_TO_ELEMENT,
-  ELEMENT_TO_NODE,
-  KEY_TO_ELEMENT,
-  NODE_TO_INDEX,
-  NODE_TO_KEY,
-  NODE_TO_PARENT,
-  EDITOR_TO_TEXTAREA,
-  EDITOR_TO_TOOLBAR,
-  EDITOR_TO_HOVER_BAR,
-  EDITOR_TO_WINDOW,
-} from '../utils/weak-maps'
-import $, {
-  DOMElement,
-  DOMNode,
-  DOMPoint,
-  DOMRange,
-  DOMSelection,
-  DOMStaticRange,
-  isDOMElement,
-  normalizeDOMPoint,
-  isDOMSelection,
-  hasShadowRoot,
-  walkTextNodes,
-} from '../utils/dom'
-import { IS_CHROME, IS_FIREFOX } from '../utils/ua'
-
-/**
- * 自定义全局 command
- */
-export const DomEditor = {
-  /**
-   * Return the host window of the current editor.
-   */
-  getWindow(editor: IDomEditor): Window {
-    const window = EDITOR_TO_WINDOW.get(editor)
-    if (!window) {
-      throw new Error('Unable to find a host window element for this editor')
-    }
-    return window
-  },
-
-  /**
-   * Find a key for a Slate node.
-   * key 即一个累加不重复的 id ,每一个 slate node 都对对应一个 key ,意思相当于 node.id
-   */
-  findKey(editor: IDomEditor | null, node: Node): Key {
-    let key = NODE_TO_KEY.get(node)
-
-    // 如果没绑定,就立马新建一个 key 来绑定
-    if (!key) {
-      key = new Key()
-      NODE_TO_KEY.set(node, key)
-    }
-
-    return key
-  },
-
-  setNewKey(node: Node) {
-    const key = new Key()
-    NODE_TO_KEY.set(node, key)
-  },
-
-  /**
-   * Find the path of Slate node.
-   * path 是一个数组,代表 slate node 的位置 https://docs.slatejs.org/concepts/03-locations#path
-   */
-  findPath(editor: IDomEditor | null, node: Node): Path {
-    const path: Path = []
-    let child = node
-
-    // eslint-disable-next-line
-    while (true) {
-      const parent = NODE_TO_PARENT.get(child)
-
-      if (parent == null) {
-        if (Editor.isEditor(child)) {
-          // 已到达最顶层,返回 patch
-          return path
-        } else {
-          break
-        }
-      }
-
-      // 获取该节点在父节点中的 index
-      const i = NODE_TO_INDEX.get(child)
-
-      if (i == null) {
-        break
-      }
-
-      // 拼接 patch
-      path.unshift(i)
-
-      // 继续向上递归
-      child = parent
-    }
-
-    throw new Error(`Unable to find the path for Slate node: ${JSON.stringify(node)}`)
-  },
-
-  /**
-   * Find the DOM node that implements DocumentOrShadowRoot for the editor.
-   */
-  findDocumentOrShadowRoot(editor: IDomEditor): Document | ShadowRoot {
-    if (editor.isDestroyed) {
-      return window.document
-    }
-
-    const el = DomEditor.toDOMNode(editor, editor)
-    const root = el.getRootNode()
-
-    if ((root instanceof Document || root instanceof ShadowRoot) && root.getSelection != null) {
-      return root
-    }
-    return el.ownerDocument
-  },
-
-  /**
-   * 获取父节点
-   * @param editor editor
-   * @param node cur node
-   */
-  getParentNode(editor: IDomEditor | null, node: Node): Ancestor | null {
-    return NODE_TO_PARENT.get(node) || null
-  },
-
-  /**
-   * 获取当前节点的所有父节点
-   * @param editor editor
-   * @param node cur node
-   */
-  getParentsNodes(editor: IDomEditor, node: Node): Ancestor[] {
-    const nodes: Ancestor[] = []
-    let curNode = node
-    while (curNode !== editor && curNode != null) {
-      const parentNode = DomEditor.getParentNode(editor, curNode)
-      if (parentNode == null) {
-        break
-      } else {
-        nodes.push(parentNode)
-        curNode = parentNode
-      }
-    }
-    return nodes
-  },
-
-  /**
-   * 获取当前节点对应的顶级节点
-   * @param editor editor
-   * @param curNode cur node
-   */
-  getTopNode(editor: IDomEditor, curNode: Node): Node {
-    const path = DomEditor.findPath(editor, curNode)
-    const topPath = [path[0]]
-    return Node.get(editor, topPath)
-  },
-
-  /**
-   * Find the native DOM element from a Slate node or editor.
-   */
-  toDOMNode(editor: IDomEditor, node: Node): HTMLElement {
-    let domNode
-    const isEditor = Editor.isEditor(node)
-    if (isEditor) {
-      domNode = EDITOR_TO_ELEMENT.get(editor)
-    } else {
-      const key = DomEditor.findKey(editor, node)
-      domNode = KEY_TO_ELEMENT.get(key)
-    }
-
-    if (!domNode) {
-      throw new Error(`Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`)
-    }
-
-    return domNode
-  },
-
-  /**
-   * Check if a DOM node is within the editor.
-   */
-  hasDOMNode(editor: IDomEditor, target: DOMNode, options: { editable?: boolean } = {}): boolean {
-    const { editable = false } = options
-    const editorEl = DomEditor.toDOMNode(editor, editor)
-    let targetEl
-
-    // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
-    // target is originating from an internal "restricted" element (e.g. a
-    // stepper arrow on a number input). (2018/05/04)
-    // https://github.com/ianstormtaylor/slate/issues/1819
-    try {
-      targetEl = (isDOMElement(target) ? target : target.parentElement) as HTMLElement
-    } catch (err) {
-      if (!err.message.includes('Permission denied to access property "nodeType"')) {
-        throw err
-      }
-    }
-
-    if (!targetEl) {
-      return false
-    }
-
-    return (
-      // 祖先节点中包括 data-slate-editor 属性,即 textarea
-      targetEl.closest(`[data-slate-editor]`) === editorEl &&
-      // 通过参数 editable 控制开启是否验证是可编辑元素或零宽字符
-      (!editable || targetEl.isContentEditable || !!targetEl.getAttribute('data-slate-zero-width'))
-    )
-  },
-
-  /**
-   * Find a native DOM range from a Slate `range`.
-   *
-   * Notice: the returned range will always be ordinal regardless of the direction of Slate `range` due to DOM API limit.
-   *
-   * there is no way to create a reverse DOM Range using Range.setStart/setEnd
-   * according to https://dom.spec.whatwg.org/#concept-range-bp-set.
-   */
-  toDOMRange(editor: IDomEditor, range: Range): DOMRange {
-    const { anchor, focus } = range
-    const isBackward = Range.isBackward(range)
-    const domAnchor = DomEditor.toDOMPoint(editor, anchor)
-    const domFocus = Range.isCollapsed(range) ? domAnchor : DomEditor.toDOMPoint(editor, focus)
-
-    const window = DomEditor.getWindow(editor)
-    const domRange = window.document.createRange()
-    const [startNode, startOffset] = isBackward ? domFocus : domAnchor
-    const [endNode, endOffset] = isBackward ? domAnchor : domFocus
-
-    // A slate Point at zero-width Leaf always has an offset of 0 but a native DOM selection at
-    // zero-width node has an offset of 1 so we have to check if we are in a zero-width node and
-    // adjust the offset accordingly.
-    const startEl = (isDOMElement(startNode) ? startNode : startNode.parentElement) as HTMLElement
-    const isStartAtZeroWidth = !!startEl.getAttribute('data-slate-zero-width')
-    const endEl = (isDOMElement(endNode) ? endNode : endNode.parentElement) as HTMLElement
-    const isEndAtZeroWidth = !!endEl.getAttribute('data-slate-zero-width')
-
-    domRange.setStart(startNode, isStartAtZeroWidth ? 1 : startOffset)
-    domRange.setEnd(endNode, isEndAtZeroWidth ? 1 : endOffset)
-    return domRange
-  },
-
-  /**
-   * Find a native DOM selection point from a Slate point.
-   */
-  toDOMPoint(editor: IDomEditor, point: Point): DOMPoint {
-    const [node] = Editor.node(editor, point.path)
-    const el = DomEditor.toDOMNode(editor, node)
-    let domPoint: DOMPoint | undefined
-
-    // If we're inside a void node, force the offset to 0, otherwise the zero
-    // width spacing character will result in an incorrect offset of 1
-    if (Editor.void(editor, { at: point })) {
-      // void 节点,offset 必须为 0
-      point = { path: point.path, offset: 0 }
-    }
-
-    // For each leaf, we need to isolate its content, which means filtering
-    // to its direct text and zero-width spans. (We have to filter out any
-    // other siblings that may have been rendered alongside them.)
-    const selector = `[data-slate-string], [data-slate-zero-width]`
-    const texts = Array.from(el.querySelectorAll(selector))
-    let start = 0
-
-    for (const text of texts) {
-      const domNode = text.childNodes[0] as HTMLElement
-
-      if (domNode == null || domNode.textContent == null) {
-        continue
-      }
-
-      const { length } = domNode.textContent
-      const attr = text.getAttribute('data-slate-length')
-      const trueLength = attr == null ? length : parseInt(attr, 10)
-      const end = start + trueLength
-
-      if (point.offset <= end) {
-        const offset = Math.min(length, Math.max(0, point.offset - start))
-        domPoint = [domNode, offset]
-        break
-      }
-
-      start = end
-    }
-
-    if (!domPoint) {
-      throw new Error(`Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`)
-    }
-
-    return domPoint
-  },
-
-  /**
-   * Find a Slate node from a native DOM `element`.
-   */
-  toSlateNode(editor: IDomEditor | null, domNode: DOMNode): Node {
-    let domEl = isDOMElement(domNode) ? domNode : domNode.parentElement
-
-    if (domEl && !domEl.hasAttribute('data-slate-node')) {
-      domEl = domEl.closest(`[data-slate-node]`)
-    }
-
-    const node = domEl ? ELEMENT_TO_NODE.get(domEl as HTMLElement) : null
-
-    if (!node) {
-      throw new Error(`Cannot resolve a Slate node from DOM node: ${domEl}`)
-    }
-
-    return node
-  },
-
-  /**
-   * Get the target range from a DOM `event`.
-   */
-  findEventRange(editor: IDomEditor, event: any): Range {
-    if ('nativeEvent' in event) {
-      // 兼容 react 的合成事件,DOM 事件中没什么用
-      event = event.nativeEvent
-    }
-
-    const { clientX: x, clientY: y, target } = event
-
-    if (x == null || y == null) {
-      throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)
-    }
-
-    const node = DomEditor.toSlateNode(editor, event.target)
-    const path = DomEditor.findPath(editor, node)
-
-    // If the drop target is inside a void node, move it into either the
-    // next or previous node, depending on which side the `x` and `y`
-    // coordinates are closest to.
-    if (Editor.isVoid(editor, node)) {
-      const rect = target.getBoundingClientRect()
-      const isPrev = editor.isInline(node)
-        ? x - rect.left < rect.left + rect.width - x
-        : y - rect.top < rect.top + rect.height - y
-
-      const edge = Editor.point(editor, path, {
-        edge: isPrev ? 'start' : 'end',
-      })
-      const point = isPrev ? Editor.before(editor, edge) : Editor.after(editor, edge)
-
-      if (point) {
-        const range = Editor.range(editor, point)
-        return range
-      }
-    }
-
-    // Else resolve a range from the caret position where the drop occured.
-    let domRange
-    const { document } = this.getWindow(editor)
-
-    // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
-    if (document.caretRangeFromPoint) {
-      domRange = document.caretRangeFromPoint(x, y)
-    } else {
-      const position = document.caretPositionFromPoint(x, y)
-      if (position) {
-        domRange = document.createRange()
-        domRange.setStart(position.offsetNode, position.offset)
-        domRange.setEnd(position.offsetNode, position.offset)
-      }
-    }
-
-    if (!domRange) {
-      throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`)
-    }
-
-    // Resolve a Slate range from the DOM range.
-    const range = DomEditor.toSlateRange(editor, domRange, {
-      exactMatch: false,
-      suppressThrow: false,
-    })
-    return range
-  },
-
-  /**
-   * Find a Slate range from a DOM range or selection.
-   */
-  toSlateRange<T extends boolean>(
-    editor: IDomEditor,
-    domRange: DOMRange | DOMStaticRange | DOMSelection,
-    options: {
-      exactMatch: T
-      suppressThrow: T
-    }
-  ): T extends true ? Range | null : Range {
-    const { exactMatch, suppressThrow } = options
-    const el = isDOMSelection(domRange) ? domRange.anchorNode : domRange.startContainer
-    let anchorNode
-    let anchorOffset
-    let focusNode
-    let focusOffset
-    let isCollapsed
-
-    if (el) {
-      if (isDOMSelection(domRange)) {
-        anchorNode = domRange.anchorNode
-        anchorOffset = domRange.anchorOffset
-        focusNode = domRange.focusNode
-        focusOffset = domRange.focusOffset
-        // COMPAT: There's a bug in chrome that always returns `true` for
-        // `isCollapsed` for a Selection that comes from a ShadowRoot.
-        // (2020/08/08)
-        // https://bugs.chromium.org/p/chromium/issues/detail?id=447523
-        if (IS_CHROME && hasShadowRoot()) {
-          isCollapsed =
-            domRange.anchorNode === domRange.focusNode &&
-            domRange.anchorOffset === domRange.focusOffset
-        } else {
-          isCollapsed = domRange.isCollapsed
-        }
-      } else {
-        anchorNode = domRange.startContainer
-        anchorOffset = domRange.startOffset
-        focusNode = domRange.endContainer
-        focusOffset = domRange.endOffset
-        isCollapsed = domRange.collapsed
-      }
-    }
-
-    if (anchorNode == null || focusNode == null || anchorOffset == null || focusOffset == null) {
-      throw new Error(`Cannot resolve a Slate range from DOM range: ${domRange}`)
-    }
-
-    const anchor = DomEditor.toSlatePoint(editor, [anchorNode, anchorOffset], {
-      exactMatch,
-      suppressThrow,
-    })
-    if (!anchor) {
-      return null as T extends true ? Range | null : Range
-    }
-
-    const focus = isCollapsed
-      ? anchor
-      : DomEditor.toSlatePoint(editor, [focusNode, focusOffset], { exactMatch, suppressThrow })
-    if (!focus) {
-      return null as T extends true ? Range | null : Range
-    }
-
-    // return { anchor, focus } as unknown as T extends true ? Range | null : Range
-
-    let range: Range = { anchor: anchor as Point, focus: focus as Point }
-    // if the selection is a hanging range that ends in a void
-    // and the DOM focus is an Element
-    // (meaning that the selection ends before the element)
-    // unhang the range to avoid mistakenly including the void
-    if (
-      Range.isExpanded(range) &&
-      Range.isForward(range) &&
-      isDOMElement(focusNode) &&
-      Editor.void(editor, { at: range.focus, mode: 'highest' })
-    ) {
-      range = Editor.unhangRange(editor, range, { voids: true })
-    }
-
-    return range as unknown as T extends true ? Range | null : Range
-  },
-
-  /**
-   * Find a Slate point from a DOM selection's `domNode` and `domOffset`.
-   */
-  toSlatePoint<T extends boolean>(
-    editor: IDomEditor,
-    domPoint: DOMPoint,
-    options: {
-      exactMatch: T
-      suppressThrow: T
-    }
-  ): T extends true ? Point | null : Point {
-    const { exactMatch, suppressThrow } = options
-    const [nearestNode, nearestOffset] = exactMatch ? domPoint : normalizeDOMPoint(domPoint)
-    const parentNode = nearestNode.parentNode as DOMElement
-    let textNode: DOMElement | null = null
-    let offset = 0
-
-    if (parentNode) {
-      const voidNode = parentNode.closest('[data-slate-void="true"]')
-      let leafNode = parentNode.closest('[data-slate-leaf]')
-      let domNode: DOMElement | null = null
-
-      // Calculate how far into the text node the `nearestNode` is, so that we
-      // can determine what the offset relative to the text node is.
-      if (leafNode) {
-        textNode = leafNode.closest('[data-slate-node="text"]')!
-        const window = DomEditor.getWindow(editor)
-        const range = window.document.createRange()
-        range.setStart(textNode, 0)
-        range.setEnd(nearestNode, nearestOffset)
-        const contents = range.cloneContents()
-        const removals = [
-          ...toArray(contents.querySelectorAll('[data-slate-zero-width]')),
-          ...toArray(contents.querySelectorAll('[contenteditable=false]')),
-        ]
-
-        removals.forEach(el => {
-          el!.parentNode!.removeChild(el)
-        })
-
-        // COMPAT: Edge has a bug where Range.prototype.toString() will
-        // convert \n into \r\n. The bug causes a loop when slate-react
-        // attempts to reposition its cursor to match the native position. Use
-        // textContent.length instead.
-        // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/
-        offset = contents.textContent!.length
-        domNode = textNode
-      } else if (voidNode) {
-        // For void nodes, the element with the offset key will be a cousin, not an
-        // ancestor, so find it by going down from the nearest void parent.
-        leafNode = voidNode.querySelector('[data-slate-leaf]')!
-
-        // COMPAT: In read-only editors the leaf is not rendered.
-        if (!leafNode) {
-          offset = 1
-        } else {
-          textNode = leafNode.closest('[data-slate-node="text"]')!
-          domNode = leafNode
-          offset = domNode.textContent!.length
-          domNode.querySelectorAll('[data-slate-zero-width]').forEach(el => {
-            offset -= el.textContent!.length
-          })
-        }
-      }
-
-      if (
-        domNode &&
-        offset === domNode.textContent!.length &&
-        // COMPAT: If the parent node is a Slate zero-width space, editor is
-        // because the text node should have no characters. However, during IME
-        // composition the ASCII characters will be prepended to the zero-width
-        // space, so subtract 1 from the offset to account for the zero-width
-        // space character.
-        (parentNode.hasAttribute('data-slate-zero-width') ||
-          // COMPAT: In Firefox, `range.cloneContents()` returns an extra trailing '\n'
-          // when the document ends with a new-line character. This results in the offset
-          // length being off by one, so we need to subtract one to account for this.
-          (IS_FIREFOX && domNode.textContent?.endsWith('\n')))
-      ) {
-        offset--
-      }
-    }
-
-    if (!textNode) {
-      if (suppressThrow) {
-        return null as T extends true ? Point | null : Point
-      }
-      throw new Error(`Cannot resolve a Slate point from DOM point: ${domPoint}`)
-    }
-
-    // COMPAT: If someone is clicking from one Slate editor into another,
-    // the select event fires twice, once for the old editor's `element`
-    // first, and then afterwards for the correct `element`. (2017/03/03)
-    const slateNode = DomEditor.toSlateNode(editor, textNode!)
-    const path = DomEditor.findPath(editor, slateNode)
-    return { path, offset } as T extends true ? Point | null : Point
-  },
-
-  hasRange(editor: IDomEditor, range: Range): boolean {
-    const { anchor, focus } = range
-    return Editor.hasPath(editor, anchor.path) && Editor.hasPath(editor, focus.path)
-  },
-
-  getNodeType(node: Node): string {
-    if (Element.isElement(node)) {
-      return node.type
-    }
-    return ''
-  },
-
-  checkNodeType(node: Node, type: string) {
-    return this.getNodeType(node) === type
-  },
-
-  getNodesStr(nodes: Node[]): string {
-    return nodes.map(node => Node.string(node)).join('')
-  },
-
-  getSelectedElems(editor: IDomEditor): Element[] {
-    const elems: Element[] = []
-
-    const nodeEntries = Editor.nodes(editor, { universal: true })
-    for (let nodeEntry of nodeEntries) {
-      const [node] = nodeEntry
-      if (Element.isElement(node)) elems.push(node)
-    }
-
-    return elems
-  },
-
-  getSelectedNodeByType(editor: IDomEditor, type: string): Node | null {
-    const [nodeEntry] = Editor.nodes(editor, {
-      match: n => this.checkNodeType(n, type),
-      universal: true,
-    })
-
-    if (nodeEntry == null) return null
-    return nodeEntry[0]
-  },
-
-  getSelectedTextNode(editor: IDomEditor): Node | null {
-    const [nodeEntry] = Editor.nodes(editor, {
-      match: n => Text.isText(n),
-      universal: true,
-    })
-
-    if (nodeEntry == null) return null
-    return nodeEntry[0]
-  },
-
-  isNodeSelected(editor: IDomEditor, node: Node): boolean {
-    const [nodeEntry] = Editor.nodes(editor, {
-      match: n => n === node,
-      universal: true,
-    })
-    if (nodeEntry == null) return false
-
-    const [n] = nodeEntry
-    if (n === node) return true
-
-    return false
-  },
-
-  isSelectionAtLineEnd(editor: IDomEditor, path: Path): boolean {
-    const { selection } = editor
-
-    if (!selection) return false
-
-    const isAtLineEnd =
-      Editor.isEnd(editor, selection.anchor, path) || Editor.isEnd(editor, selection.focus, path)
-
-    return isAtLineEnd
-  },
-
-  // 获取 textarea 实例
-  getTextarea(editor: IDomEditor): TextArea {
-    const textarea = EDITOR_TO_TEXTAREA.get(editor)
-    if (textarea == null) throw new Error('Cannot find textarea instance by editor')
-    return textarea
-  },
-
-  // 获取 toolbar 实例
-  getToolbar(editor: IDomEditor): Toolbar | null {
-    return EDITOR_TO_TOOLBAR.get(editor) || null
-  },
-
-  // 获取 hoverbar 实例
-  getHoverbar(editor: IDomEditor): HoverBar | null {
-    return EDITOR_TO_HOVER_BAR.get(editor) || null
-  },
-
-  // 格式化 editor content
-  normalizeContent(editor: IDomEditor) {
-    editor.children.forEach((node, index) => {
-      editor.normalizeNode([node, [index]])
-    })
-  },
-
-  /**
-   * 获取:距离触发 maxLength,还可以插入多少字符
-   * @param editor editor
-   */
-  getLeftLengthOfMaxLength(editor: IDomEditor): number {
-    const { maxLength, onMaxLength } = editor.getConfig()
-
-    // 未设置 maxLength ,则返回 number 最大值
-    if (typeof maxLength !== 'number' || maxLength <= 0) return Infinity
-
-    const editorText = editor.getText().replace(/\r|\n|(\r\n)/g, '') // 去掉换行
-    const curLength = editorText.length
-    const leftLength = maxLength - curLength
-
-    if (leftLength <= 0) {
-      // 触发 maxLength 限制,不再继续插入文字
-      if (onMaxLength) onMaxLength(editor)
-    }
-
-    return leftLength
-  },
-
-  // 清理暴露的 text 节点(拼音输入时经常出现)
-  cleanExposedTexNodeInSelectionBlock(editor: IDomEditor) {
-    // 有时候全选删除新增的文本节点可能不在段落内,因此遍历textArea删除掉
-    const { $textArea } = DomEditor.getTextarea(editor)
-    const childNodes = $textArea?.[0].childNodes
-    if (childNodes) {
-      for (const node of Array.from(childNodes)) {
-        if (node.nodeType === 3) {
-          node.remove()
-        } else {
-          break
-        }
-      }
-    }
-
-    const nodeEntries = Editor.nodes(editor, {
-      match: n => {
-        if (Element.isElement(n)) {
-          if (!editor.isInline(n)) {
-            // 匹配 block element
-            return true
-          }
-        }
-        return false
-      },
-      universal: true,
-    })
-    for (let nodeEntry of nodeEntries) {
-      if (nodeEntry != null) {
-        const n = nodeEntry[0]
-        const elem = DomEditor.toDOMNode(editor, n)
-
-        // 只遍历 elem 范围,考虑性能
-        walkTextNodes(elem, (textNode, parent) => {
-          const $parent = $(parent)
-          if ($parent.attr('data-slate-string')) {
-            return // 正常的 text
-          }
-          if ($parent.attr('data-slate-zero-width')) {
-            return // 正常的 text
-          }
-          if ($parent.attr('data-w-e-reserve')) {
-            return // 故意保留的节点
-          }
-
-          // 暴露的 text node ,删除
-          parent.removeChild(textNode)
-        })
-      }
-    }
-  },
-
-  /**
-   * 是否是编辑器里最后一个元素
-   * @param editor editor
-   * @param node node
-   */
-  isLastNode(editor: IDomEditor, node: Node) {
-    const editorChildren = editor.children || []
-    const editorChildrenLength = editorChildren.length
-    return editorChildren[editorChildrenLength - 1] === node
-  },
-
-  /**
-   * 生成空白 paragraph
-   */
-  genEmptyParagraph(): Element {
-    return { type: 'paragraph', children: [{ text: '' }] }
-  },
-
-  /**
-   * 是否选中了 void node
-   * @param editor editor
-   */
-  isSelectedVoidNode(editor: IDomEditor): boolean {
-    const voidNodes = Editor.nodes(editor, {
-      match: n => editor.isVoid(n as Element),
-    })
-    let len = 0
-    for (const n of voidNodes) {
-      len++
-    }
-    return len > 0
-  },
-
-  /**
-   * 选区是否在一个空行
-   * @param editor editor
-   */
-  isSelectedEmptyParagraph(editor: IDomEditor) {
-    const { selection } = editor
-    if (selection == null) return false
-
-    if (Range.isExpanded(selection)) return false
-
-    const selectedNode = DomEditor.getSelectedNodeByType(editor, 'paragraph')
-    if (selectedNode === null) return false
-
-    const { children } = selectedNode as Element
-    if (children.length !== 1) return false
-
-    const { text } = children[0] as Text
-    if (text === '') return true
-  },
-
-  /**
-   * 当前 path 指向的 node ,是否是空的(无内容)
-   * @param editor editor
-   * @param path path
-   */
-  isEmptyPath(editor: IDomEditor, path: Path): boolean {
-    const entry = Editor.node(editor, path)
-    if (entry == null) return false
-
-    const [node] = entry
-
-    const { children } = node as Element
-    if (children.length === 1) {
-      const { text } = children[0] as Text
-      if (text === '') return true // 内容为空
-    }
-
-    return false
-  },
-}
diff --git a/packages/core/src/editor/interface.ts b/packages/core/src/editor/interface.ts
deleted file mode 100644
index e62edaad3..000000000
--- a/packages/core/src/editor/interface.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * @description editor interface
- * @author wangfupeng
- */
-
-import { Editor, Location, Node, Ancestor, Element } from 'slate'
-import ee from 'event-emitter'
-import { IEditorConfig, AlertType, ISingleMenuConfig } from '../config/interface'
-import { IPositionStyle } from '../menus/interface'
-import { DOMElement } from '../utils/dom'
-
-export type ElementWithId = Element & { id: string }
-
-/**
- * 扩展 slate Editor 接口
- */
-export interface IDomEditor extends Editor {
-  // data 相关(粘贴、拖拽等)
-  insertData: (data: DataTransfer) => void
-  setFragmentData: (data: Pick<DataTransfer, 'getData' | 'setData'>) => void
-
-  // config
-  getConfig: () => IEditorConfig
-  getMenuConfig: (menuKey: string) => ISingleMenuConfig
-  getAllMenuKeys: () => string[]
-  alert: (info: string, type: AlertType) => void
-
-  // 内容处理
-  handleTab: () => void
-  getHtml: () => string
-  getText: () => string
-  getSelectionText: () => string // 获取选区文字
-  getElemsByTypePrefix: (typePrefix: string) => ElementWithId[]
-  getElemsByType: (type: string, isPrefix?: boolean) => ElementWithId[]
-  getParentNode: (node: Node) => Ancestor | null
-  isEmpty: () => boolean
-  clear: () => void
-  dangerouslyInsertHtml: (html: string, isRecursive?: boolean) => void
-  setHtml: (html: string) => void
-
-  // dom 相关
-  id: string
-  isDestroyed: boolean
-  isFullScreen: boolean
-  focus: (isEnd?: boolean) => void
-  isFocused: () => boolean
-  blur: () => void
-  updateView: () => void
-  destroy: () => void
-  scrollToElem: (id: string) => void
-  showProgressBar: (progress: number) => void
-  hidePanelOrModal: () => void
-  enable: () => void
-  disable: () => void
-  isDisabled: () => boolean
-  toDOMNode: (node: Node) => HTMLElement
-  fullScreen: () => void
-  unFullScreen: () => void
-  getEditableContainer: () => DOMElement
-
-  // selection 相关
-  select: (at: Location) => void
-  deselect: () => void
-  move: (distance: number, reverse?: boolean) => void
-  moveReverse: (distance: number) => void
-  restoreSelection: () => void
-  getSelectionPosition: () => Partial<IPositionStyle>
-  getNodePosition: (node: Node) => Partial<IPositionStyle>
-  isSelectedAll: () => boolean
-  selectAll: () => void
-
-  // 自定义事件
-  on: (type: string, listener: ee.EventListener) => void
-  off: (type: string, listener: ee.EventListener) => void
-  once: (type: string, listener: ee.EventListener) => void
-  emit: (type: string, ...args: any[]) => void
-
-  // undo redo - 不用自己实现,使用 slate-history 扩展
-  undo?: () => void
-  redo?: () => void
-}
diff --git a/packages/core/src/editor/plugins/with-config.ts b/packages/core/src/editor/plugins/with-config.ts
deleted file mode 100644
index c1901f1ba..000000000
--- a/packages/core/src/editor/plugins/with-config.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * @description slate 插件 - config 相关
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import { IDomEditor } from '../..'
-import { EDITOR_TO_CONFIG } from '../../utils/weak-maps'
-import { IEditorConfig, AlertType, ISingleMenuConfig } from '../../config/interface'
-import { MENU_ITEM_FACTORIES } from '../../menus/register'
-
-export const withConfig = <T extends Editor>(editor: T) => {
-  const e = editor as T & IDomEditor
-
-  e.getAllMenuKeys = (): string[] => {
-    const arr: string[] = []
-    for (let key in MENU_ITEM_FACTORIES) {
-      arr.push(key)
-    }
-    return arr
-  }
-
-  // 获取 editor 配置信息
-  e.getConfig = (): IEditorConfig => {
-    const config = EDITOR_TO_CONFIG.get(e)
-    if (config == null) throw new Error('Can not get editor config')
-    return config
-  }
-
-  // 获取 menu config
-  e.getMenuConfig = (menuKey: string): ISingleMenuConfig => {
-    const { MENU_CONF = {} } = e.getConfig()
-    return MENU_CONF[menuKey] || {}
-  }
-
-  // alert
-  e.alert = (info: string, type: AlertType = 'info') => {
-    const { customAlert } = e.getConfig()
-    if (customAlert) customAlert(info, type)
-  }
-
-  return e
-}
diff --git a/packages/core/src/editor/plugins/with-content.ts b/packages/core/src/editor/plugins/with-content.ts
deleted file mode 100644
index 1af7d7444..000000000
--- a/packages/core/src/editor/plugins/with-content.ts
+++ /dev/null
@@ -1,409 +0,0 @@
-/**
- * @description slate 插件 - content
- * @author wangfupeng
- */
-
-import { Editor, Node, Text, Path, Operation, Range, Transforms, Element, Descendant } from 'slate'
-import { DomEditor } from '../dom-editor'
-import { IDomEditor } from '../..'
-import { EDITOR_TO_SELECTION, NODE_TO_KEY } from '../../utils/weak-maps'
-import node2html from '../../to-html/node2html'
-import { genElemId } from '../../render/helper'
-import { Key } from '../../utils/key'
-import $, { DOMElement, NodeType } from '../../utils/dom'
-import { findCurrentLineRange } from '../../utils/line'
-import { ElementWithId } from '../interface'
-import { PARSE_ELEM_HTML_CONF, TEXT_TAGS } from '../../parse-html/index'
-import parseElemHtml from '../../parse-html/parse-elem-html'
-import { htmlToContent } from '../../create/helper'
-import { IGNORE_TAGS } from '../../constants'
-
-/**
- * 把 elem 插入到编辑器
- * @param editor editor
- * @param elem slate elem
- */
-function insertElemToEditor(editor: IDomEditor, elem: Element) {
-  if (editor.isInline(elem)) {
-    // inline elem 直接插入
-    editor.insertNode(elem)
-
-    // link 特殊处理,否则后面插入的文字全都在 a 里面 issue#4573
-    if (elem.type === 'link') editor.insertFragment([{ text: '' }])
-  } else {
-    // block elem ,另起一行插入 —— 重要
-    Transforms.insertNodes(editor, elem, { mode: 'highest' })
-  }
-}
-
-export const withContent = <T extends Editor>(editor: T) => {
-  const e = editor as T & IDomEditor
-  const { onChange, insertText, apply, deleteBackward } = e
-
-  e.insertText = (text: string) => {
-    const { readOnly } = e.getConfig()
-    if (readOnly) return
-
-    insertText(text)
-  }
-
-  // 重写 apply 方法
-  // apply 方法非常重要,它最终执行 operation https://docs.slatejs.org/concepts/05-operations
-  // operation 的接口定义参考 slate src/interfaces/operation.ts
-  e.apply = (op: Operation) => {
-    const matches: [Path, Key][] = []
-
-    switch (op.type) {
-      case 'insert_text':
-      case 'remove_text':
-      case 'set_node': {
-        for (const [node, path] of Editor.levels(e, { at: op.path })) {
-          // 在当前节点寻找
-          const key = DomEditor.findKey(e, node)
-          matches.push([path, key])
-        }
-        break
-      }
-
-      case 'insert_node':
-      case 'remove_node':
-      case 'merge_node':
-      case 'split_node': {
-        for (const [node, path] of Editor.levels(e, { at: Path.parent(op.path) })) {
-          // 在父节点寻找
-          const key = DomEditor.findKey(e, node)
-          matches.push([path, key])
-        }
-        break
-      }
-
-      case 'move_node': {
-        for (const [node, path] of Editor.levels(e, {
-          at: Path.common(Path.parent(op.path), Path.parent(op.newPath)),
-        })) {
-          const key = DomEditor.findKey(e, node)
-          matches.push([path, key])
-        }
-        break
-      }
-    }
-
-    // 执行原本的 apply - 重要!!!
-    apply(op)
-
-    // 绑定 node 和 key
-    for (const [path, key] of matches) {
-      const [node] = Editor.node(e, path)
-      NODE_TO_KEY.set(node, key)
-    }
-  }
-
-  e.deleteBackward = unit => {
-    if (unit !== 'line') {
-      return deleteBackward(unit)
-    }
-
-    if (editor.selection && Range.isCollapsed(editor.selection)) {
-      const parentBlockEntry = Editor.above(editor, {
-        match: n => Editor.isBlock(editor, n),
-        at: editor.selection,
-      })
-
-      if (parentBlockEntry) {
-        const [, parentBlockPath] = parentBlockEntry
-        const parentElementRange = Editor.range(editor, parentBlockPath, editor.selection.anchor)
-
-        const currentLineRange = findCurrentLineRange(e, parentElementRange)
-
-        if (!Range.isCollapsed(currentLineRange)) {
-          Transforms.delete(editor, { at: currentLineRange })
-        }
-      }
-    }
-  }
-
-  // 重写 onchange API
-  e.onChange = () => {
-    // 记录当前选区
-    const { selection } = e
-    if (selection != null) {
-      EDITOR_TO_SELECTION.set(e, selection)
-    }
-
-    // 触发配置的 change 事件
-    e.emit('change')
-
-    onChange()
-  }
-
-  // tab
-  e.handleTab = () => {
-    e.insertText('    ')
-  }
-
-  // 获取 html (去掉了格式化 2021.12.10)
-  e.getHtml = (): string => {
-    const { children = [] } = e
-    const html = children.map(child => node2html(child, e)).join('')
-    return html
-  }
-
-  // 获取 text
-  e.getText = (): string => {
-    const { children = [] } = e
-    return children.map(child => Node.string(child)).join('\n')
-  }
-
-  // 获取选区文字
-  e.getSelectionText = (): string => {
-    const { selection } = e
-    if (selection == null) return ''
-    return Editor.string(editor, selection)
-  }
-
-  // 根据 type 获取 elems
-  e.getElemsByType = (type: string, isPrefix = false): ElementWithId[] => {
-    const elems: ElementWithId[] = []
-
-    // 获取 editor 所有 nodes
-    const nodeEntries = Editor.nodes(e, {
-      at: [],
-      universal: true,
-    })
-    for (let nodeEntry of nodeEntries) {
-      const [node] = nodeEntry
-      if (Element.isElement(node)) {
-        // 判断 type (前缀 or 全等)
-        let flag = isPrefix ? node.type.indexOf(type) >= 0 : node.type === type
-        if (flag) {
-          const key = DomEditor.findKey(e, node)
-          const id = genElemId(key.id)
-
-          // node + id
-          elems.push({
-            ...node,
-            id,
-          })
-        }
-      }
-    }
-
-    return elems
-  }
-
-  // 根据 type 前缀,获取 elems
-  e.getElemsByTypePrefix = (typePrefix: string): ElementWithId[] => {
-    return e.getElemsByType(typePrefix, true)
-  }
-
-  /**
-   * 判断 editor 是否为空(只有一个空 paragraph)
-   */
-  e.isEmpty = () => {
-    const { children = [] } = e
-    if (children.length > 1) return false // >1 个顶级节点
-
-    const firstNode = children[0]
-    if (firstNode == null) return true // editor.children 空数组
-
-    if (Element.isElement(firstNode) && firstNode.type === 'paragraph') {
-      const { children: texts = [] } = firstNode
-      if (texts.length > 1) return false // >1 text node
-
-      const t = texts[0]
-      if (t == null) return true // 无 text 节点
-
-      if (Text.isText(t) && t.text === '') return true // 只有一个 text 且是空字符串
-    }
-
-    return false
-  }
-
-  /**
-   * 清空内容
-   */
-  e.clear = () => {
-    const initialEditorValue: Node[] = [
-      {
-        type: 'paragraph',
-        children: [{ text: '' }],
-      },
-    ]
-
-    Transforms.delete(e, {
-      at: {
-        anchor: Editor.start(e, []),
-        focus: Editor.end(e, []),
-      },
-    })
-
-    if (e.children.length === 0) {
-      Transforms.insertNodes(e, initialEditorValue)
-    }
-  }
-
-  e.getParentNode = (node: Node) => {
-    return DomEditor.getParentNode(e, node)
-  }
-
-  /**
-   * 插入 html (不保证语义完全正确),用于粘贴
-   * @param html html string
-   * @param isRecursive 是否递归调用(内部使用,使用者不要传参)
-   */
-  e.dangerouslyInsertHtml = (html: string = '', isRecursive = false) => {
-    if (!html) return
-
-    // ------------- 把 html 转换为 DOM nodes -------------
-    const div = document.createElement('div')
-    div.innerHTML = html
-    let domNodes = Array.from(div.childNodes)
-
-    // 过滤一下,只保留 elem 和 text ,并却掉一些无用标签(如 style script 等)
-    domNodes = domNodes.filter(n => {
-      const { nodeType, nodeName } = n
-      // Text Node
-      if (nodeType === NodeType.TEXT_NODE) return true
-
-      // Element Node
-      if (nodeType === NodeType.ELEMENT_NODE) {
-        // 过滤掉忽略的 tag
-        if (IGNORE_TAGS.has(nodeName.toLowerCase())) return false
-        else return true
-      }
-      return false
-    })
-    if (domNodes.length === 0) return
-
-    // ------------- 把 DOM nodes 转换为 slate nodes ,并插入到编辑器 -------------
-
-    const { selection } = e
-    if (selection == null) return
-    let curEmptyParagraphPath: Path | null = null
-
-    // 是否当前选中了一个空 p (如果是,后面会删掉)
-    // 递归调用时不判断
-    if (DomEditor.isSelectedEmptyParagraph(e) && !isRecursive) {
-      const { focus } = selection
-      curEmptyParagraphPath = [focus.path[0]] // 只记录顶级 path 即可
-    }
-
-    div.setAttribute('hidden', 'true')
-    document.body.appendChild(div)
-
-    let insertedElemNum = 0 // 记录插入 elem 的数量 ( textNode 不算 )
-    domNodes.forEach(n => {
-      const { nodeType, nodeName, textContent = '' } = n
-
-      // ------ Text node ------
-      if (nodeType === NodeType.TEXT_NODE) {
-        if (!textContent || !textContent.trim()) return // 无内容的 Text
-
-        // 插入文本
-        //【注意】insertNode 和 insertText 有区别:后者会继承光标处的文本样式(如加粗);前者会加入纯文本,无样式;
-        e.insertNode({ text: textContent })
-        return
-      }
-
-      // ------ Element Node ------
-      if (nodeName === 'BR') {
-        e.insertText('\n') // 换行
-        return
-      }
-
-      // 判断当前的 el 是否是可识别的 tag
-      const el = n as DOMElement
-      let isParseMatch = false
-      if (TEXT_TAGS.includes(nodeName.toLowerCase())) {
-        // text elem,如 <span>
-        isParseMatch = true
-      } else {
-        for (let selector in PARSE_ELEM_HTML_CONF) {
-          if (el.matches(selector)) {
-            // 普通 elem,如 <p> <a> 等(非 text elem)
-            isParseMatch = true
-            break
-          }
-        }
-      }
-
-      // 匹配上了,则生成 slate elem 并插入
-      if (isParseMatch) {
-        // 生成并插入
-        const $el = $(el)
-        const parsedRes = parseElemHtml($el, e) as Element
-
-        if (Array.isArray(parsedRes)) {
-          parsedRes.forEach(el => insertElemToEditor(e, el))
-          insertedElemNum++ // 记录数量
-        } else {
-          insertElemToEditor(e, parsedRes)
-          insertedElemNum++ // 记录数量
-        }
-
-        // 如果当前选中 void node ,则选区移动一下
-        if (DomEditor.isSelectedVoidNode(e)) e.move(1)
-
-        return
-      }
-
-      // 没有匹配上(如 div )
-      const display = window.getComputedStyle(el).display
-      if (!DomEditor.isSelectedEmptyParagraph(e)) {
-        // 当前不是空行,且 非 inline - 则换行
-        if (display.indexOf('inline') < 0) e.insertBreak()
-      }
-      e.dangerouslyInsertHtml(el.innerHTML, true) // 继续插入子内容
-    })
-
-    // 删除第一个空行
-    if (insertedElemNum && curEmptyParagraphPath) {
-      if (DomEditor.isEmptyPath(e, curEmptyParagraphPath)) {
-        Transforms.removeNodes(e, { at: curEmptyParagraphPath })
-      }
-    }
-
-    div.remove() // 粘贴完了,移除 div
-  }
-
-  /**
-   * 重置 HTML 内容
-   * @param html html string
-   */
-  e.setHtml = (html: string = '') => {
-    // 记录编辑器当前状态
-    const isEditorDisabled = e.isDisabled()
-    const isEditorFocused = e.isFocused()
-    const editorSelectionStr = JSON.stringify(e.selection)
-
-    // 删除当前内容
-    e.enable()
-    e.focus()
-    // 需要标准的{anchor:xxx, focus: xxxx} 否则无法通过slate history的检查
-    // 使用 e.select([]) e.selectAll() 生成的location不是标准的{anchor: xxxx, focus: xxx}形式
-    // https://github.com/wangeditor-team/wangEditor/issues/4754
-    e.clear()
-    // 设置新内容
-    const newContent = htmlToContent(e, html)
-    Transforms.insertFragment(e, newContent)
-
-    // 恢复编辑器状态和选区
-    if (!isEditorFocused) {
-      e.deselect()
-      e.blur()
-    }
-    if (isEditorDisabled) {
-      e.deselect()
-      e.disable()
-    }
-    if (e.isFocused()) {
-      try {
-        e.select(JSON.parse(editorSelectionStr)) // 选中原来的位置
-      } catch (ex) {
-        e.select(Editor.start(e, [])) // 选中开始
-      }
-    }
-  }
-
-  return e
-}
diff --git a/packages/core/src/editor/plugins/with-dom.ts b/packages/core/src/editor/plugins/with-dom.ts
deleted file mode 100644
index ef1226768..000000000
--- a/packages/core/src/editor/plugins/with-dom.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-/**
- * @description slate 插件 - dom 相关
- * @author wangfupeng
- */
-
-import { Node, Editor, Transforms } from 'slate'
-import { DomEditor } from '../dom-editor'
-import { IDomEditor } from '../..'
-import $, { Dom7Array } from '../../utils/dom'
-import {
-  IS_FOCUSED,
-  EDITOR_TO_PANEL_AND_MODAL,
-  EDITOR_TO_TEXTAREA,
-  TEXTAREA_TO_EDITOR,
-  EDITOR_TO_TOOLBAR,
-  TOOLBAR_TO_EDITOR,
-  EDITOR_TO_HOVER_BAR,
-  HOVER_BAR_TO_EDITOR,
-  EDITOR_TO_SELECTION,
-} from '../../utils/weak-maps'
-
-let ID = 1
-
-/**
- * `withDOM` adds DOM specific behaviors to the editor.
- */
-export const withDOM = <T extends Editor>(editor: T) => {
-  const e = editor as T & IDomEditor
-
-  e.id = `wangEditor-${ID++}`
-
-  e.isDestroyed = false
-
-  e.isFullScreen = false
-
-  // focus
-  e.focus = (isEnd?: boolean) => {
-    const el = DomEditor.toDOMNode(e, e)
-    el.focus({ preventScroll: true })
-
-    IS_FOCUSED.set(e, true)
-
-    // 恢复选区
-    if (isEnd) {
-      // 选区定位到结尾
-      const end = Editor.end(e, [])
-      Transforms.select(e, end)
-    } else {
-      const selection = EDITOR_TO_SELECTION.get(e)
-      if (selection) {
-        Transforms.select(e, selection) // 选区定位到之前的位置
-      } else {
-        Transforms.select(e, Editor.start(e, [])) // 选区定位到开始
-      }
-    }
-  }
-
-  // isFocused
-  e.isFocused = () => {
-    return !!IS_FOCUSED.get(e)
-  }
-
-  // blur
-  e.blur = () => {
-    const el = DomEditor.toDOMNode(e, e)
-    el.blur()
-
-    // 手动执行一次光标 deselect, 触发 onchange 回调,改变 Toolbar 的状态
-    Transforms.deselect(e)
-
-    IS_FOCUSED.set(e, false)
-  }
-
-  // 手动更新试图
-  e.updateView = () => {
-    const textarea = DomEditor.getTextarea(e)
-    textarea.changeViewState()
-
-    const toolbar = DomEditor.getToolbar(e)
-    toolbar && toolbar.changeToolbarState()
-
-    const hoverbar = DomEditor.getHoverbar(e)
-    hoverbar && hoverbar.changeHoverbarState()
-  }
-
-  // destroy
-  e.destroy = () => {
-    // 销毁相关实例(会销毁 DOM)
-    if (e.isDestroyed) return
-    // fix https://github.com/wangeditor-team/wangEditor-v5/issues/457
-    const textarea = DomEditor.getTextarea(e)
-    textarea.destroy()
-    EDITOR_TO_TEXTAREA.delete(e)
-    TEXTAREA_TO_EDITOR.delete(textarea)
-
-    const toolbar = DomEditor.getToolbar(e)
-    if (toolbar) {
-      toolbar.destroy()
-      EDITOR_TO_TOOLBAR.delete(e)
-      TOOLBAR_TO_EDITOR.delete(toolbar)
-    }
-
-    const hoverbar = DomEditor.getHoverbar(e)
-    if (hoverbar) {
-      hoverbar.destroy()
-      EDITOR_TO_HOVER_BAR.delete(e)
-      HOVER_BAR_TO_EDITOR.delete(hoverbar)
-    }
-
-    // 修改属性
-    e.isDestroyed = true
-
-    // 触发自定义事件
-    e.emit('destroyed')
-  }
-
-  // scroll to elem
-  e.scrollToElem = (id: string) => {
-    const { scroll } = e.getConfig()
-    if (!scroll) {
-      // 没有设置编辑区域滚动,则不能用
-      let info = '编辑器禁用了 scroll ,编辑器内容无法滚动,请自行实现该功能'
-      info += '\nYou has disabled editor scroll, please do this yourself'
-      console.warn(info)
-      return
-    }
-
-    const $elem = $(`#${id}`)
-    if ($elem.length === 0) return
-
-    // $elem 不在 editor DOM 范围之内
-    const elem = $elem[0]
-    if (!DomEditor.hasDOMNode(e, elem)) {
-      let info = `Element (found by id is '${id}') is not in editor DOM`
-      info += `\n 通过 id '${id}' 找到的 element 不在 editor DOM 之内`
-      console.error(info, elem)
-      return
-    }
-
-    const textarea = DomEditor.getTextarea(e)
-    const { $textAreaContainer, $scroll } = textarea
-
-    const { top: elemTop } = $elem.offset()
-    const { top: containerTop } = $textAreaContainer.offset()
-
-    // 滚动到指定元素
-    $scroll[0].scrollBy({ top: elemTop - containerTop, behavior: 'smooth' })
-  }
-
-  // showProgressBar
-  e.showProgressBar = (progress: number) => {
-    // progress 值范围: 0 - 100
-    if (progress < 1) return
-
-    // 显示进度条
-    const textarea = DomEditor.getTextarea(e)
-    textarea.changeProgress(progress)
-  }
-
-  // 隐藏 panel 或 modal
-  e.hidePanelOrModal = () => {
-    const set = EDITOR_TO_PANEL_AND_MODAL.get(e)
-    if (set == null) return
-    set.forEach(panelOrModal => panelOrModal.hide())
-  }
-
-  e.enable = () => {
-    const config = e.getConfig()
-    config.readOnly = false
-
-    // 更新视图
-    e.updateView()
-  }
-
-  e.disable = () => {
-    const config = e.getConfig()
-    config.readOnly = true
-
-    // 更新视图
-    e.updateView()
-  }
-
-  e.isDisabled = () => {
-    const config = e.getConfig()
-    return config.readOnly
-  }
-
-  e.toDOMNode = (node: Node) => {
-    return DomEditor.toDOMNode(e, node)
-  }
-
-  e.fullScreen = () => {
-    if (e.isFullScreen) return
-
-    let $toolbarBox: Dom7Array | null = null
-    const toolbar = DomEditor.getToolbar(e)
-    if (toolbar) {
-      $toolbarBox = toolbar.$box
-    }
-
-    const textarea = DomEditor.getTextarea(e)
-    const $textAreaBox = textarea.$box
-    const $parent = $textAreaBox.parent()
-
-    if ($toolbarBox && $toolbarBox.parent()[0] !== $parent[0]) {
-      // toolbar DOM 父节点,和 editor DOM 父节点不一致,则不能设置全屏
-      let info =
-        'Can not set full screen, cause toolbar DOM parent is not equal to textarea DOM parent'
-      info += '\n不能设置全屏,因为 toolbar DOM 父节点和 textarea DOM 父节点不一致'
-      throw new Error(info)
-    }
-
-    // 设置全屏
-    $parent.addClass('w-e-full-screen-container')
-
-    // 设置 z-index
-    const curZIndex = $parent.css('z-index')
-    $parent.attr('data-z-index', curZIndex.toString())
-
-    // 记录属性
-    e.isFullScreen = true
-
-    // 触发自定义事件
-    e.emit('fullScreen')
-  }
-
-  e.unFullScreen = () => {
-    if (!e.isFullScreen) return
-
-    const textarea = DomEditor.getTextarea(e)
-    const $textAreaBox = textarea.$box
-    const $parent = $textAreaBox.parent()
-
-    // 解决#issue175, 编辑器取消全屏 - element dialog组件会被隐藏
-    setTimeout(() => {
-      // 取消全屏
-      $parent.removeClass('w-e-full-screen-container')
-
-      // 记录属性
-      e.isFullScreen = false
-
-      // 触发自定义事件
-      e.emit('unFullScreen')
-    }, 200)
-  }
-
-  /**
-   * 获取编辑区域 DOM 容器
-   */
-  e.getEditableContainer = () => {
-    const textarea = DomEditor.getTextarea(e)
-    return textarea.$textAreaContainer[0]
-  }
-
-  return e
-}
diff --git a/packages/core/src/editor/plugins/with-emitter.ts b/packages/core/src/editor/plugins/with-emitter.ts
deleted file mode 100644
index cb77cbabc..000000000
--- a/packages/core/src/editor/plugins/with-emitter.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * @description 自定义事件 插件
- * @author wangfupeng
- */
-
-import ee, { Emitter } from 'event-emitter'
-import { Editor } from 'slate'
-import { IDomEditor } from '../interface'
-import { EDITOR_TO_EMITTER } from '../../utils/weak-maps'
-
-/**
- * 获取 editor 的 emitter 实例
- * @param editor editor
- */
-function getEmitter(editor: IDomEditor): Emitter {
-  let emitter = EDITOR_TO_EMITTER.get(editor)
-  if (emitter == null) {
-    emitter = ee()
-    EDITOR_TO_EMITTER.set(editor, emitter)
-  }
-  return emitter
-}
-
-// 记录下当前 editor 的 destroy listeners
-const EDITOR_TO_DESTROY_LISTENERS: WeakMap<IDomEditor, Set<Function>> = new WeakMap()
-function recordDestroyListeners(editor: IDomEditor, fn: Function) {
-  let listeners = EDITOR_TO_DESTROY_LISTENERS.get(editor)
-  if (listeners == null) {
-    listeners = new Set<Function>()
-    EDITOR_TO_DESTROY_LISTENERS.set(editor, listeners)
-  }
-  listeners.add(fn)
-}
-function getDestroyListeners(editor: IDomEditor): Set<Function> {
-  return EDITOR_TO_DESTROY_LISTENERS.get(editor) || new Set()
-}
-function clearDestroyListeners(editor: IDomEditor) {
-  EDITOR_TO_DESTROY_LISTENERS.set(editor, new Set())
-}
-
-export const withEmitter = <T extends Editor>(editor: T) => {
-  const e = editor as T & IDomEditor
-
-  // 自定义事件
-  e.on = (type, listener) => {
-    const emitter = getEmitter(e)
-
-    // 绑定事件
-    emitter.on(type, listener)
-
-    // destroyed 事件需要记录下来,以便最后统一 off 掉
-    if (type === 'destroyed') recordDestroyListeners(e, listener)
-
-    // editor 销毁时,取消绑定 - 重要
-    if (type !== 'destroyed') {
-      const fn = () => emitter.off(type, listener)
-      emitter.on('destroyed', fn)
-      recordDestroyListeners(e, fn) // 记录下来
-    }
-  }
-  e.once = (type, listener) => {
-    const emitter = getEmitter(e)
-    emitter.once(type, listener)
-  }
-  e.off = (type, listener) => {
-    const emitter = getEmitter(e)
-    emitter.off(type, listener)
-  }
-  e.emit = (type, ...args: any[]) => {
-    const emitter = getEmitter(e)
-    emitter.emit(type, ...args)
-
-    // editor 销毁时,off 掉 destroyed listeners
-    if (type === 'destroyed') {
-      const listeners = getDestroyListeners(e)
-      listeners.forEach(fn => emitter.off('destroyed', fn as ee.EventListener))
-      clearDestroyListeners(e) // 清空 destroyed listeners
-    }
-  }
-
-  return e
-}
diff --git a/packages/core/src/editor/plugins/with-event-data.ts b/packages/core/src/editor/plugins/with-event-data.ts
deleted file mode 100644
index c6576909c..000000000
--- a/packages/core/src/editor/plugins/with-event-data.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * @description slate 插件 - event data 相关
- * @author wangfupeng
- */
-
-import { Editor, Node, Transforms, Range } from 'slate'
-import { DomEditor } from '../dom-editor'
-import { IDomEditor } from '../..'
-
-import { isDOMText, getPlainText } from '../../utils/dom'
-
-export const withEventData = <T extends Editor>(editor: T) => {
-  const e = editor as T & IDomEditor
-  const { insertText, insertFragment } = e
-
-  e.setFragmentData = (data: Pick<DataTransfer, 'getData' | 'setData'>) => {
-    const { selection } = e
-
-    if (!selection) {
-      return
-    }
-
-    // 获取开始、结束两个 point { path, offset }
-    const [start, end] = Range.edges(selection)
-    // Editor.void - Match a void node in the current branch of the editor.
-    const startVoid = Editor.void(e, { at: start.path })
-    const endVoid = Editor.void(e, { at: end.path })
-
-    if (Range.isCollapsed(selection) && !startVoid) {
-      return
-    }
-
-    // Create a fake selection so that we can add a Base64-encoded copy of the
-    // fragment to the HTML, to decode on future pastes.
-    const domRange = DomEditor.toDOMRange(e, selection)
-    let contents = domRange.cloneContents()
-    let attach = contents.childNodes[0] as HTMLElement
-
-    // Make sure attach is non-empty, since empty nodes will not get copied.
-    contents.childNodes.forEach(node => {
-      if (node.textContent && node.textContent.trim() !== '') {
-        attach = node as HTMLElement
-      }
-    })
-
-    // COMPAT: If the end node is a void node, we need to move the end of the
-    // range from the void node's spacer span, to the end of the void node's
-    // content, since the spacer is before void's content in the DOM.
-    if (endVoid) {
-      const [voidNode] = endVoid
-      const r = domRange.cloneRange()
-      const domNode = DomEditor.toDOMNode(e, voidNode)
-      r.setEndAfter(domNode)
-      contents = r.cloneContents()
-    }
-
-    // COMPAT: If the start node is a void node, we need to attach the encoded
-    // fragment to the void node's content node instead of the spacer, because
-    // attaching it to empty `<div>/<span>` nodes will end up having it erased by
-    // most browsers. (2018/04/27)
-    if (startVoid) {
-      attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement
-    }
-
-    // Remove any zero-width space spans from the cloned DOM so that they don't
-    // show up elsewhere when pasted.
-    Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
-      const isNewline = zw.getAttribute('data-slate-zero-width') === 'n'
-      zw.textContent = isNewline ? '\n' : ''
-    })
-
-    // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
-    // in the HTML, and can be used for intra-Slate pasting. If it's a text
-    // node, wrap it in a `<span>` so we have something to set an attribute on.
-    if (isDOMText(attach)) {
-      const span = attach.ownerDocument.createElement('span')
-      // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
-      // then leading and trailing spaces will be ignored. (2017/09/21)
-      span.style.whiteSpace = 'pre'
-      span.appendChild(attach)
-      contents.appendChild(span)
-      attach = span
-    }
-
-    const fragment = e.getFragment()
-    const string = JSON.stringify(fragment)
-    const encoded = window.btoa(encodeURIComponent(string))
-    attach.setAttribute('data-slate-fragment', encoded)
-    data.setData('application/x-slate-fragment', encoded)
-
-    // Add the content to a <div> so that we can get its inner HTML.
-    const div = contents.ownerDocument.createElement('div')
-    div.appendChild(contents)
-    div.setAttribute('hidden', 'true')
-    contents.ownerDocument.body.appendChild(div)
-    data.setData('text/html', div.innerHTML)
-    data.setData('text/plain', getPlainText(div))
-    contents.ownerDocument.body.removeChild(div)
-
-    return data
-  }
-
-  e.insertData = (data: DataTransfer) => {
-    const fragment = data.getData('application/x-slate-fragment')
-    if (fragment) {
-      const decoded = decodeURIComponent(window.atob(fragment))
-      const parsed = JSON.parse(decoded) as Node[]
-      e.insertFragment(parsed)
-      return
-    }
-
-    const text = data.getData('text/plain')
-    const html = data.getData('text/html')
-    // const rtf = data.getData('text/rtf')
-
-    if (html) {
-      e.dangerouslyInsertHtml(html)
-      return
-    }
-
-    if (text) {
-      const lines = text.split(/\r\n|\r|\n/)
-      let split = false
-
-      for (const line of lines) {
-        if (split) {
-          Transforms.splitNodes(e, { always: true })
-        }
-
-        insertText(line)
-        split = true
-      }
-      return
-    }
-  }
-
-  return e
-}
diff --git a/packages/core/src/editor/plugins/with-max-length.ts b/packages/core/src/editor/plugins/with-max-length.ts
deleted file mode 100644
index 57c8900b0..000000000
--- a/packages/core/src/editor/plugins/with-max-length.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * @description slate 插件 - maxLength
- * @author wangfupeng
- */
-
-//【注意】拼音输入时 maxLength 限制在 CompositionEnd 事件中处理
-
-import { Editor, Node } from 'slate'
-import { IDomEditor, DomEditor } from '../..'
-import { IGNORE_TAGS } from '../../constants'
-import { NodeType } from '../../utils/dom'
-
-export const withMaxLength = <T extends Editor>(editor: T) => {
-  const e = editor as T & IDomEditor
-  const { insertText, insertNode, insertFragment, dangerouslyInsertHtml } = e
-
-  // 处理 text
-  e.insertText = (text: string) => {
-    const { maxLength } = e.getConfig()
-    if (!maxLength) {
-      insertText(text)
-      return
-    }
-
-    const leftLength = DomEditor.getLeftLengthOfMaxLength(e)
-    if (leftLength <= 0) {
-      // 已经触发 maxLength ,不再输入文字
-      return
-    }
-
-    if (leftLength < text.length) {
-      // 剩余长度小于 text 长度,则截取 text
-      insertText(text.slice(0, leftLength))
-      return
-    }
-
-    insertText(text)
-  }
-
-  // 处理 node
-  e.insertNode = (node: Node) => {
-    const { maxLength } = e.getConfig()
-    if (!maxLength) {
-      insertNode(node)
-      return
-    }
-
-    const leftLength = DomEditor.getLeftLengthOfMaxLength(e)
-    if (leftLength <= 0) {
-      // 已经触发 maxLength ,不再插入
-      return
-    }
-
-    const text = Node.string(node)
-    if (leftLength < text.length) {
-      // 剩余长度,不够 node text 长度,不再插入
-      return
-    }
-
-    insertNode(node)
-  }
-
-  // 处理 fragment
-  e.insertFragment = (fragment: Node[]) => {
-    const { maxLength } = e.getConfig()
-    if (!maxLength) {
-      // 无 maxLength
-      insertFragment(fragment)
-      return
-    }
-
-    // 只有一个 node 时,使用 insertFragment ,防止换行
-    if (fragment.length === 1) {
-      const node = fragment[0]
-      const leftLength = DomEditor.getLeftLengthOfMaxLength(e)
-      const text = Node.string(node)
-
-      if (leftLength < text.length) {
-        // 已经触发 maxLength ,不再插入
-        return
-      }
-
-      insertFragment(fragment)
-      return
-    }
-    // 有 maxLength ,则分别插入 node
-    fragment.forEach(n => {
-      e.insertNode(n) //【注意】这里必须使用 `e.insertNode` ,而不是 insertNode
-    })
-  }
-
-  e.dangerouslyInsertHtml = (html: string = '', isRecursive = false) => {
-    if (!html) return
-
-    const { maxLength } = e.getConfig()
-    if (!maxLength) {
-      // 无 maxLength
-      dangerouslyInsertHtml(html, isRecursive)
-      return
-    }
-    const leftLength = DomEditor.getLeftLengthOfMaxLength(e)
-    if (leftLength <= 0) {
-      // 已经触发 maxLength ,不再输入文字
-      return
-    }
-
-    // ------------- 把 html 转换为 DOM nodes -------------
-    const div = document.createElement('div')
-    div.innerHTML = html
-    const text = Array.from(div.childNodes).reduce<string>((acc, node) => {
-      const { nodeType, nodeName } = node
-      if (!node) {
-        return acc
-      }
-      // Text Node
-      if (nodeType === NodeType.TEXT_NODE) return acc + (node.textContent || '')
-
-      // Element Node
-      if (nodeType === NodeType.ELEMENT_NODE) {
-        // 过滤掉忽略的 tag
-        if (IGNORE_TAGS.has(nodeName.toLowerCase())) return acc
-        else return acc + (node.textContent || '')
-      }
-      return acc
-    }, '')
-
-    if (leftLength < text.length) {
-      return
-    }
-
-    dangerouslyInsertHtml(html, isRecursive)
-  }
-
-  return e // 返回 editor 实例
-}
diff --git a/packages/core/src/editor/plugins/with-selection.ts b/packages/core/src/editor/plugins/with-selection.ts
deleted file mode 100644
index 7b53902fe..000000000
--- a/packages/core/src/editor/plugins/with-selection.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * @description slate 插件 - selection 相关
- * @author wangfupeng
- */
-
-import { Editor, Transforms, Location, Node, Range, Point } from 'slate'
-import { IDomEditor } from '../interface'
-import { DomEditor } from '../dom-editor'
-import { getPositionByNode, getPositionBySelection } from '../../menus/helpers/position'
-import { EDITOR_TO_SELECTION } from '../../utils/weak-maps'
-
-export const withSelection = <T extends Editor>(editor: T) => {
-  const e = editor as T & IDomEditor
-
-  // 选中
-  e.select = (at: Location) => {
-    Transforms.select(e, at)
-  }
-
-  // 取消选中
-  e.deselect = () => {
-    const { selection } = e
-    const root = DomEditor.findDocumentOrShadowRoot(e)
-    const domSelection = root.getSelection()
-
-    if (domSelection && domSelection.rangeCount > 0) {
-      domSelection.removeAllRanges()
-    }
-
-    if (selection) {
-      Transforms.deselect(editor)
-    }
-  }
-
-  // 移动光标
-  e.move = (distance: number, reverse = false) => {
-    if (!distance) return
-    if (distance < 0) return
-
-    Transforms.move(editor, {
-      distance,
-      unit: 'character',
-      reverse,
-    })
-  }
-
-  // 反向移动光标
-  e.moveReverse = (distance: number) => {
-    e.move(distance, true)
-  }
-
-  /**
-   * 还原选区
-   */
-  e.restoreSelection = () => {
-    const selection = EDITOR_TO_SELECTION.get(e)
-    if (selection == null) return
-
-    e.focus()
-    Transforms.select(e, selection)
-  }
-
-  /**
-   * 获取选区的 position
-   */
-  e.getSelectionPosition = () => {
-    return getPositionBySelection(e)
-  }
-
-  /**
-   * 获取 node 的 position
-   */
-  e.getNodePosition = (node: Node) => {
-    return getPositionByNode(e, node)
-  }
-
-  /**
-   * 是否全选
-   */
-  e.isSelectedAll = () => {
-    const { selection } = e
-    if (selection == null) return false
-
-    const [start1, end1] = Range.edges(selection) // 获取当前选取的开始、结束 point
-    const [start2, end2] = Editor.edges(e, []) // 获取编辑器全部的开始、结束 point
-
-    if (Point.equals(start1, start2) && Point.equals(end1, end2)) {
-      return true
-    }
-    return false
-  }
-
-  /**
-   * 全选
-   */
-  e.selectAll = () => {
-    const start = Editor.start(e, [])
-    const end = Editor.end(e, [])
-
-    Transforms.select(e, {
-      anchor: start,
-      focus: end,
-    })
-  }
-
-  return e
-}
diff --git a/packages/core/src/i18n/index.ts b/packages/core/src/i18n/index.ts
deleted file mode 100644
index e9a3b9204..000000000
--- a/packages/core/src/i18n/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * @description i18n entry
- * @author wangfupeng
- */
-
-import i18next from 'i18next'
-
-// i18n nameSpace
-const NS = 'translation'
-
-i18next.init({
-  lng: 'zh-CN',
-  // debug: true,
-  resources: {}, // 资源为空,随后添加
-})
-
-/**
- * 添加多语言配置
- * @param lng 语言
- * @param resources 多语言配置
- */
-export function i18nAddResources(lng: string, resources: object) {
-  i18next.addResourceBundle(lng, NS, resources, true, true)
-}
-
-/**
- * 设置语言
- * @param lng 语言
- */
-export function i18nChangeLanguage(lng: string) {
-  i18next.changeLanguage(lng)
-}
-
-/**
- * 获取多语言配置
- * @param lng lang
- */
-export function i18nGetResources(lng: string) {
-  return i18next.getResourceBundle(lng, NS)
-}
-
-/**
- * 翻译
- */
-export const t = i18next.t.bind(i18next)
-
-export default i18next
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
deleted file mode 100644
index 87785e6bd..000000000
--- a/packages/core/src/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @description core index
- * @author wangfupeng
- */
-
-import './assets/index.less'
-
-import { RenderStyleFnType, IRenderElemConf } from './render/index'
-import { styleToHtmlFnType, IElemToHtmlConf } from './to-html/index'
-import { IPreParseHtmlConf, ParseStyleHtmlFnType, IParseElemHtmlConf } from './parse-html/index'
-import { IRegisterMenuConf } from './menus/index'
-import { IDomEditor } from './editor/interface'
-
-// 创建
-export * from './create/index'
-
-// config
-export { IEditorConfig, IToolbarConfig } from './config/interface'
-
-// editor 接口和 command
-export * from './editor/interface'
-export * from './editor/dom-editor'
-
-// 注册 render
-export * from './render/index'
-
-// 注册 toHtml
-export * from './to-html/index'
-
-// 注册 parseHtml
-export * from './parse-html/index'
-
-// menu 的接口、注册、方法等
-export * from './menus/index'
-
-// upload
-export * from './upload/index'
-
-// i18n
-export * from './i18n/index'
-
-export interface IModuleConf {
-  // 注册菜单
-  menus: Array<IRegisterMenuConf>
-
-  // 渲染 modal -> view
-  renderStyle: RenderStyleFnType
-  renderElems: Array<IRenderElemConf>
-
-  // to html
-  styleToHtml: styleToHtmlFnType
-  elemsToHtml: Array<IElemToHtmlConf>
-
-  // parse html
-  preParseHtml: Array<IPreParseHtmlConf>
-  parseStyleHtml: ParseStyleHtmlFnType
-  parseElemsHtml: Array<IParseElemHtmlConf>
-
-  // 注册插件
-  editorPlugin: <T extends IDomEditor>(editor: T) => T
-}
diff --git a/packages/core/src/menus/README.md b/packages/core/src/menus/README.md
deleted file mode 100644
index d4762eced..000000000
--- a/packages/core/src/menus/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# menus
-
-统一注册 menu ,menu 支持
-- classic toolbar
-- hovering toolbar
-- tooltip
-- contextMenu
diff --git a/packages/core/src/menus/bar-item/BaseButton.ts b/packages/core/src/menus/bar-item/BaseButton.ts
deleted file mode 100644
index 4f1f6c144..000000000
--- a/packages/core/src/menus/bar-item/BaseButton.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
- * @description base button class
- * @author wangfupeng
- */
-
-import { IButtonMenu, IDropPanelMenu, IModalMenu } from '../interface'
-import $, { Dom7Array } from '../../utils/dom'
-import { IBarItem, getEditorInstance } from './index'
-import { clearSvgStyle } from '../helpers/helpers'
-import { promiseResolveThen } from '../../utils/util'
-import { addTooltip } from './tooltip'
-
-abstract class BaseButton implements IBarItem {
-  readonly $elem: Dom7Array = $(`<div class="w-e-bar-item"></div>`)
-  protected readonly $button: Dom7Array = $(`<button type="button"></button>`)
-  menu: IButtonMenu | IDropPanelMenu | IModalMenu
-  private disabled = false
-
-  constructor(key: string, menu: IButtonMenu | IDropPanelMenu | IModalMenu, inGroup = false) {
-    this.menu = menu
-
-    // 验证 tag
-    const { tag, width } = menu
-    if (tag !== 'button') throw new Error(`Invalid tag '${tag}', expected 'button'`)
-
-    // ----------------- 初始化 dom -----------------
-    const { title, hotkey = '', iconSvg = '' } = menu
-    const { $button } = this
-    if (iconSvg) {
-      const $svg = $(iconSvg)
-      clearSvgStyle($svg) // 清理 svg 样式(扩展的菜单,svg 是不可控的,所以要清理一下)
-      $button.append($svg)
-    } else {
-      // 无 icon 则显示 title
-      $button.text(title)
-    }
-    addTooltip($button, iconSvg, title, hotkey, inGroup) // 设置 tooltip
-    if (inGroup && iconSvg) {
-      // in groupButton(且有 icon),显示 menu title
-      // 如果没有 icon ,上面已添加 title ,不用重复添加
-      $button.append($(`<span class="title">${title}</span>`))
-    }
-    if (width) {
-      $button.css('width', `${width}px`)
-    }
-    $button.attr('data-menu-key', key) // menu key
-    this.$elem.append($button)
-
-    // ----------------- 异步绑定事件 -----------------
-    promiseResolveThen(() => this.init())
-  }
-
-  private init() {
-    // 设置 button 属性
-    this.setActive()
-    this.setDisabled()
-
-    // button click
-    this.$button.on('click', e => {
-      e.preventDefault()
-      const editor = getEditorInstance(this)
-
-      editor.hidePanelOrModal() // 隐藏当前的各种 panel
-
-      if (this.disabled) return
-
-      this.exec() // 执行 menu.exec
-      this.onButtonClick() // 执行其他的逻辑
-    })
-  }
-
-  /**
-   * 执行 menu.exec
-   */
-  private exec() {
-    const editor = getEditorInstance(this)
-    const menu = this.menu
-    const value = menu.getValue(editor)
-    menu.exec(editor, value)
-  }
-
-  // 交给子类去扩展
-  abstract onButtonClick(): void
-
-  private setActive() {
-    const editor = getEditorInstance(this)
-    const { $button } = this
-    const active = this.menu.isActive(editor)
-
-    const className = 'active'
-    if (active) {
-      // 设置为 active
-      $button.addClass(className)
-    } else {
-      // 取消 active
-      $button.removeClass(className)
-    }
-  }
-
-  private setDisabled() {
-    const editor = getEditorInstance(this)
-    const { $button } = this
-    let disabled = this.menu.isDisabled(editor)
-
-    if (editor.selection == null || editor.isDisabled()) {
-      // 未选中,或者 readOnly ,强行设置为 disabled
-      disabled = true
-    }
-
-    // 永远 enable
-    if (this.menu.alwaysEnable) disabled = false
-
-    const className = 'disabled'
-    if (disabled) {
-      // 设置为 disabled
-      $button.addClass(className)
-    } else {
-      // 取消 disabled
-      $button.removeClass(className)
-    }
-
-    this.disabled = disabled // 记录下来
-  }
-
-  changeMenuState() {
-    this.setActive()
-    this.setDisabled()
-  }
-}
-
-export default BaseButton
diff --git a/packages/core/src/menus/bar-item/DropPanelButton.ts b/packages/core/src/menus/bar-item/DropPanelButton.ts
deleted file mode 100644
index 10c126193..000000000
--- a/packages/core/src/menus/bar-item/DropPanelButton.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * @description dropPanel button class
- * @author wangfupeng
- */
-
-import { IDropPanelMenu } from '../interface'
-import BaseButton from './BaseButton'
-import DropPanel from '../panel-and-modal/DropPanel'
-import { gen$downArrow } from '../helpers/helpers'
-import { getEditorInstance } from './index'
-
-class DropPanelButton extends BaseButton {
-  private dropPanel: DropPanel | null = null
-  menu: IDropPanelMenu
-
-  constructor(key: string, menu: IDropPanelMenu, inGroup = false) {
-    super(key, menu, inGroup)
-    this.menu = menu
-
-    if (menu.showDropPanel) {
-      const $arrow = gen$downArrow()
-      this.$button.append($arrow)
-    }
-  }
-
-  // button 点击之后
-  onButtonClick() {
-    if (this.menu.showDropPanel) {
-      this.handleDropPanel()
-    }
-  }
-
-  // 显示/隐藏 dropPanel
-  private handleDropPanel() {
-    const menu = this.menu
-    if (menu.getPanelContentElem == null) return
-    const editor = getEditorInstance(this)
-
-    if (this.dropPanel == null) {
-      // 初次创建
-      const dropPanel = new DropPanel(editor)
-      const contentElem = menu.getPanelContentElem(editor)
-      dropPanel.renderContent(contentElem)
-      dropPanel.appendTo(this.$elem)
-      dropPanel.show()
-
-      // 记录下来,防止重复创建
-      this.dropPanel = dropPanel
-    } else {
-      // 不是初次创建
-      const dropPanel = this.dropPanel
-      if (dropPanel.isShow) {
-        // 当前处于显示状态,则隐藏
-        dropPanel.hide()
-      } else {
-        // 当前未处于显示状态,则重新渲染内容 ,并显示
-        const contentElem = menu.getPanelContentElem(editor)
-        dropPanel.renderContent(contentElem)
-        dropPanel.show()
-      }
-    }
-
-    // 判断 dropPanel 的位置:在菜单右侧/左侧
-    const dropPanel = this.dropPanel
-    if (dropPanel.isShow) {
-      const $menu = this.$elem
-      const { left } = $menu.offset() // 菜单元素 left
-
-      const $toolbar = $menu.parents('.w-e-bar')
-      const { left: toolbarLeft } = $toolbar.offset() // toolbar left
-      const toolbarWidth = $toolbar.width() // toolbar width
-      const halfToolbarWidth = toolbarWidth / 2 // toolbar width 的 1/2
-
-      if (left - toolbarLeft >= halfToolbarWidth) {
-        // 菜单在 toolbar 的右半部分,则 dropPanel 要显示在菜单左侧
-        dropPanel.$elem.css({
-          left: 'none',
-          right: '0',
-        })
-      } else {
-        // 菜单在 toolbar 左半部分,则 dropPanel 显示在菜单右侧
-        dropPanel.$elem.css({
-          left: '0',
-          right: 'none',
-        })
-      }
-    }
-  }
-}
-
-export default DropPanelButton
diff --git a/packages/core/src/menus/bar-item/GroupButton.ts b/packages/core/src/menus/bar-item/GroupButton.ts
deleted file mode 100644
index a9d8d3cb1..000000000
--- a/packages/core/src/menus/bar-item/GroupButton.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * @description group button class
- * @author wangfupeng
- */
-
-import { gen$downArrow } from '../helpers/helpers'
-import $, { Dom7Array } from '../../utils/dom'
-import { IMenuGroup } from '../interface'
-import { clearSvgStyle } from '../helpers/helpers'
-import { IBarItem } from './index'
-class GroupButton {
-  readonly $elem: Dom7Array = $(`<div class="w-e-bar-item w-e-bar-item-group"></div>`)
-  private readonly $container: Dom7Array = $('<div class="w-e-bar-item-menus-container"></div>')
-  readonly $button = $(`<button type="button"></button>`)
-
-  constructor(menu: IMenuGroup) {
-    const { key, iconSvg, title /*, menuKeys = [] */ } = menu
-    const { $elem, $button } = this
-
-    // button
-    if (iconSvg) {
-      const $svg = $(iconSvg)
-      clearSvgStyle($svg) // 清理 svg 样式(扩展的菜单,svg 是不可控的,所以要清理一下)
-      $button.append($svg)
-    } else {
-      // 无 icon 则显示 title
-      $button.text(title)
-    }
-    $button.attr('data-menu-key', key) // menu key
-
-    const $arrow = gen$downArrow()
-    $button.append($arrow)
-    $elem.append($button)
-
-    // menu container
-    const { $container } = this
-    $elem.append($container)
-
-    // 监听 container 内容变化,以判断 $button 是否应该禁用
-    const observer = this.createObserver()
-    this.observe(observer)
-  }
-
-  appendBarItem(barItem: IBarItem) {
-    const { $elem } = barItem
-    this.$container.append($elem)
-  }
-
-  private observe(observer: MutationObserver) {
-    const { $container } = this
-    observer.observe($container[0], { childList: true, subtree: true, attributes: true })
-  }
-
-  private createObserver(): MutationObserver {
-    const { $container, $button } = this
-
-    const observer = new MutationObserver(() => {
-      // 找出 container 下所有的 button
-      const $buttons = $container.find('button')
-      const buttonsLength = $buttons.length
-      if (buttonsLength === 0) return
-
-      // 找出所有 disabled 的 button
-      let disabledButtonsLength = 0
-      $buttons.each(btn => {
-        const $btn = $(btn)
-        if ($btn.hasClass('disabled')) {
-          disabledButtonsLength++
-        }
-      })
-
-      // 判断 group button 是否应该被禁用
-      observer.disconnect()
-      if (disabledButtonsLength === buttonsLength) {
-        // 如果 container 所有的 button 都已经 disabled ,则当前的 GroupButton 也需要 disabled
-        $button.addClass('disabled')
-      } else {
-        // 否则,取消当前的 GroupButton disabled
-        $button.removeClass('disabled')
-      }
-      this.observe(observer)
-    })
-
-    return observer
-  }
-}
-
-export default GroupButton
diff --git a/packages/core/src/menus/bar-item/ModalButton.ts b/packages/core/src/menus/bar-item/ModalButton.ts
deleted file mode 100644
index 999032152..000000000
--- a/packages/core/src/menus/bar-item/ModalButton.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * @description modal button class
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-import { IModalMenu, IPositionStyle } from '../interface'
-import BaseButton from './BaseButton'
-import Modal from '../panel-and-modal/Modal'
-import { getEditorInstance } from './index'
-import { getPositionBySelection, getPositionByNode, correctPosition } from '../helpers/position'
-import { DomEditor } from '../../editor/dom-editor'
-import $ from '../../utils/dom'
-
-class ModalButton extends BaseButton {
-  private $body = $('body')
-  private modal: Modal | null = null
-  menu: IModalMenu
-
-  constructor(key: string, menu: IModalMenu, inGroup = false) {
-    super(key, menu, inGroup)
-    this.menu = menu
-  }
-
-  onButtonClick() {
-    if (this.menu.showModal) {
-      this.handleModal()
-    }
-  }
-
-  /**
-   * 获取 modal 定位
-   */
-  private getPosition(): Partial<IPositionStyle> {
-    const editor = getEditorInstance(this)
-    const positionNode = this.menu.getModalPositionNode(editor)
-
-    if (Element.isElement(positionNode)) {
-      // elem node ,按 node 定位
-      return getPositionByNode(editor, positionNode, 'modal')
-    }
-
-    // 其他情况(如 positionNode == null 或是 text node)则按选区定位
-    return getPositionBySelection(editor)
-  }
-
-  // 显示/隐藏 modal
-  private handleModal() {
-    const editor = getEditorInstance(this)
-    const menu = this.menu
-
-    if (this.modal == null) {
-      // 初次创建
-      const modal = new Modal(editor, menu.modalWidth)
-      this.renderAndShowModal(modal, true)
-
-      // 记录下来,防止重复创建
-      this.modal = modal
-    } else {
-      // 不是初次创建
-      const modal = this.modal
-      if (modal.isShow) {
-        // 当前处于显示状态,则隐藏
-        modal.hide()
-      } else {
-        // 当前未处于显示状态,则重新渲染内容 ,并显示
-        this.renderAndShowModal(modal, false)
-      }
-    }
-  }
-
-  /**
-   * 渲染并显示 modal
-   * @param modal modal
-   * @param firstTime 是否第一次显示 modal
-   */
-  private renderAndShowModal(modal: Modal, firstTime: boolean = false) {
-    const editor = getEditorInstance(this)
-    const menu = this.menu
-    if (menu.getModalContentElem == null) return
-
-    const textarea = DomEditor.getTextarea(editor)
-    const toolbar = DomEditor.getToolbar(editor)
-    const { modalAppendToBody } = toolbar?.getConfig() || {}
-
-    const contentElem = menu.getModalContentElem(editor)
-    modal.renderContent(contentElem)
-
-    if (modalAppendToBody) {
-      // appendTo body 时,用户自己设置 modal 定位
-      modal.setStyle({ left: '0', right: '0' })
-    } else {
-      // 计算并设置 modal position
-      const positionStyle = this.getPosition()
-      modal.setStyle(positionStyle)
-    }
-
-    if (firstTime) {
-      if (modalAppendToBody) {
-        modal.appendTo(this.$body)
-      } else {
-        modal.appendTo(textarea.$textAreaContainer)
-      }
-    }
-
-    modal.show()
-
-    if (!modalAppendToBody) {
-      // 修正 modal 定位,避免超出 textContainer 边界( appendTo body 则不用设置,用户自己设置 )
-      correctPosition(editor, modal.$elem)
-    }
-
-    // 让 editor 失焦,否则点击 modal 触发 onChange 会导致 modal 隐藏
-    setTimeout(() => {
-      editor.blur()
-    })
-  }
-}
-
-export default ModalButton
diff --git a/packages/core/src/menus/bar-item/Select.ts b/packages/core/src/menus/bar-item/Select.ts
deleted file mode 100644
index f5d3f1d0e..000000000
--- a/packages/core/src/menus/bar-item/Select.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-/**
- * @description select
- * @author wangfupeng
- */
-
-import $, { Dom7Array } from '../../utils/dom'
-import { IBarItem, getEditorInstance } from './index'
-import { IOption, ISelectMenu } from '../interface'
-import SelectList from '../panel-and-modal/SelectList'
-import { gen$downArrow } from '../helpers/helpers'
-import { promiseResolveThen } from '../../utils/util'
-import { addTooltip } from './tooltip'
-
-// 根据 option value 获取 text
-function getOptionText(options: IOption[], value: string): string {
-  const length = options.length
-  let text = ''
-  for (let i = 0; i < length; i++) {
-    const opt = options[i]
-    if (opt.value === value) {
-      text = opt.text
-      break
-    }
-  }
-  return text
-}
-
-class BarItemSelect implements IBarItem {
-  readonly $elem: Dom7Array = $(`<div class="w-e-bar-item"></div>`)
-  private readonly $button: Dom7Array = $(`<button type="button" class="select-button"></button>`)
-  menu: ISelectMenu
-  private disabled = false
-  private selectList: SelectList | null = null
-
-  constructor(key: string, menu: ISelectMenu, inGroup = false) {
-    // 验证 tag
-    const { tag, title, width, iconSvg = '', hotkey = '' } = menu
-    if (tag !== 'select') throw new Error(`Invalid tag '${tag}', expected 'select'`)
-
-    // 初始化 dom
-    const $button = this.$button
-    if (width) {
-      $button.css('width', `${width}px`)
-    }
-    $button.attr('data-menu-key', key) // menu key
-    addTooltip($button, iconSvg, title, hotkey, inGroup) // 设置 tooltip
-    this.$elem.append($button)
-
-    this.menu = menu
-
-    // 异步绑定事件
-    promiseResolveThen(() => this.init())
-  }
-
-  private init() {
-    // 设置 select 属性
-    this.setSelectedValue()
-
-    // select button click
-    this.$button.on('click', (e: Event) => {
-      e.preventDefault()
-      const editor = getEditorInstance(this)
-      editor.hidePanelOrModal() // 隐藏当前的各种 panel
-      this.trigger()
-    })
-  }
-
-  private trigger() {
-    const editor = getEditorInstance(this)
-
-    if (editor.isDisabled()) return
-    if (this.disabled) return
-
-    const menu = this.menu
-
-    // 显示下拉列表
-    if (this.selectList == null) {
-      // 初次创建,渲染 list 并显示
-      this.selectList = new SelectList(editor, menu.selectPanelWidth)
-      const selectList = this.selectList
-      const options = menu.getOptions(editor)
-      selectList.renderList(options)
-      selectList.appendTo(this.$elem)
-      selectList.show()
-
-      // 初次创建,绑定事件
-      selectList.$elem.on('click', 'li', (e: Event) => {
-        const { target } = e
-        if (target == null) return
-
-        e.preventDefault()
-        const $li = $(target)
-        const val = $li.attr('data-value')
-        this.onChange(val)
-      })
-    } else {
-      // 不是初次创建
-      const selectList = this.selectList
-      if (selectList.isShow) {
-        // 当前处于显示状态,则隐藏
-        selectList.hide()
-      } else {
-        // 当前未处于显示状态,则重新渲染 list ,并显示
-        const options = menu.getOptions(editor) // 每次都要重新获取 options ,因为选中项可能会变化
-        selectList.renderList(options)
-        selectList.show()
-      }
-    }
-  }
-
-  private onChange(value: string) {
-    const editor = getEditorInstance(this)
-    const menu = this.menu
-    menu.exec && menu.exec(editor, value)
-  }
-
-  private setSelectedValue() {
-    const editor = getEditorInstance(this)
-    const menu = this.menu
-    const value = menu.getValue(editor)
-
-    const options = menu.getOptions(editor)
-    const optText = getOptionText(options, value.toString())
-
-    const $button = this.$button
-    const $downArrow = gen$downArrow() // 向下的箭头图标
-    $button.empty()
-    $button.text(optText)
-    $button.append($downArrow)
-  }
-
-  private setDisabled() {
-    const editor = getEditorInstance(this)
-    const menu = this.menu
-    let disabled = menu.isDisabled(editor)
-    const $button = this.$button
-
-    if (editor.selection == null || editor.isDisabled()) {
-      // 未选中,或者 readOnly ,强行设置为 disabled
-      disabled = true
-    }
-
-    const className = 'disabled'
-    if (disabled) {
-      // 设置为 disabled
-      $button.addClass(className)
-    } else {
-      // 取消 disabled
-      $button.removeClass(className)
-    }
-
-    this.disabled = disabled // 记录下来
-  }
-
-  changeMenuState() {
-    this.setSelectedValue()
-    this.setDisabled()
-  }
-}
-
-export default BarItemSelect
diff --git a/packages/core/src/menus/bar-item/SimpleButton.ts b/packages/core/src/menus/bar-item/SimpleButton.ts
deleted file mode 100644
index 0ee7180a3..000000000
--- a/packages/core/src/menus/bar-item/SimpleButton.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @description button class
- * @author wangfupeng
- */
-
-import { IButtonMenu } from '../interface'
-import BaseButton from './BaseButton'
-
-class SimpleButton extends BaseButton {
-  constructor(key: string, menu: IButtonMenu, inGroup = false) {
-    super(key, menu, inGroup)
-  }
-  onButtonClick() {
-    // menu.exec 已经在 BaseButton 实现了
-    // 所以,此处不用做任何逻辑
-  }
-}
-
-export default SimpleButton
diff --git a/packages/core/src/menus/bar-item/index.ts b/packages/core/src/menus/bar-item/index.ts
deleted file mode 100644
index 2c892475e..000000000
--- a/packages/core/src/menus/bar-item/index.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * @description bar item
- * @author wangfupeng
- */
-
-import { Dom7Array } from '../../utils/dom'
-import { IButtonMenu, ISelectMenu, IDropPanelMenu, IModalMenu, IMenuGroup } from '../interface'
-import { IDomEditor } from '../../editor/interface'
-import { BAR_ITEM_TO_EDITOR } from '../../utils/weak-maps'
-import SimpleButton from './SimpleButton'
-import DropPanelButton from './DropPanelButton'
-import ModalButton from './ModalButton'
-import Select from './Select'
-import GroupButton from './GroupButton'
-
-type MenuType = IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu
-
-export interface IBarItem {
-  $elem: Dom7Array
-  menu: MenuType
-  changeMenuState: () => void
-}
-
-// menu -> barItem
-const MENU_TO_BAR_ITEM = new WeakMap<MenuType, IBarItem>()
-
-export function getEditorInstance(item: IBarItem): IDomEditor {
-  const editor = BAR_ITEM_TO_EDITOR.get(item)
-  if (editor == null) throw new Error('Can not get editor instance')
-  return editor
-}
-
-/**
- * 创建 bar button/select
- * @param key menu key
- * @param menu menu
- * @param inGroup 在 groupButton 中
- */
-export function createBarItem(key: string, menu: MenuType, inGroup: boolean = false): IBarItem {
-  // 尝试从缓存获取
-  let barItem = MENU_TO_BAR_ITEM.get(menu)
-  if (barItem) return barItem
-
-  // 缓存没有则创建
-  const { tag } = menu
-  if (tag === 'button') {
-    // @ts-ignore
-    const { showDropPanel, showModal } = menu
-    if (showDropPanel) {
-      barItem = new DropPanelButton(key, menu as IDropPanelMenu, inGroup)
-    } else if (showModal) {
-      barItem = new ModalButton(key, menu as IModalMenu, inGroup)
-    } else {
-      barItem = new SimpleButton(key, menu, inGroup)
-    }
-  }
-  if (tag === 'select') {
-    barItem = new Select(key, menu as ISelectMenu, inGroup)
-  }
-
-  if (barItem == null) throw new Error(`Invalid tag in menu ${JSON.stringify(menu)}`)
-
-  // 记录缓存
-  MENU_TO_BAR_ITEM.set(menu, barItem)
-
-  return barItem
-}
-
-export function createBarItemGroup(menu: IMenuGroup): GroupButton {
-  return new GroupButton(menu)
-}
diff --git a/packages/core/src/menus/bar-item/tooltip.ts b/packages/core/src/menus/bar-item/tooltip.ts
deleted file mode 100644
index 5d1b50327..000000000
--- a/packages/core/src/menus/bar-item/tooltip.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @description tooltip 功能
- * @author wangfupeng
- */
-
-import { Dom7Array } from '../../utils/dom'
-import { IS_APPLE } from '../../utils/ua'
-
-export function addTooltip(
-  $button: Dom7Array,
-  iconSvg: string,
-  title: string,
-  hotkey: string,
-  inGroup = false
-) {
-  if (!iconSvg) {
-    // 没有 icon 直接显示 title ,不用 tooltip
-    return
-  }
-
-  if (hotkey) {
-    const fnKey = IS_APPLE ? 'cmd' : 'ctrl' // mac OS 转换为 cmd ,windows 转换为 ctrl
-    hotkey = hotkey.replace('mod', fnKey)
-  }
-
-  if (inGroup) {
-    // in groupButton ,tooltip 只显示 快捷键
-    if (hotkey) {
-      $button.attr('data-tooltip', hotkey)
-      $button.addClass('w-e-menu-tooltip-v5')
-      $button.addClass('tooltip-right') // tooltip 显示在右侧
-    }
-  } else {
-    // 非 in groupButton ,正常实现 tooltip
-    const tooltip = hotkey ? `${title}\n${hotkey}` : title
-    $button.attr('data-tooltip', tooltip)
-    $button.addClass('w-e-menu-tooltip-v5')
-  }
-}
diff --git a/packages/core/src/menus/bar/HoverBar.ts b/packages/core/src/menus/bar/HoverBar.ts
deleted file mode 100644
index db2f9df46..000000000
--- a/packages/core/src/menus/bar/HoverBar.ts
+++ /dev/null
@@ -1,340 +0,0 @@
-/**
- * @description hover bar class
- * @author wangfupeng
- */
-
-import debounce from 'lodash.debounce'
-import { Editor, Node, Element, Text, Path, Range } from 'slate'
-import $ from '../../utils/dom'
-import { MENU_ITEM_FACTORIES } from '../register'
-import { promiseResolveThen } from '../../utils/util'
-import { IDomEditor } from '../../editor/interface'
-import { DomEditor } from '../../editor/dom-editor'
-import { HOVER_BAR_TO_EDITOR, BAR_ITEM_TO_EDITOR } from '../../utils/weak-maps'
-import { IBarItem, createBarItem } from '../bar-item/index'
-import { gen$barItemDivider } from '../helpers/helpers'
-import { getPositionBySelection, getPositionByNode, correctPosition } from '../helpers/position'
-import { IButtonMenu, ISelectMenu, IDropPanelMenu, IModalMenu } from '../interface'
-import { CustomElement } from '../../../../custom-types'
-
-type MenuType = IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu
-
-/**
- * 是否选中了 text (用于 text hoverbarKeys)
- * @param editor editor
- * @param n node
- */
-function isSelectedText(editor: IDomEditor, n: Node) {
-  const { selection } = editor
-  if (selection == null) return false // 无选区
-  if (Range.isCollapsed(selection)) return false // 未选中文字,选区的是折叠的
-
-  const selectedElems = DomEditor.getSelectedElems(editor)
-  const notMatch = selectedElems.some((elem: CustomElement) => {
-    if (editor.isVoid(elem)) return true
-
-    const { type } = elem
-    if (['pre', 'code', 'table'].includes(type)) return true
-  })
-  if (notMatch) return false
-
-  if (Text.isText(n)) return true // 匹配 text node
-  return false
-}
-
-class HoverBar {
-  private readonly $elem = $('<div class="w-e-bar w-e-bar-hidden w-e-hover-bar"></div>')
-  private menus: { [key: string]: MenuType } = {}
-  private hoverbarItems: IBarItem[] = []
-  private prevSelectedNode: Node | null = null // 上一次选中的 node
-  private isShow = false
-
-  constructor() {
-    // 异步,否则获取不到 DOM 和 editor
-    promiseResolveThen(() => {
-      const editor = this.getEditorInstance()
-
-      // 将 elem 渲染为 DOM
-      const $elem = this.$elem
-      // @ts-ignore
-      $elem.on('mousedown', e => e.preventDefault(), { passive: false }) // 防止点击失焦
-      const textarea = DomEditor.getTextarea(editor)
-      textarea.$textAreaContainer.append($elem)
-
-      // 绑定 editor onchange
-      editor.on('change', this.changeHoverbarState)
-
-      // 滚动时隐藏
-      const hideAndClean = this.hideAndClean.bind(this)
-      editor.on('scroll', hideAndClean)
-
-      // fullScreen 时隐藏
-      editor.on('fullScreen', hideAndClean)
-      editor.on('unFullScreen', hideAndClean)
-    })
-  }
-
-  getMenus() {
-    return this.menus
-  }
-
-  hideAndClean() {
-    const $elem = this.$elem
-    $elem.removeClass('w-e-bar-show').addClass('w-e-bar-hidden')
-
-    // 及时先清空内容,否则影响下次
-    this.hoverbarItems = []
-    $elem.empty()
-
-    this.isShow = false
-  }
-
-  /**
-   * 判断 hoverbar 是否在网页下部?
-   * 如果是,SelectList 和 DropPanel 要显示在 hoverbar 上面
-   */
-  private checkPositionBottom() {
-    const $elem = this.$elem
-
-    let isBottom = false
-    const { innerHeight } = window
-    const minDistance = 360 // 距离底部最小 360px
-    if (innerHeight && innerHeight >= minDistance) {
-      const { bottom } = $elem[0].getBoundingClientRect()
-      if (innerHeight - bottom < minDistance) {
-        // hoverbar 距离底部不足 360
-        isBottom = true
-      }
-    }
-    if (isBottom) {
-      $elem.addClass('w-e-bar-bottom')
-    } else {
-      $elem.removeClass('w-e-bar-bottom')
-    }
-  }
-
-  private show() {
-    this.$elem.removeClass('w-e-bar-hidden').addClass('w-e-bar-show')
-    this.isShow = true
-
-    // 判断 hoverbar 是否在网页下部
-    this.checkPositionBottom()
-  }
-
-  private changeItemsState() {
-    promiseResolveThen(() => {
-      this.hoverbarItems.forEach(item => {
-        item.changeMenuState()
-      })
-    })
-  }
-
-  private registerItems(menuKeys: string[]) {
-    const $elem = this.$elem
-
-    menuKeys.forEach(key => {
-      if (key === '|') {
-        // 分割线
-        const $divider = gen$barItemDivider()
-        $elem.append($divider)
-        return
-      }
-
-      // 正常菜单
-      this.registerSingleItem(key)
-    })
-  }
-
-  // 注册单个 bar item
-  private registerSingleItem(key: string) {
-    const editor = this.getEditorInstance()
-
-    // 尝试从缓存中获取
-    const { menus } = this
-    let menu = menus[key]
-
-    if (menu == null) {
-      // 缓存获取失败,则重新创建
-      const factory = MENU_ITEM_FACTORIES[key]
-      if (factory == null) {
-        throw new Error(`Not found menu item factory by key '${key}'`)
-      }
-      if (typeof factory !== 'function') {
-        throw new Error(`Menu item factory (key='${key}') is not a function`)
-      }
-
-      // 创建 barItem 并记录缓存
-      menu = factory()
-      menus[key] = menu
-    }
-
-    const barItem = createBarItem(key, menu)
-    this.hoverbarItems.push(barItem)
-
-    // 保存 barItem 和 editor 的关系
-    BAR_ITEM_TO_EDITOR.set(barItem, editor)
-
-    // 添加 DOM
-    const $elem = this.$elem
-    $elem.append(barItem.$elem)
-  }
-
-  private setPosition(node: Node) {
-    const editor = this.getEditorInstance()
-    const $elem = this.$elem
-    $elem.attr('style', '') // 先清空 style ,再重新设置
-
-    if (Element.isElement(node)) {
-      // 根据 elem node 定位
-      const positionStyle = getPositionByNode(editor, node, 'bar')
-      $elem.css(positionStyle)
-      correctPosition(editor, $elem) // 修正 position 避免超出 textContainer 边界
-      return
-    }
-    if (Text.isText(node)) {
-      // text node ,根据选区定位
-      const positionStyle = getPositionBySelection(editor)
-      $elem.css(positionStyle)
-      correctPosition(editor, $elem) // 修正 position 避免超出 textContainer 边界
-      return
-    }
-    // 其他情况,非 elem 非 text ,不处理
-    throw new Error('hoverbar.setPosition error, current selected node is not elem nor text')
-  }
-
-  /**
-   * 获取选中的 node ,以及对应的 menu keys
-   */
-  private getSelectedNodeAndMenuKeys(): { node: Node; menuKeys: string[] } | null {
-    const editor = this.getEditorInstance()
-
-    if (editor.selection == null) {
-      return null
-    }
-
-    // 获取 hover bar 配置
-    const keysConf = this.getHoverbarKeysConf()
-    // 开始匹配
-    let matchNode: Node | null = null
-    let matchMenuKeys: string[] = []
-
-    for (const elemType in keysConf) {
-      const conf = keysConf[elemType]
-      const { match, menuKeys = [] } = conf
-
-      // 定义了 match 则用 match 。未定义 match 则用 elemType
-      const matchFn = match
-        ? match
-        : (editor: IDomEditor, n: Node) => DomEditor.checkNodeType(n, elemType)
-
-      const [nodeEntry] = Editor.nodes(editor, {
-        match: n => matchFn(editor, n),
-        universal: true,
-      })
-
-      // 匹配成功(找到第一个就停止,不再继续找了)
-      if (nodeEntry != null) {
-        matchNode = nodeEntry[0]
-        matchMenuKeys = menuKeys
-        break
-      }
-    }
-
-    // 未匹配成功
-    if (matchNode == null || matchMenuKeys.length === 0) return null
-
-    // 匹配成功
-    return {
-      node: matchNode,
-      menuKeys: matchMenuKeys,
-    }
-  }
-
-  /**
-   * editor onChange 时触发(涉及 DOM 操作,加防抖)
-   */
-  changeHoverbarState = debounce(() => {
-    // 获取选中的 node ,以及对应的 menu keys
-    const { isShow } = this
-    const { node = null, menuKeys = [] } = this.getSelectedNodeAndMenuKeys() || {}
-
-    if (node != null) {
-      this.changeItemsState() // 更新菜单状态
-    }
-
-    if (node && Element.isElement(node)) {
-      // 选中了 elem node(不可以是 text node)
-      if (isShow) {
-        // hoverbar 当前已显示
-        const samePath = this.isSamePath(node, this.prevSelectedNode)
-        if (samePath) {
-          // 和之前选中的 node path 相同 —— 满足这些条件,即终止
-          return
-        }
-      }
-    }
-
-    // 选择了新的 node(或选区是 null),先隐藏
-    this.hideAndClean()
-
-    if (node != null) {
-      // 选中了新的 node
-      this.registerItems(menuKeys)
-      this.setPosition(node)
-      this.show()
-    }
-
-    // 最后,重新记录 prevSelectedNode ,重要
-    this.prevSelectedNode = node
-  }, 200)
-
-  private getEditorInstance(): IDomEditor {
-    const editor = HOVER_BAR_TO_EDITOR.get(this)
-    if (editor == null) throw new Error('Can not get editor instance')
-    return editor
-  }
-
-  private getHoverbarKeysConf() {
-    const editor = this.getEditorInstance()
-    const { hoverbarKeys = {} } = editor.getConfig()
-
-    const textHoverbarKeys = hoverbarKeys.text
-    if (textHoverbarKeys && textHoverbarKeys.match == null) {
-      // 对 text hoverbarKeys 增加 match 函数(否则无法判断是否选中了 text)
-      textHoverbarKeys.match = isSelectedText
-    }
-
-    return hoverbarKeys
-  }
-
-  /**
-   * 检查两个 node 是否 path 相等
-   */
-  private isSamePath(node1: Node | null, node2: Node | null) {
-    if (node1 == null || node2 == null) {
-      return false
-    }
-
-    const path1 = DomEditor.findPath(null, node1)
-    const path2 = DomEditor.findPath(null, node2)
-    const res = Path.equals(path1, path2)
-    return res
-  }
-
-  /**
-   * 销毁 hoverbar
-   */
-  destroy() {
-    // fix https://github.com/wangeditor-team/wangEditor-v5/issues/410
-    this.changeHoverbarState.cancel()
-    // 销毁 DOM
-    this.$elem.remove()
-
-    // 清空属性
-    this.menus = {}
-    this.hoverbarItems = []
-    this.prevSelectedNode = null
-  }
-}
-
-export default HoverBar
diff --git a/packages/core/src/menus/bar/Toolbar.ts b/packages/core/src/menus/bar/Toolbar.ts
deleted file mode 100644
index 052451644..000000000
--- a/packages/core/src/menus/bar/Toolbar.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-/**
- * @description classic toolbar
- * @author wangfupeng
- */
-
-import debounce from 'lodash.debounce'
-import clonedeep from 'lodash.clonedeep'
-import $, { Dom7Array, DOMElement } from '../../utils/dom'
-import { MENU_ITEM_FACTORIES } from '../register'
-import { promiseResolveThen } from '../../utils/util'
-import { TOOLBAR_TO_EDITOR, BAR_ITEM_TO_EDITOR } from '../../utils/weak-maps'
-import { IDomEditor } from '../../editor/interface'
-import { IBarItem, createBarItem, createBarItemGroup } from '../bar-item/index'
-import { gen$barItemDivider } from '../helpers/helpers'
-import { IMenuGroup, IButtonMenu, ISelectMenu, IDropPanelMenu, IModalMenu } from '../interface'
-import GroupButton from '../bar-item/GroupButton'
-import { IToolbarConfig } from '../../config/interface'
-
-type MenuType = IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu
-
-class Toolbar {
-  $box: Dom7Array
-  private readonly $toolbar: Dom7Array = $(`<div class="w-e-bar w-e-bar-show w-e-toolbar"></div>`)
-  private menus: { [key: string]: MenuType } = {}
-  private toolbarItems: IBarItem[] = []
-  private config: Partial<IToolbarConfig> = {}
-
-  constructor(boxSelector: string | DOMElement, config: Partial<IToolbarConfig>) {
-    this.config = config
-
-    // @ts-ignore 初始化 DOM
-    const $box = $(boxSelector)
-    if ($box.length === 0) {
-      throw new Error(`Cannot find toolbar DOM by selector '${boxSelector}'`)
-    }
-    this.$box = $box
-    const $toolbar = this.$toolbar
-    // @ts-ignore
-    $toolbar.on('mousedown', e => e.preventDefault(), { passive: false }) // 防止点击失焦
-    $box.append($toolbar)
-
-    // 异步,否则拿不到 editor 实例
-    promiseResolveThen(() => {
-      // 注册 items
-      this.registerItems()
-
-      // 创建完,先模拟一次 onchange
-      this.changeToolbarState()
-
-      // 监听 editor onchange
-      const editor = this.getEditorInstance()
-      editor.on('change', this.changeToolbarState)
-    })
-  }
-
-  getMenus() {
-    return this.menus
-  }
-
-  getConfig() {
-    return this.config
-  }
-
-  // 注册 toolbarItems
-  private registerItems() {
-    let prevKey = ''
-    const $toolbar = this.$toolbar
-    const { toolbarKeys = [], insertKeys = { index: 0, keys: [] }, excludeKeys = [] } = this.config // 格式如 ['a', '|', 'b', 'c', '|', 'd']
-
-    // 新插入菜单
-    const toolbarKeysWithInsertedKeys = clonedeep(toolbarKeys)
-    if (insertKeys.keys.length > 0) {
-      if (typeof insertKeys.keys === 'string') {
-        insertKeys.keys = [insertKeys.keys]
-      }
-
-      insertKeys.keys.forEach((k, i) => {
-        toolbarKeysWithInsertedKeys.splice(insertKeys.index + i, 0, k)
-      })
-    }
-
-    // 排除某些菜单
-    const filteredKeys = toolbarKeysWithInsertedKeys.filter(key => {
-      if (typeof key === 'string') {
-        // 普通菜单
-        if (excludeKeys.includes(key)) return false
-      } else {
-        // group
-        if (excludeKeys.includes(key.key)) return false
-      }
-      return true
-    })
-    const filteredKeysLength = filteredKeys.length
-
-    // 开始注册菜单
-    filteredKeys.forEach((key, index) => {
-      if (key === '|') {
-        // 第一个就是 `|` ,忽略
-        if (index === 0) return
-
-        // 最后一个是 `|` ,忽略
-        if (index + 1 === filteredKeysLength) return
-
-        // 多个紧挨着的 `|` ,只显示一个
-        if (prevKey === '|') return
-
-        // 分割线
-        const $divider = gen$barItemDivider()
-        $toolbar.append($divider)
-        prevKey = key
-        return
-      }
-
-      // 正常菜单
-      if (typeof key === 'string') {
-        this.registerSingleItem(key, this)
-        prevKey = key
-        return
-      }
-
-      // 菜单组
-      this.registerGroup(key)
-      prevKey = 'group'
-    })
-  }
-
-  // 注册菜单组
-  private registerGroup(menu: IMenuGroup) {
-    const $toolbar = this.$toolbar
-    const group = createBarItemGroup(menu)
-    const { menuKeys = [] } = menu
-    const { excludeKeys = [] } = this.config
-
-    // 注册子菜单
-    menuKeys.forEach(key => {
-      if (excludeKeys.includes(key)) return
-      this.registerSingleItem(
-        key,
-        group // 将子菜单,添加到 group
-      )
-    })
-
-    // 添加到 DOM
-    $toolbar.append(group.$elem)
-  }
-
-  // 注册单个 toolbarItem
-  private registerSingleItem(key: string, container: GroupButton | Toolbar) {
-    const editor = this.getEditorInstance()
-    const inGroup = container instanceof GroupButton // 要添加到 groupButton
-
-    // 尝试从缓存中获取
-    const { menus } = this
-    let menu = menus[key]
-
-    if (menu == null) {
-      // 缓存中没有,则创建
-      const factory = MENU_ITEM_FACTORIES[key]
-      if (factory == null) {
-        throw new Error(`Not found menu item factory by key '${key}'`)
-      }
-      if (typeof factory !== 'function') {
-        throw new Error(`Menu item factory (key='${key}') is not a function`)
-      }
-
-      // 创建 toolbarItem 并记录缓存
-      menu = factory()
-      menus[key] = menu
-    } else {
-      console.warn(`Duplicated toolbar menu key '${key}'\n重复注册了菜单栏 menu '${key}'`)
-    }
-
-    const toolbarItem = createBarItem(key, menu, inGroup)
-    this.toolbarItems.push(toolbarItem)
-
-    // 保存 toolbarItem 和 editor 的关系
-    BAR_ITEM_TO_EDITOR.set(toolbarItem, editor)
-
-    // 添加 DOM
-    if (inGroup) {
-      // barItem 是 groupButton
-      const group = container as GroupButton
-      group.appendBarItem(toolbarItem)
-    } else {
-      // barItem 添加到 toolbar
-      const toolbar = container as Toolbar
-      toolbar.$toolbar.append(toolbarItem.$elem)
-    }
-  }
-
-  private getEditorInstance(): IDomEditor {
-    const editor = TOOLBAR_TO_EDITOR.get(this)
-    if (editor == null) throw new Error('Can not get editor instance')
-    return editor
-  }
-
-  /**
-   * editor onChange 时触发(涉及 DOM 操作,加防抖)
-   */
-  changeToolbarState = debounce(() => {
-    this.toolbarItems.forEach(toolbarItem => {
-      toolbarItem.changeMenuState()
-    })
-  }, 200)
-
-  /**
-   * 销毁 toolbar
-   */
-  destroy() {
-    // 销毁 DOM
-    this.$toolbar.remove()
-
-    // 清空属性
-    this.menus = {}
-    this.toolbarItems = []
-  }
-}
-
-export default Toolbar
diff --git a/packages/core/src/menus/helpers/helpers.ts b/packages/core/src/menus/helpers/helpers.ts
deleted file mode 100644
index ffdd5794e..000000000
--- a/packages/core/src/menus/helpers/helpers.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @description menu helpers
- * @author wangfupeng
- */
-
-import $, { Dom7Array } from '../../utils/dom'
-import { SVG_DOWN_ARROW } from '../../constants/svg'
-
-/**
- * 清理 svg 的样式
- * @param $elem svg elem
- */
-export function clearSvgStyle($elem: Dom7Array) {
-  $elem.removeAttr('width')
-  $elem.removeAttr('height')
-  $elem.removeAttr('fill')
-  $elem.removeAttr('class')
-  $elem.removeAttr('t')
-  $elem.removeAttr('p-id')
-
-  const children = $elem.children()
-  if (children.length) {
-    clearSvgStyle(children)
-  }
-}
-
-/**
- * 向下箭头 icon svg
- */
-export function gen$downArrow() {
-  const $downArrow = $(SVG_DOWN_ARROW)
-  return $downArrow
-}
-
-/**
- * bar item 分割线
- */
-export function gen$barItemDivider() {
-  return $('<div class="w-e-bar-divider"></div>')
-}
diff --git a/packages/core/src/menus/helpers/position.ts b/packages/core/src/menus/helpers/position.ts
deleted file mode 100644
index ae8d1efeb..000000000
--- a/packages/core/src/menus/helpers/position.ts
+++ /dev/null
@@ -1,287 +0,0 @@
-/**
- * @description menu position helpers
- * @author wangfupeng
- */
-
-import { Node, Element } from 'slate'
-import { Dom7Array, getFirstVoidChild } from '../../utils/dom'
-import { IDomEditor } from '../../editor/interface'
-import { DomEditor } from '../../editor/dom-editor'
-import { NODE_TO_ELEMENT } from '../../utils/weak-maps'
-import { IPositionStyle } from '../interface'
-import { promiseResolveThen } from '../../utils/util'
-
-/**
- * 获取 textContainer 尺寸和定位
- * @param editor editor
- */
-export function getTextContainerRect(editor: IDomEditor): {
-  top: number
-  left: number
-  width: number
-  height: number
-} | null {
-  const textarea = DomEditor.getTextarea(editor)
-
-  // 获取 textareaContainer
-  const $textareaContainer = textarea.$textAreaContainer
-  const width = $textareaContainer.width()
-  const height = $textareaContainer.height()
-  const { top, left } = $textareaContainer.offset()
-
-  return { top, left, width, height }
-}
-
-/**
- * 根据选区,计算定位(用于 modal hoverbar)
- * @param editor editor
- */
-export function getPositionBySelection(editor: IDomEditor): Partial<IPositionStyle> {
-  // 默认情况下 { top: 0, left: 0 }
-  const defaultStyle = { top: '0', left: '0' }
-
-  const { selection } = editor
-  if (selection == null) return defaultStyle // 默认 position
-
-  // 获取 textContainer rect
-  const containerRect = getTextContainerRect(editor)
-  if (containerRect == null) return defaultStyle // 默认 position
-  const {
-    top: containerTop,
-    left: containerLeft,
-    width: containerWidth,
-    height: containerHeight,
-  } = containerRect
-
-  // 获取当前选区的 rect
-  const range = DomEditor.toDOMRange(editor, selection)
-  const rangeRect = range.getClientRects()[0]
-  if (rangeRect == null) return defaultStyle // 默认 position
-  const { width: rangeWidth, height: rangeHeight, top: rangeTop, left: rangeLeft } = rangeRect
-
-  // 存储计算结构
-  const positionStyle: Partial<IPositionStyle> = {}
-
-  // 获取 选区 top left 和 container top left 的差值(< 0 则使用 0)
-  let relativeTop = rangeTop - containerTop
-  let relativeLeft = rangeLeft - containerLeft
-
-  // 判断水平位置: modal/bar 显示在选区左侧,还是右侧?
-  if (relativeLeft > containerWidth / 2) {
-    // 选区 left 大于 containerWidth/2 (选区在 container 的右侧),则 modal/bar 显示在选区左侧
-    let r = containerWidth - relativeLeft
-    positionStyle.right = `${r + 5}px` // 5px 间隔
-  } else {
-    // 否则(选区在 container 的左侧),modal/bar 显示在选区右侧
-    positionStyle.left = `${relativeLeft + 5}px` // 5px 间隔
-  }
-
-  // 判断垂直的位置: modal/bar 显示在选区上面,还是下面?
-  if (relativeTop > containerHeight / 2) {
-    // 选区 top  > containerHeight/2 (选区在 container 的下半部分),则 modal/bar 显示在选区的上面
-    let b = containerHeight - relativeTop
-    positionStyle.bottom = `${b + 5}px` // 5px 间隔
-  } else {
-    // 否则(选区在 container 的上半部分),则 modal/bar 显示在选区的下面
-    let t = relativeTop + rangeHeight
-    if (t < 0) t = 0
-    positionStyle.top = `${t + 5}px` // 5px 间隔
-  }
-
-  return positionStyle
-}
-
-/**
- * 根据 node ,计算定位(用于 modal hoverbar)
- * @param editor editor
- * @param node slate node
- * @param type 'modal'/'bar'
- */
-export function getPositionByNode(
-  editor: IDomEditor,
-  node: Node,
-  type: string = 'modal'
-): Partial<IPositionStyle> {
-  // 默认情况下 { top: 0, left: 0 }
-  const defaultStyle = { top: '0', left: '0' }
-
-  const { selection } = editor
-  if (selection == null) return defaultStyle // 默认 position
-
-  // 根据 node 获取 elem
-  const isVoidElem = Element.isElement(node) && editor.isVoid(node)
-  const isInlineElem = Element.isElement(node) && editor.isInline(node)
-  const elem = NODE_TO_ELEMENT.get(node)
-  if (elem == null) return defaultStyle // 默认 position
-  let {
-    top: elemTop,
-    left: elemLeft,
-    height: elemHeight,
-    width: elemWidth,
-  } = elem.getBoundingClientRect()
-  if (isVoidElem) {
-    // void node ,重新计算 top 和 height
-    const voidElem = getFirstVoidChild(elem)
-    if (voidElem != null) {
-      const { top, height } = voidElem.getBoundingClientRect()
-      elemTop = top
-      elemHeight = height
-    }
-  }
-
-  // 获取 textContainer rect
-  const containerRect = getTextContainerRect(editor)
-  if (containerRect == null) return defaultStyle // 默认 position
-  const {
-    top: containerTop,
-    left: containerLeft,
-    width: containerWidth,
-    height: containerHeight,
-  } = containerRect
-
-  // 存储计算结构
-  const positionStyle: Partial<IPositionStyle> = {}
-
-  // 获取 elem top left 和 container top left 的差值(< 0 则使用 0)
-  let relativeTop = elemTop - containerTop
-  let relativeLeft = elemLeft - containerLeft
-
-  if (type === 'bar') {
-    // bar - 1. left 对齐 elem.left ;2. 尽量显示在 elem 上方
-    positionStyle.left = `${relativeLeft}px`
-    if (relativeTop > 40) {
-      // top > 40 则显示在上方
-      positionStyle.bottom = `${containerHeight - relativeTop + 5}px` // 5px 间隙
-    } else {
-      // 否则,显示在下方
-      positionStyle.top = `${relativeTop + elemHeight + 5}px` // 5px 间隙
-    }
-
-    return positionStyle
-  }
-
-  if (type === 'modal') {
-    // modal - 1. top 和 elem 需要计算,尽量不遮挡 elem
-
-    // 水平
-    if (!isVoidElem) {
-      // 非 void node - left 和 elem left 对齐
-      positionStyle.left = `${relativeLeft}px`
-    } else {
-      if (isInlineElem) {
-        // inline void node 需要计算
-        if (relativeLeft > (containerWidth - elemWidth) / 2) {
-          // elem 在 container 的右侧,则 modal 显示在 elem 左侧
-          positionStyle.right = `${containerWidth - relativeLeft + 5}px`
-        } else {
-          // 否则 elem 在 container 左侧,则 modal 显示在 elem 右侧
-          positionStyle.left = `${relativeLeft + elemWidth + 5}px`
-        }
-      } else {
-        // block void node 水平靠左即可
-        positionStyle.left = `20px`
-      }
-    }
-
-    // 垂直
-    if (isVoidElem) {
-      // void node - top 和 elem top 对齐
-      let t = relativeTop
-      if (t < 0) t = 0 // top 不能小于 0
-      positionStyle.top = `${t}px`
-    } else {
-      // 非 void node ,计算 top
-      if (relativeTop > (containerHeight - elemHeight) / 2) {
-        // elem 在 container 的下半部分,则 modal 显示在 elem 上方
-        positionStyle.bottom = `${containerHeight - relativeTop + 5}px`
-      } else {
-        // elem 在 container 的上半部分,则 modal 显示在 elem 下方
-        let t = relativeTop + elemHeight
-        if (t < 0) t = 0
-        positionStyle.top = `${t + 5}px`
-      }
-    }
-
-    return positionStyle
-  }
-
-  throw new Error(`type '${type}' is invalid`)
-}
-
-/**
- * 异步修正 position ,不能超出 textContainer 边界
- * @param editor editor
- * @param $positionElem modal/bar
- */
-export function correctPosition(editor: IDomEditor, $positionElem: Dom7Array) {
-  // 异步,否则 DOM 尚未渲染
-  promiseResolveThen(() => {
-    // 获取 textContainer rect
-    const containerRect = getTextContainerRect(editor)
-    if (containerRect == null) return
-    const {
-      top: containerTop,
-      left: containerLeft,
-      width: containerWidth,
-      height: containerHeight,
-    } = containerRect
-
-    // 获取 modal bar 的 rect
-    const { top: positionElemTop, left: positionElemLeft } = $positionElem.offset()
-    const positionElemWidth = $positionElem.width()
-    const positionElemHeight = $positionElem.height()
-    const relativeTop = positionElemTop - containerTop
-    const relativeLeft = positionElemLeft - containerLeft
-
-    // 获取 modal bar 设置的 style
-    const styleStr = $positionElem.attr('style')
-
-    if (styleStr.indexOf('top') >= 0) {
-      // 设置了 top ,则有可能超过 textContainer 的下边界
-      const d = relativeTop + positionElemHeight - containerHeight
-      if (d > 0) {
-        // 已超过 textContainer 的下边界,则上移
-        const curTopStr = $positionElem.css('top')
-        const curTop = parseInt(curTopStr.toString())
-        let newTop = curTop - d
-        if (newTop < 0) newTop = 0 // 不能超过 textContainer 上边界
-        $positionElem.css('top', `${newTop}px`)
-      }
-    }
-
-    if (styleStr.indexOf('bottom') >= 0) {
-      // 设置了 bottom ,则有可能超过 textContainer 的上边界
-      if (positionElemTop < 0) {
-        // 已超出了上边界
-        const curBottomStr = $positionElem.css('bottom')
-        const curBottom = parseInt(curBottomStr.toString())
-        const newBottom = curBottom - Math.abs(positionElemTop) // 保证上边界和 textContainer 对齐即可,下边界不管
-        $positionElem.css('bottom', `${newBottom}px`)
-      }
-    }
-
-    if (styleStr.indexOf('left') >= 0) {
-      // 设置了 left ,则有可能超过 textContainer 的右边界
-      const d = relativeLeft + positionElemWidth - containerWidth
-      if (d > 0) {
-        // 已超过 textContainer 的右边界,需左移
-        const curLeftStr = $positionElem.css('left')
-        const curLeft = parseInt(curLeftStr.toString())
-        let newLeft = curLeft - d
-        if (newLeft < 0) newLeft = 0 // 不能超过 textContainer 左边界
-        $positionElem.css('left', `${newLeft}px`)
-      }
-    }
-
-    if (styleStr.indexOf('right') >= 0) {
-      // 设置了 right ,则有可能超过 textContainer 的左边界
-      if (positionElemLeft < 0) {
-        // 已超出了左边界
-        const curRightStr = $positionElem.css('right')
-        const curRight = parseInt(curRightStr.toString())
-        const newRight = curRight - Math.abs(positionElemLeft) // 保证左边界和 textContainer 对齐即可,右边界不管
-        $positionElem.css('right', `${newRight}px`)
-      }
-    }
-  })
-}
diff --git a/packages/core/src/menus/index.ts b/packages/core/src/menus/index.ts
deleted file mode 100644
index 6a48b239b..000000000
--- a/packages/core/src/menus/index.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @description menus entry
- * @author wangfupeng
- */
-
-import Toolbar from './bar/Toolbar'
-
-// 注册
-export { registerMenu } from './register'
-
-// menu 相关接口
-export {
-  IButtonMenu,
-  ISelectMenu,
-  IDropPanelMenu,
-  IModalMenu,
-  IRegisterMenuConf,
-  IOption,
-} from './interface'
-
-// 输出 modal 相关方法
-export {
-  genModalInputElems,
-  genModalButtonElems,
-  genModalTextareaElems,
-} from './panel-and-modal/Modal'
-
-export { Toolbar }
diff --git a/packages/core/src/menus/interface.ts b/packages/core/src/menus/interface.ts
deleted file mode 100644
index 95da55262..000000000
--- a/packages/core/src/menus/interface.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * @description menu interface
- * @author wangfupeng
- */
-
-import { Node } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import { DOMElement } from '../utils/dom'
-
-export interface IMenuGroup {
-  key: string
-  title: string
-  iconSvg?: string
-  menuKeys: string[]
-}
-
-export interface IPositionStyle {
-  top: string
-  left: string
-  right: string
-  bottom: string
-}
-
-export interface IOption {
-  value: string
-  text: string
-  selected?: boolean
-  styleForRenderMenuList?: { [key: string]: string } // 渲染菜单 list 时的样式
-}
-
-interface IBaseMenu {
-  readonly title: string
-  readonly iconSvg?: string
-  readonly hotkey?: string // 快捷键,使用 https://www.npmjs.com/package/is-hotkey
-  readonly alwaysEnable?: boolean // 永远不 disabled ,如“全屏”
-
-  readonly tag: string // 'button' | 'select'
-  readonly width?: number // 设置 button 宽度
-
-  getValue: (editor: IDomEditor) => string | boolean // 获取菜单相关的 val 。如是否加粗、颜色值、h1/h2/h3 等
-  isActive: (editor: IDomEditor) => boolean // 是否激活菜单,如选区处于加粗文本时,激活 bold
-  isDisabled: (editor: IDomEditor) => boolean // 是否禁用菜单,如选区处于 code-block 时,禁用 bold 等样式操作
-
-  exec: (editor: IDomEditor, value: string | boolean) => void // button click 或 select change 时触发
-}
-
-export interface IButtonMenu extends IBaseMenu {
-  /* 其他属性 */
-}
-
-export interface ISelectMenu extends IBaseMenu {
-  readonly selectPanelWidth?: number
-  getOptions: (editor: IDomEditor) => IOption[] // select -> options
-}
-
-export interface IDropPanelMenu extends IBaseMenu {
-  readonly showDropPanel: boolean // 点击 'button' 显示 dropPanel
-  getPanelContentElem: (editor: IDomEditor) => DOMElement // showDropPanel 情况下,获取 content elem
-}
-
-export interface IModalMenu extends IBaseMenu {
-  readonly showModal: boolean // 点击 'button' 显示 modal
-  readonly modalWidth: number
-  getModalContentElem: (editor: IDomEditor) => DOMElement // showModal 情况下,获取 content elem
-  getModalPositionNode: (editor: IDomEditor) => Node | null // 获取 modal 定位的 node ,null 即依据选区定位
-}
-
-export type MenuFactoryType = () => IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu
-
-export interface IRegisterMenuConf {
-  key: string
-  factory: MenuFactoryType
-  config?: { [key: string]: any }
-}
diff --git a/packages/core/src/menus/panel-and-modal/BaseClass.ts b/packages/core/src/menus/panel-and-modal/BaseClass.ts
deleted file mode 100644
index 72327ee7b..000000000
--- a/packages/core/src/menus/panel-and-modal/BaseClass.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * @description panel modal baseClass
- * @author wangfupeng
- */
-
-import { IDomEditor } from '../../editor/interface'
-import { Dom7Array, DOMElement } from '../../utils/dom'
-import { EDITOR_TO_PANEL_AND_MODAL, PANEL_OR_MODAL_TO_EDITOR } from '../../utils/weak-maps'
-
-abstract class PanelAndModal {
-  abstract readonly type: string
-  abstract readonly $elem: Dom7Array
-  isShow: boolean = false
-  private showTime: number = 0 // 显示时的时间戳
-
-  constructor(editor: IDomEditor) {
-    this.record(editor)
-  }
-
-  /**
-   * 记录下来,以便隐藏,API editor.hidePanelOrModal
-   */
-  private record(editor: IDomEditor) {
-    let set = EDITOR_TO_PANEL_AND_MODAL.get(editor)
-    if (set == null) {
-      set = new Set()
-      EDITOR_TO_PANEL_AND_MODAL.set(editor, set)
-    }
-    set.add(this)
-
-    PANEL_OR_MODAL_TO_EDITOR.set(this, editor)
-  }
-
-  /**
-   * 除了 content 之外的其他自己要增加的 elem
-   */
-  abstract genSelfElem(): Dom7Array | null
-
-  renderContent(contentElem: DOMElement) {
-    const { $elem } = this
-    $elem.empty() // 先清空,再填充内容
-    $elem.append(contentElem)
-
-    // 添加自己额外的 elem
-    const $selfElem = this.genSelfElem()
-    if ($selfElem) {
-      $elem.append($selfElem)
-    }
-  }
-
-  appendTo($menuElem: Dom7Array) {
-    const { $elem } = this
-    $menuElem.append($elem)
-  }
-
-  show() {
-    if (this.isShow) return
-    this.showTime = Date.now()
-
-    const { $elem } = this
-    $elem.show()
-    this.isShow = true
-
-    // 触发事件
-    const editor = PANEL_OR_MODAL_TO_EDITOR.get(this)
-    if (editor) editor.emit('modalOrPanelShow', this)
-  }
-
-  hide() {
-    if (!this.isShow) return
-
-    const now = Date.now()
-    if (now - this.showTime < 200) {
-      // 刚显示的,不要立刻隐藏(避免频繁触发 show/hide )
-      return
-    }
-
-    const { $elem } = this
-    $elem.hide()
-    this.isShow = false
-
-    // 触发事件
-    const editor = PANEL_OR_MODAL_TO_EDITOR.get(this)
-    if (editor) editor.emit('modalOrPanelHide')
-  }
-}
-
-export default PanelAndModal
diff --git a/packages/core/src/menus/panel-and-modal/DropPanel.ts b/packages/core/src/menus/panel-and-modal/DropPanel.ts
deleted file mode 100644
index 024b4a073..000000000
--- a/packages/core/src/menus/panel-and-modal/DropPanel.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @description dropPanel class
- * @author wangfupeng
- */
-
-import { IDomEditor } from '../../editor/interface'
-import $, { Dom7Array } from '../../utils/dom'
-import PanelAndModal from './BaseClass'
-
-class DropPanel extends PanelAndModal {
-  type = 'dropPanel'
-  readonly $elem: Dom7Array = $(`<div class="w-e-drop-panel"></div>`)
-
-  constructor(editor: IDomEditor) {
-    super(editor)
-  }
-
-  genSelfElem(): Dom7Array | null {
-    return null
-  }
-}
-
-export default DropPanel
diff --git a/packages/core/src/menus/panel-and-modal/Modal.ts b/packages/core/src/menus/panel-and-modal/Modal.ts
deleted file mode 100644
index 0415e6eb5..000000000
--- a/packages/core/src/menus/panel-and-modal/Modal.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-/**
- * @description modal class
- * @author wangfupeng
- */
-
-import $, { Dom7Array, DOMElement } from '../../utils/dom'
-import { IPositionStyle } from '../interface'
-import PanelAndModal from './BaseClass'
-import { IDomEditor } from '../../editor/interface'
-// import { DomEditor } from '../../editor/dom-editor'
-import { SVG_CLOSE } from '../../constants/svg'
-import { PANEL_OR_MODAL_TO_EDITOR } from '../../utils/weak-maps'
-
-class Modal extends PanelAndModal {
-  type = 'modal'
-  readonly $elem: Dom7Array = $(`<div class="w-e-modal"></div>`)
-  private width: number = 0
-
-  constructor(editor: IDomEditor, width: number = 0) {
-    super(editor)
-    if (width) this.width = width
-
-    const { $elem } = this
-
-    // mousedown 阻止冒泡,因为在 $textContainer 通过 mousedown 隐藏 panel & modal
-    $elem.on('click', e => e.stopPropagation())
-
-    // esc 关闭 modal
-    $elem.on('keyup', e => {
-      const event = e as KeyboardEvent
-      if (event.code === 'Escape') {
-        this.hide()
-        editor.restoreSelection() // 还原选区
-      }
-    })
-  }
-
-  /**
-   * 生成要添加到 modal $elem 的元素
-   * 【注意】不要直接 append 到 modal $elem ,因为它每次都会清空 html('')
-   */
-  genSelfElem(): Dom7Array | null {
-    // 关闭按钮
-    const $closeButton = $(`<span class="btn-close">${SVG_CLOSE}</span>`)
-    const editor = PANEL_OR_MODAL_TO_EDITOR.get(this)
-
-    $closeButton.on('click', () => {
-      this.hide()
-      editor?.restoreSelection()
-    })
-    return $closeButton
-  }
-
-  setStyle(positionStyle: Partial<IPositionStyle>) {
-    const { width, $elem } = this
-
-    $elem.attr('style', '') // 先清空 style ,再重新设置
-
-    if (width) $elem.css('width', `${width}px`)
-    $elem.css(positionStyle)
-  }
-}
-
-export default Modal
-
-// ---------------------------------- 分割线 ----------------------------------
-
-/**
- * 生成 modal input elems
- * @param labelText label text
- * @param inputId input dom id
- * @param placeholder input placeholder
- * @returns [$container, $input]
- */
-export function genModalInputElems(
-  labelText: string,
-  inputId: string,
-  placeholder?: string
-): DOMElement[] {
-  const $container = $('<label class="babel-container"></label>')
-  $container.append(`<span>${labelText}</span>`)
-  const $input = $(`<input type="text" id="${inputId}" placeholder="${placeholder || ''}">`)
-  $container.append($input)
-
-  return [$container[0], $input[0]]
-}
-
-/**
- * 生成 modal textarea elems
- * @param labelText label text
- * @param textareaId input dom id
- * @param placeholder input placeholder
- * @returns [$container, $textarea]
- */
-export function genModalTextareaElems(
-  labelText: string,
-  textareaId: string,
-  placeholder?: string
-): DOMElement[] {
-  const $container = $('<label class="babel-container"></label>')
-  $container.append(`<span>${labelText}</span>`)
-  const $textarea = $(
-    `<textarea type="text" id="${textareaId}" placeholder="${placeholder || ''}"></textarea>`
-  )
-  $container.append($textarea)
-
-  return [$container[0], $textarea[0]]
-}
-
-/**
- * 生成 modal button elems
- * @param buttonId button dom id
- * @param buttonText button text
- * @returns [ $container, $button ]
- */
-export function genModalButtonElems(buttonId: string, buttonText: string): DOMElement[] {
-  const $buttonContainer = $('<div class="button-container"></div>')
-  const $button = $(`<button type="button" id="${buttonId}">${buttonText}</button>`)
-  $buttonContainer.append($button)
-
-  return [$buttonContainer[0], $button[0]]
-}
diff --git a/packages/core/src/menus/panel-and-modal/SelectList.ts b/packages/core/src/menus/panel-and-modal/SelectList.ts
deleted file mode 100644
index 9d3cbbc6a..000000000
--- a/packages/core/src/menus/panel-and-modal/SelectList.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @description SelectList class
- * @author wangfupeng
- */
-
-import $, { Dom7Array } from '../../utils/dom'
-import { IOption } from '../interface'
-import PanelAndModal from './BaseClass'
-import { IDomEditor } from '../../editor/interface'
-import { SVG_CHECK_MARK } from '../../constants/svg'
-
-// “对号”icon
-function gen$SelectedIcon() {
-  return $(SVG_CHECK_MARK)
-}
-
-class SelectList extends PanelAndModal {
-  type = 'selectList'
-  readonly $elem: Dom7Array = $(`<div class="w-e-select-list"></div>`)
-
-  constructor(editor: IDomEditor, width?: number) {
-    super(editor)
-
-    if (width) {
-      this.$elem.css('width', `${width}px`)
-    }
-
-    this.$elem.on('click', (e: Event) => {
-      // selectList 如有滚动条,可能会点击拖拽,参考 https://github.com/wangeditor-team/wangEditor-v5/issues/325
-      // 此时需要阻止冒泡,因为在 $container.on('mousedown', () => editor.hidePanelOrModal()) ,$container 就是 `.w-e-text-container`
-      e.stopPropagation()
-    })
-  }
-
-  /**
-   * 渲染 list
-   * @param options select options
-   */
-  renderList(options: IOption[]) {
-    const $elem = this.$elem
-    $elem.empty() // 先清空内容,再重新渲染
-
-    const $list = $(`<ul></ul>`)
-    options.forEach(opt => {
-      const { value, text, selected, styleForRenderMenuList } = opt
-      const $li = $(`<li data-value="${value}"></li>`) // 【注意】必须用 <li> 必须用 data-value!!!
-
-      if (styleForRenderMenuList) {
-        $li.css(styleForRenderMenuList)
-      }
-
-      if (selected) {
-        const $selectedIcon = gen$SelectedIcon()
-        $li.append($selectedIcon)
-        $li.addClass('selected')
-      }
-
-      $li.append($(`<span data-value="${value}">${text}</span>`))
-      $li.attr('title', text)
-      $list.append($li)
-    })
-    $elem.append($list)
-  }
-
-  genSelfElem(): Dom7Array | null {
-    return null
-  }
-}
-
-export default SelectList
diff --git a/packages/core/src/menus/register.ts b/packages/core/src/menus/register.ts
deleted file mode 100644
index 423673009..000000000
--- a/packages/core/src/menus/register.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @description register menu
- * @author wangfupeng
- */
-
-import { MenuFactoryType, IRegisterMenuConf } from './interface'
-import { registerGlobalMenuConf } from '../config/register'
-
-// menu item 的工厂函数 - 集合
-export const MENU_ITEM_FACTORIES: {
-  [key: string]: MenuFactoryType
-} = {}
-
-/**
- * 注册菜单配置
- * @param registerMenuConf { key, factory, config } ,各个 menu key 不能重复
- * @param customConfig 自定义 menu config
- */
-export function registerMenu(
-  registerMenuConf: IRegisterMenuConf,
-  customConfig?: { [key: string]: any }
-) {
-  const { key, factory, config } = registerMenuConf
-
-  // 合并 config
-  const newConfig = { ...config, ...(customConfig || {}) }
-
-  // 注册 menu
-  if (MENU_ITEM_FACTORIES[key] != null) {
-    throw new Error(`Duplicated key '${key}' in menu items`)
-  }
-  MENU_ITEM_FACTORIES[key] = factory
-
-  // 将 config 保存到全局
-  registerGlobalMenuConf(key, newConfig)
-}
diff --git a/packages/core/src/parse-html/README.md b/packages/core/src/parse-html/README.md
deleted file mode 100644
index 7ec999845..000000000
--- a/packages/core/src/parse-html/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# parse html
-
-把 html 转换为 JSON content
diff --git a/packages/core/src/parse-html/helper.ts b/packages/core/src/parse-html/helper.ts
deleted file mode 100644
index 00984bb35..000000000
--- a/packages/core/src/parse-html/helper.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * @description parse-html helper fns
- * @author wangfupeng
- */
-
-const REPLACE_SPACE_160_REG = new RegExp(String.fromCharCode(160), 'g')
-
-/**
- * 把 charCode 160 的空格(`&nbsp` 转换的),替换为 charCode 32 的空格(JS 默认的)
- * @param str str
- * @returns str
- */
-export function replaceSpace160(str: string): string {
-  const res = str.replace(REPLACE_SPACE_160_REG, ' ')
-  return res
-}
diff --git a/packages/core/src/parse-html/index.ts b/packages/core/src/parse-html/index.ts
deleted file mode 100644
index ee1ad7656..000000000
--- a/packages/core/src/parse-html/index.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * @description parse html
- * @author wangfupeng
- */
-
-import { DOMElement } from '../utils/dom'
-import { Element as SlateElement, Descendant } from 'slate'
-import { IDomEditor } from '../editor/interface'
-
-// 常见的 text tag
-export const TEXT_TAGS = [
-  'span',
-  'b',
-  'strong',
-  'i',
-  'em',
-  's',
-  'strike',
-  'u',
-  'font',
-  'sub',
-  'sup',
-]
-
-// ------------------------------------ pre-parse html ------------------------------------
-export type PreParseHtmlFnType = ($node: DOMElement) => DOMElement
-
-export interface IPreParseHtmlConf {
-  selector: string // css 选择器,如 `p` `div[data-type="xxx"]`
-  preParseHtml: PreParseHtmlFnType
-}
-
-export const PRE_PARSE_HTML_CONF_LIST: IPreParseHtmlConf[] = []
-
-/**
- * 注册 pre-parse html 配置
- * @param conf pre-parse html conf
- */
-export function registerPreParseHtmlConf(conf: IPreParseHtmlConf) {
-  PRE_PARSE_HTML_CONF_LIST.push(conf)
-}
-
-// ------------------------------------ parse style html ------------------------------------
-
-export type ParseStyleHtmlFnType = (
-  $node: DOMElement,
-  node: Descendant,
-  editor: IDomEditor
-) => Descendant
-
-export const PARSE_STYLE_HTML_FN_LIST: ParseStyleHtmlFnType[] = []
-
-/**
- * 注册 parseStyleHtml 函数
- * @param fn parse style html 的函数
- */
-export function registerParseStyleHtmlHandler(fn: ParseStyleHtmlFnType) {
-  PARSE_STYLE_HTML_FN_LIST.push(fn)
-}
-
-// ------------------------------------ parse elem html ------------------------------------
-
-export type ParseElemHtmlFnType = (
-  $elem: DOMElement,
-  children: Descendant[],
-  editor: IDomEditor
-) => SlateElement | SlateElement[]
-
-export const PARSE_ELEM_HTML_CONF: {
-  [key: string]: ParseElemHtmlFnType // key 是 css 选择器,如 `p` `div[data-type="xxx"]`
-} = {}
-
-export interface IParseElemHtmlConf {
-  selector: string
-  parseElemHtml: ParseElemHtmlFnType
-}
-
-export function registerParseElemHtmlConf(conf: IParseElemHtmlConf) {
-  const { selector, parseElemHtml } = conf
-  PARSE_ELEM_HTML_CONF[selector] = parseElemHtml
-}
diff --git a/packages/core/src/parse-html/parse-common-elem-html.ts b/packages/core/src/parse-html/parse-common-elem-html.ts
deleted file mode 100644
index c72b84020..000000000
--- a/packages/core/src/parse-html/parse-common-elem-html.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-/**
- * @description parse elem html
- * @author wangfupeng
- */
-
-import $, { Dom7Array } from 'dom7'
-import { Editor, Element, Descendant, Text } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import parseElemHtml from './parse-elem-html'
-import { PARSE_ELEM_HTML_CONF, ParseElemHtmlFnType, PARSE_STYLE_HTML_FN_LIST } from './index'
-import { NodeType, DOMElement } from '../utils/dom'
-import { replaceSpace160 } from './helper'
-
-/**
- * 往 children 最后一个 item(如果是 text node) 插入文字
- * @param children children
- * @param str str
- * @returns 是否插入成功
- */
-function tryInsertTextToChildrenLastItem(children: Descendant[], str: string): boolean {
-  const len = children.length
-  if (len) {
-    const lastItem = children[len - 1]
-    if (Text.isText(lastItem)) {
-      const keys = Object.keys(lastItem)
-      if (keys.length === 1 && keys[0] === 'text') {
-        // lastItem 必须是纯文本,没有 marks
-        lastItem.text = lastItem.text + str
-        return true
-      }
-    }
-  }
-  return false
-}
-
-/**
- * 生成 slate node children
- * @param $elem $elem
- * @param editor editor
- */
-function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] {
-  const children: Descendant[] = []
-
-  // void node( html 中编辑的,如 video 的 html 中会有 data-w-e-is-void 属性 ),不需要生成 children
-  const isVoid = $elem.attr('data-w-e-is-void') != null
-  if (isVoid) {
-    return children
-  }
-
-  const childNodes = $elem[0].childNodes
-
-  // 处理空行(只有一个 child ,是 <br>)
-  if (childNodes.length === 1) {
-    if (childNodes[0].nodeName === 'BR') {
-      children.push({ text: '' })
-      return children // 直接返回
-    }
-  }
-
-  // 遍历 DOM 子节点,生成 slate elem node children
-  childNodes.forEach(child => {
-    if (child.nodeType === NodeType.ELEMENT_NODE) {
-      // <br> ,则往 children 最后一个元素(如果是 text )追加 `\n`
-      if (child.nodeName === 'BR') {
-        // 尝试把 text 插入到最后一个 children
-        const res = tryInsertTextToChildrenLastItem(children, '\n')
-        if (!res) {
-          // 若插入失败,则新建 item
-          children.push({ text: '\n' })
-        }
-        return
-      }
-
-      // 其他 elem
-      const $child = $(child)
-      const parsedRes = parseElemHtml($child, editor)
-      if (Array.isArray(parsedRes)) {
-        parsedRes.forEach(el => children.push(el))
-      } else {
-        children.push(parsedRes)
-      }
-      return
-    }
-    if (child.nodeType === NodeType.TEXT_NODE) {
-      // text
-      let text = child.textContent || ''
-      if (text.trim() === '' && text.indexOf('\n') >= 0) {
-        // 有换行,但无实际内容
-        return
-      }
-
-      if (text) {
-        // 把 charCode 160 的空格(`&nbsp` 转换的),替换为 charCode 32 的空格(JS 默认的)
-        text = replaceSpace160(text)
-
-        // 尝试把 text 插入到最后一个 children
-        const res = tryInsertTextToChildrenLastItem(children, text)
-        if (!res) {
-          // 若插入失败,则新建 item
-          children.push({ text })
-        }
-      }
-      return
-    }
-  })
-  return children
-}
-
-/**
- * 默认的 parseElemHtml ,直接转换为 paragraph
- * @param elem elem
- * @param children children
- */
-function defaultParser(elem: DOMElement, children: Descendant[], editor: IDomEditor): Element {
-  return {
-    type: 'paragraph',
-    children: [{ text: $(elem).text().replace(/\s+/gm, ' ') }],
-  }
-}
-
-/**
- * 获取当前 html 元素的 parseElemHtml 函数
- * @param $elem $elem
- */
-function getParser($elem: Dom7Array): ParseElemHtmlFnType {
-  for (let selector in PARSE_ELEM_HTML_CONF) {
-    if ($elem[0].matches(selector)) {
-      return PARSE_ELEM_HTML_CONF[selector]
-    }
-  }
-  return defaultParser
-}
-
-/**
- * 处理普通 DOM elem html ,非 span font 等文本 elem
- * @param $elem $elem
- * @param editor editor
- * @returns slate element
- */
-function parseCommonElemHtml($elem: Dom7Array, editor: IDomEditor): Element[] {
-  const children = genChildren($elem, editor)
-
-  // parse
-  const parser = getParser($elem)
-  let parsedRes = parser($elem[0], children, editor)
-
-  if (!Array.isArray(parsedRes)) parsedRes = [parsedRes] // 临时处理为数组
-
-  parsedRes.forEach(elem => {
-    const isVoid = Editor.isVoid(editor, elem)
-    if (!isVoid) {
-      // 非 void ,如果没有 children ,则取纯文本
-      if (children.length === 0) {
-        elem.children = [{ text: $elem.text().replace(/\s+/gm, ' ') }]
-      }
-
-      // 处理 style
-      PARSE_STYLE_HTML_FN_LIST.forEach(fn => {
-        elem = fn($elem[0], elem, editor) as Element
-      })
-    }
-  })
-
-  return parsedRes
-}
-
-export default parseCommonElemHtml
diff --git a/packages/core/src/parse-html/parse-elem-html.ts b/packages/core/src/parse-html/parse-elem-html.ts
deleted file mode 100644
index ea866a1b2..000000000
--- a/packages/core/src/parse-html/parse-elem-html.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @description parse node html
- * @author wangfupeng
- */
-
-import $, { Dom7Array } from 'dom7'
-import { Descendant } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import parseCommonElemHtml from './parse-common-elem-html'
-import parseTextElemHtml from './parse-text-elem-html'
-import { getTagName } from '../utils/dom'
-import { PRE_PARSE_HTML_CONF_LIST, TEXT_TAGS } from '../index'
-
-/**
- * 处理 DOM Elem html
- * @param $elem $elem
- * @param editor editor
- * @returns slate Descendant
- */
-function parseElemHtml($elem: Dom7Array, editor: IDomEditor): Descendant | Descendant[] {
-  // pre-parse
-  PRE_PARSE_HTML_CONF_LIST.forEach(conf => {
-    const { selector, preParseHtml } = conf
-    if ($elem[0].matches(selector)) {
-      $elem = $(preParseHtml($elem[0]))
-    }
-  })
-
-  const tagName = getTagName($elem)
-
-  // <span> 判断有没有 data-w-e-type 属性。有则是 elem ,没有则是 text
-  if (tagName === 'span') {
-    if ($elem.attr('data-w-e-type')) {
-      return parseCommonElemHtml($elem, editor)
-    } else {
-      return parseTextElemHtml($elem, editor)
-    }
-  }
-
-  // <code> 特殊处理
-  if (tagName === 'code') {
-    const parentTagName = getTagName($elem.parent())
-    if (parentTagName === 'pre') {
-      // <code> 在 <pre> 内,则是 elem
-      return parseCommonElemHtml($elem, editor)
-    } else {
-      // <code> 不在 <pre> 内,则是 text
-      return parseTextElemHtml($elem, editor)
-    }
-  }
-
-  // 非 <code> ,正常处理
-  if (TEXT_TAGS.includes(tagName)) {
-    // text node
-    return parseTextElemHtml($elem, editor)
-  } else {
-    // elem node
-    return parseCommonElemHtml($elem, editor)
-  }
-}
-
-export default parseElemHtml
diff --git a/packages/core/src/parse-html/parse-text-elem-html.ts b/packages/core/src/parse-html/parse-text-elem-html.ts
deleted file mode 100644
index f875c9527..000000000
--- a/packages/core/src/parse-html/parse-text-elem-html.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * @description parse text html
- * @author wangfupeng
- */
-
-import { Dom7Array } from 'dom7'
-import { Text } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import { PARSE_STYLE_HTML_FN_LIST } from './index'
-import { deReplaceHtmlSpecialSymbols } from '../utils/util'
-import { replaceSpace160 } from './helper'
-
-/**
- * 处理 text elem ,如 <span> <strong> <em> 等(并不是 DOM Text Node)
- * @param $text $text
- * @param editor editor
- * @returns slate text
- */
-function parseTextElemHtml($text: Dom7Array, editor: IDomEditor): Text {
-  if ($text.parents('pre').length === 0) {
-    // 不在 <pre> 内部
-    // 1. 替换无用空格、换行; 2. 将 <br> 替换为 `\n`
-    $text[0].innerHTML = $text[0].innerHTML.replace(/\s+/gm, ' ').replace(/<br>/g, '\n')
-  }
-
-  // 用 textContent ,不能用 .text() 。后者无法识别 text 开头和末尾的 &nbsp;
-  let text = $text[0].textContent || ''
-
-  //【翻转】替换 html 特殊字符,如 &lt; 替换为 <
-  text = deReplaceHtmlSpecialSymbols(text)
-
-  // 把 charCode 160 的空格(`&nbsp` 转换的),替换为 charCode 32 的空格(JS 默认的)
-  text = replaceSpace160(text)
-
-  // 生成 text node
-  let textNode = { text }
-
-  // 处理 style
-  PARSE_STYLE_HTML_FN_LIST.forEach(fn => {
-    textNode = fn($text[0], textNode, editor) as Text
-  })
-
-  return textNode
-}
-
-export default parseTextElemHtml
diff --git a/packages/core/src/render/README.md b/packages/core/src/render/README.md
deleted file mode 100644
index 5d56c6e73..000000000
--- a/packages/core/src/render/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# render
-
-把 JSON content 转换为 vdom
diff --git a/packages/core/src/render/element/getRenderElem.tsx b/packages/core/src/render/element/getRenderElem.tsx
deleted file mode 100644
index a1d274429..000000000
--- a/packages/core/src/render/element/getRenderElem.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @description 获取 elem render 函数
- * @author wangfupeng
- */
-
-import { Element as SlateElement } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { IDomEditor } from '../../editor/interface'
-import { RENDER_ELEM_CONF, RenderElemFnType } from '../index'
-
-/**
- * 默认的 render elem
- * @param elemNode elem
- * @param editor editor
- * @param children children vnode
- * @returns vnode
- */
-function defaultRender(
-  elemNode: SlateElement,
-  children: VNode[] | null,
-  editor: IDomEditor
-): VNode {
-  const Tag = editor.isInline(elemNode) ? 'span' : 'div'
-
-  const vnode = <Tag>{children}</Tag>
-
-  return vnode
-}
-
-/**
- * 根据 elemNode.type 获取 renderElement 函数
- * @param type elemNode.type
- */
-function getRenderElem(type: string): RenderElemFnType {
-  const fn = RENDER_ELEM_CONF[type]
-  return fn || defaultRender
-}
-
-export default getRenderElem
diff --git a/packages/core/src/render/element/renderElement.tsx b/packages/core/src/render/element/renderElement.tsx
deleted file mode 100644
index 46ccb118d..000000000
--- a/packages/core/src/render/element/renderElement.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * @description render element node
- * @author wangfupeng
- */
-
-import { Editor, Node, Element as SlateElement } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { node2Vnode } from '../node2Vnode'
-import { DomEditor } from '../../editor/dom-editor'
-import { IDomEditor } from '../../editor/interface'
-import {
-  KEY_TO_ELEMENT,
-  NODE_TO_ELEMENT,
-  ELEMENT_TO_NODE,
-  NODE_TO_INDEX,
-  NODE_TO_PARENT,
-} from '../../utils/weak-maps'
-import getRenderElem from './getRenderElem'
-import renderStyle from './renderStyle'
-import { promiseResolveThen } from '../../utils/util'
-import { genElemId } from '../helper'
-import { getElementById } from '../../utils/dom'
-
-interface IAttrs {
-  id: string
-  key: string | number
-  'data-slate-node': 'element'
-  'data-slate-inline'?: boolean
-  'data-slate-void'?: boolean
-  contentEditable?: Boolean
-}
-
-function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode {
-  const key = DomEditor.findKey(editor, elemNode)
-  // const readOnly = editor.isDisabled()
-  const isInline = editor.isInline(elemNode)
-  const isVoid = Editor.isVoid(editor, elemNode)
-  const domId = genElemId(key.id)
-  const attrs: IAttrs = {
-    id: domId,
-    key: key.id,
-    'data-slate-node': 'element',
-    'data-slate-inline': isInline,
-  }
-
-  // 根据 type 生成 vnode 的函数
-  const { type, children = [] } = elemNode
-  let renderElem = getRenderElem(type)
-
-  let childrenVnode
-  if (isVoid) {
-    childrenVnode = null // void 节点 render elem 时不传入 children
-  } else {
-    childrenVnode = children.map((child: Node, index: number) => {
-      return node2Vnode(child, index, elemNode, editor)
-    })
-  }
-
-  // 创建 vnode
-  let vnode = renderElem(elemNode, childrenVnode, editor)
-
-  // void node 要特殊处理
-  if (isVoid) {
-    attrs['data-slate-void'] = true
-
-    // 如果这里设置 contentEditable = false ,那图片就无法删除了 ???
-    // if (!readOnly && isInline) {
-    //     attrs.contentEditable = false
-    // }
-
-    const Tag = isInline ? 'span' : 'div'
-    const [[text]] = Node.texts(elemNode)
-
-    const textVnode = node2Vnode(text, 0, elemNode, editor)
-    const textWrapperVnode = (
-      <Tag
-        data-slate-spacer
-        style={{
-          height: '0',
-          color: 'transparent',
-          outline: 'none',
-          position: 'absolute',
-        }}
-      >
-        {textVnode}
-      </Tag>
-    )
-
-    // 重写 vnode
-    vnode = (
-      // 设置 position: relative,保证 absolute 的 textWrapperVnode 不乱跑
-      <Tag style={{ position: 'relative' }}>
-        {vnode}
-        {textWrapperVnode}
-      </Tag>
-    )
-
-    // 记录 text 相关 weakMap
-    NODE_TO_INDEX.set(text, 0)
-    NODE_TO_PARENT.set(text, elemNode)
-  }
-
-  // 添加 element 属性
-  if (vnode.data == null) vnode.data = {}
-  Object.assign(vnode.data, attrs)
-
-  // 添加文本相关的样式,如 text-align
-  if (!isVoid && !isInline) {
-    // 非 void + 非 inline
-    vnode = renderStyle(elemNode, vnode)
-  }
-
-  // 更新 element 相关的 weakMap
-  promiseResolveThen(() => {
-    // 异步,否则拿不到 DOM 节点
-    const dom = getElementById(domId)
-    if (dom == null) return
-    KEY_TO_ELEMENT.set(key, dom)
-    NODE_TO_ELEMENT.set(elemNode, dom)
-    ELEMENT_TO_NODE.set(dom, elemNode)
-  })
-
-  return vnode
-}
-
-export default renderElement
diff --git a/packages/core/src/render/element/renderStyle.ts b/packages/core/src/render/element/renderStyle.ts
deleted file mode 100644
index 5aef9e065..000000000
--- a/packages/core/src/render/element/renderStyle.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * @description 添加文本相关的样式
- * @author wangfupeng
- */
-
-import { Element as SlateElement } from 'slate'
-import { VNode } from 'snabbdom'
-import { RENDER_STYLE_HANDLER_LIST } from '../index'
-
-/**
- * 渲染样式
- * @param elem slate elem node
- * @param vnode elem Vnode
- */
-function renderStyle(elem: SlateElement, vnode: VNode): VNode {
-  let newVnode = vnode
-
-  RENDER_STYLE_HANDLER_LIST.forEach(styleHandler => {
-    newVnode = styleHandler(elem, vnode)
-  })
-
-  return newVnode
-}
-
-export default renderStyle
diff --git a/packages/core/src/render/helper.ts b/packages/core/src/render/helper.ts
deleted file mode 100644
index ac66dbb59..000000000
--- a/packages/core/src/render/helper.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @description formats helper
- * @author wangfupeng
- */
-
-export function genElemId(id: string) {
-  return `w-e-element-${id}`
-}
-
-export function genTextId(id: string) {
-  return `w-e-text-${id}`
-}
diff --git a/packages/core/src/render/index.ts b/packages/core/src/render/index.ts
deleted file mode 100644
index 90ed07aca..000000000
--- a/packages/core/src/render/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * @description formats entry
- * @author wangfupeng
- */
-
-import { Element as SlateElement, Descendant } from 'slate'
-import { VNode } from 'snabbdom'
-import { IDomEditor } from '../editor/interface'
-
-// ------------------------------------ render style ------------------------------------
-
-export type RenderStyleFnType = (node: Descendant, vnode: VNode) => VNode
-
-// 存储:处理文本样式的函数,如 b u color 等
-export const RENDER_STYLE_HANDLER_LIST: RenderStyleFnType[] = []
-
-/**
- * 注册处理文本样式的函数
- * @param fn 处理文本样式的函数
- */
-export function registerStyleHandler(fn: RenderStyleFnType) {
-  RENDER_STYLE_HANDLER_LIST.push(fn)
-}
-
-// ------------------------------------ render elem ------------------------------------
-
-export type RenderElemFnType = (
-  elemNode: SlateElement,
-  children: VNode[] | null,
-  editor: IDomEditor
-) => VNode
-
-// 注册 render element 配置
-export const RENDER_ELEM_CONF: {
-  [key: string]: RenderElemFnType // key 要和 node.type 对应 !!!
-} = {}
-
-export interface IRenderElemConf {
-  type: string
-  renderElem: RenderElemFnType
-}
-
-/**
- * 注册 render elem 函数
- * @param conf { type, renderElem } ,type 即 node.type
- */
-export function registerRenderElemConf(conf: IRenderElemConf) {
-  const { type, renderElem } = conf
-  const key = type || ''
-
-  // 如果 key 重复了,就后者覆盖前者
-  RENDER_ELEM_CONF[key] = renderElem
-}
diff --git a/packages/core/src/render/node2Vnode.ts b/packages/core/src/render/node2Vnode.ts
deleted file mode 100644
index ac35ff209..000000000
--- a/packages/core/src/render/node2Vnode.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * @description slate node to vnode
- * @author wangfupeng
- */
-
-import { Element, Text, Node, Ancestor } from 'slate'
-import { VNode } from 'snabbdom'
-import { IDomEditor } from '../editor/interface'
-import renderElement from './element/renderElement'
-import renderText from './text/renderText'
-import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'
-
-/**
- * 根据 slate node 生成 snabbdom vnode
- * @param node node
- * @param index node index in parent.children
- * @param parent parent node
- * @param editor editor
- */
-export function node2Vnode(node: Node, index: number, parent: Ancestor, editor: IDomEditor): VNode {
-  // 设置相关 weakMap 信息
-  NODE_TO_INDEX.set(node, index)
-  NODE_TO_PARENT.set(node, parent)
-
-  let vnode: VNode
-  if (Element.isElement(node)) {
-    // element
-    vnode = renderElement(node as Element, editor)
-  } else {
-    // text
-    vnode = renderText(node as Text, parent, editor)
-  }
-
-  return vnode
-}
diff --git a/packages/core/src/render/text/genVnode.tsx b/packages/core/src/render/text/genVnode.tsx
deleted file mode 100644
index 6b34c6690..000000000
--- a/packages/core/src/render/text/genVnode.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * @description 生成 text vnode
- * @author wangfupeng
- */
-
-import { Editor, Path, Node, Text as SlateText, Ancestor } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { DomEditor } from '../../editor/dom-editor'
-import { IDomEditor } from '../../editor/interface'
-
-function str(text: string, isTrailing = false): VNode {
-  return <span data-slate-string>{isTrailing ? text + '\n' : text}</span>
-}
-
-function zeroWidthStr(length = 0, isLineBreak = false): VNode {
-  return (
-    <span data-slate-zero-width={isLineBreak ? 'n' : 'z'} data-slate-length={length}>
-      {'\uFEFF'}
-      {isLineBreak ? <br /> : null}
-    </span>
-  )
-}
-
-function genTextVnode(
-  leafNode: SlateText,
-  isLast: boolean = false,
-  textNode: SlateText,
-  parent: Ancestor,
-  editor: IDomEditor
-): VNode {
-  const { text } = leafNode
-  const path = DomEditor.findPath(editor, textNode)
-  const parentPath = Path.parent(path)
-
-  if (Editor.isEditor(parent)) {
-    throw new Error(`Text node ${JSON.stringify(textNode)} parent is Editor`)
-  }
-
-  // COMPAT: Render text inside void nodes with a zero-width space.
-  // So the node can contain selection but the text is not visible.
-  if (editor.isVoid(parent)) {
-    return zeroWidthStr(Node.string(parent).length)
-  }
-
-  // COMPAT: If this is the last text node in an empty block, render a zero-
-  // width space that will convert into a line break when copying and pasting
-  // to support expected plain text.
-  if (
-    text === '' &&
-    parent.children[parent.children.length - 1] === textNode &&
-    !editor.isInline(parent) &&
-    Editor.string(editor, parentPath) === ''
-  ) {
-    return zeroWidthStr(0, true)
-  }
-
-  // COMPAT: If the text is empty, it's because it's on the edge of an inline
-  // node, so we render a zero-width space so that the selection can be
-  // inserted next to it still.
-  if (text === '') {
-    return zeroWidthStr()
-  }
-
-  // COMPAT: Browsers will collapse trailing new lines at the end of blocks,
-  // so we need to add an extra trailing new lines to prevent that.
-  if (isLast && text.slice(-1) === '\n') {
-    return str(text, true)
-  }
-
-  return str(text)
-}
-
-export default genTextVnode
diff --git a/packages/core/src/render/text/renderStyle.ts b/packages/core/src/render/text/renderStyle.ts
deleted file mode 100644
index 7fe7cc9fc..000000000
--- a/packages/core/src/render/text/renderStyle.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * @description text 样式
- * @author wangfupeng
- */
-
-import { Text as SlateText } from 'slate'
-import { VNode } from 'snabbdom'
-import { RENDER_STYLE_HANDLER_LIST } from '../index'
-
-/**
- * 给字符串增加样式
- * @param leafNode slate text leaf node
- * @param textVnode textVnode
- */
-function addTextVnodeStyle(leafNode: SlateText, textVnode: VNode): VNode {
-  let newTextVnode = textVnode
-
-  RENDER_STYLE_HANDLER_LIST.forEach(styleHandler => {
-    newTextVnode = styleHandler(leafNode, newTextVnode)
-  })
-
-  return newTextVnode
-}
-
-export default addTextVnodeStyle
diff --git a/packages/core/src/render/text/renderText.tsx b/packages/core/src/render/text/renderText.tsx
deleted file mode 100644
index 2a5b05b59..000000000
--- a/packages/core/src/render/text/renderText.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * @description render text node
- * @author wangfupeng
- */
-
-import { Text as SlateText, Ancestor } from 'slate'
-import { jsx, VNode } from 'snabbdom'
-import { DomEditor } from '../../editor/dom-editor'
-import { IDomEditor } from '../../editor/interface'
-import { KEY_TO_ELEMENT, NODE_TO_ELEMENT, ELEMENT_TO_NODE } from '../../utils/weak-maps'
-import genTextVnode from './genVnode'
-import addTextVnodeStyle from './renderStyle'
-import { promiseResolveThen } from '../../utils/util'
-import { genTextId } from '../helper'
-import { getElementById } from '../../utils/dom'
-
-function renderText(textNode: SlateText, parent: Ancestor, editor: IDomEditor): VNode {
-  if (textNode.text == null)
-    throw new Error(`Current node is not slate Text ${JSON.stringify(textNode)}`)
-  const key = DomEditor.findKey(editor, textNode)
-
-  // 根据 decorate 将 text 拆分为多个叶子节点 text[]
-  const { decorate } = editor.getConfig()
-  if (decorate == null) throw new Error(`Can not get config.decorate`)
-  const path = DomEditor.findPath(editor, textNode)
-  const ds = decorate([textNode, path])
-  const leaves = SlateText.decorations(textNode, ds)
-
-  // 生成 leaves vnode
-  const leavesVnode = leaves.map((leafNode, index) => {
-    // 文字和样式
-    const isLast = index === leaves.length - 1
-    let strVnode = genTextVnode(leafNode, isLast, textNode, parent, editor)
-    strVnode = addTextVnodeStyle(leafNode, strVnode)
-    // 生成每一个 leaf 节点
-    return <span data-slate-leaf>{strVnode}</span>
-  })
-
-  // 生成 text vnode
-  const textId = genTextId(key.id)
-  const vnode = (
-    <span data-slate-node="text" id={textId} key={key.id}>
-      {leavesVnode /* 一个 text 可能包含多个 leaf */}
-    </span>
-  )
-
-  // 更新 weak-map
-  promiseResolveThen(() => {
-    // 异步,否则拿不到 DOM
-    const dom = getElementById(textId)
-    if (dom == null) return
-    KEY_TO_ELEMENT.set(key, dom)
-    NODE_TO_ELEMENT.set(textNode, dom)
-    ELEMENT_TO_NODE.set(dom, textNode)
-  })
-
-  return vnode
-}
-
-export default renderText
diff --git a/packages/core/src/text-area/TextArea.ts b/packages/core/src/text-area/TextArea.ts
deleted file mode 100644
index b325b5482..000000000
--- a/packages/core/src/text-area/TextArea.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-/**
- * @description text-area class
- * @author wangfupeng
- */
-
-import { Range } from 'slate'
-import throttle from 'lodash.throttle'
-import forEach from 'lodash.foreach'
-import $, { Dom7Array, DOMElement } from '../utils/dom'
-import { TEXTAREA_TO_EDITOR } from '../utils/weak-maps'
-import { IDomEditor } from '../editor/interface'
-import { DomEditor } from '../editor/dom-editor'
-import updateView from './update-view'
-import { handlePlaceholder } from './place-holder'
-import { editorSelectionToDOM, DOMSelectionToEditor } from './syncSelection'
-import { promiseResolveThen } from '../utils/util'
-import eventHandlerConf from './event-handlers/index'
-
-let ID = 1
-
-class TextArea {
-  readonly id = ID++
-  $box: Dom7Array
-  $textAreaContainer: Dom7Array
-  $scroll: Dom7Array
-  $textArea: Dom7Array | null = null
-  private readonly $progressBar = $('<div class="w-e-progress-bar"></div>')
-  private readonly $maxLengthInfo = $('<div class="w-e-max-length-info"></div>')
-  isComposing: boolean = false
-  isUpdatingSelection: boolean = false
-  isDraggingInternally: boolean = false
-  latestElement: DOMElement | null = null
-  showPlaceholder = false
-  $placeholder: Dom7Array | null = null
-  private latestEditorSelection: Range | null = null
-
-  constructor(boxSelector: string | DOMElement) {
-    // @ts-ignore 初始化 dom
-    const $box = $(boxSelector)
-    if ($box.length === 0) {
-      throw new Error(`Cannot find textarea DOM by selector '${boxSelector}'`)
-    }
-    this.$box = $box
-    const $container = $(`<div class="w-e-text-container"></div>`)
-    $container.append(this.$progressBar) // 进度条
-    $container.append(this.$maxLengthInfo) // max length 提示信息
-    $box.append($container)
-    const $scroll = $(`<div class="w-e-scroll"></div>`)
-    $container.append($scroll)
-    this.$scroll = $scroll
-    this.$textAreaContainer = $container
-
-    // 异步,否则获取不到 editor 和 DOM
-    promiseResolveThen(() => {
-      const editor = this.editorInstance
-      const window = DomEditor.getWindow(editor)
-
-      // 监听 selection change
-      window.document.addEventListener('selectionchange', this.onDOMSelectionChange)
-      // editor 销毁时,解绑 selection change
-      editor.on('destroyed', () => {
-        window.document.removeEventListener('selectionchange', this.onDOMSelectionChange)
-      })
-
-      // 点击编辑区域,关闭 panel
-      $container.on('click', () => editor.hidePanelOrModal())
-
-      // editor onchange 时更新视图
-      editor.on('change', this.changeViewState.bind(this))
-
-      // editor onchange 时触发用户配置的 onChange (需要在 changeViewState 后执行)
-      const { onChange } = editor.getConfig()
-      if (onChange) {
-        editor.on('change', () => onChange(editor))
-      }
-
-      // 监听 onfocus onblur
-      this.onFocusAndOnBlur()
-
-      // 实时修改 maxLength 提示信息
-      editor.on('change', this.changeMaxLengthInfo.bind(this))
-
-      // 绑定 DOM 事件
-      this.bindEvent()
-    })
-  }
-
-  private get editorInstance(): IDomEditor {
-    const editor = TEXTAREA_TO_EDITOR.get(this)
-    if (editor == null) throw new Error('Can not get editor instance')
-    return editor
-  }
-
-  private onDOMSelectionChange = throttle(() => {
-    const editor = this.editorInstance
-    DOMSelectionToEditor(this, editor)
-  }, 100)
-
-  /**
-   * 绑定事件,如 beforeinput onblur onfocus keydown click copy/paste drag/drop 等
-   */
-  private bindEvent() {
-    const { $textArea, $scroll } = this
-    const editor = this.editorInstance
-
-    if ($textArea == null) return
-
-    // 遍历所有事件类型,绑定
-    forEach(eventHandlerConf, (fn, eventType) => {
-      $textArea.on(eventType, event => {
-        fn(event, this, editor)
-      })
-    })
-
-    // 设置 scroll
-    const { scroll } = editor.getConfig()
-    if (scroll) {
-      $scroll.css('overflow-y', 'auto')
-      // scroll 自定义事件
-      $scroll.on(
-        'scroll',
-        throttle(() => {
-          editor.emit('scroll')
-        }, 100)
-      )
-    }
-  }
-
-  private onFocusAndOnBlur() {
-    const editor = this.editorInstance
-    const { onBlur, onFocus } = editor.getConfig()
-    this.latestEditorSelection = editor.selection
-
-    editor.on('change', () => {
-      if (this.latestEditorSelection == null && editor.selection != null) {
-        // 异步触发 focus
-        setTimeout(() => onFocus && onFocus(editor))
-      } else if (this.latestEditorSelection != null && editor.selection == null) {
-        // 异步触发 blur
-        setTimeout(() => onBlur && onBlur(editor))
-      }
-
-      this.latestEditorSelection = editor.selection // 重新记录 selection
-    })
-  }
-
-  /**
-   * 修改 maxLength 提示信息
-   */
-  private changeMaxLengthInfo() {
-    const editor = this.editorInstance
-    const { maxLength } = editor.getConfig()
-    if (maxLength) {
-      const leftLength = DomEditor.getLeftLengthOfMaxLength(editor)
-      const curLength = maxLength - leftLength
-      this.$maxLengthInfo[0].innerHTML = `${curLength}/${maxLength}`
-    }
-  }
-
-  /**
-   * 修改进度条
-   * @param progress 进度
-   */
-  changeProgress(progress: number) {
-    const $progressBar = this.$progressBar
-    $progressBar.css('width', `${progress}%`)
-
-    // 进度 100% 之后,定时隐藏
-    if (progress >= 100) {
-      setTimeout(() => {
-        $progressBar.hide()
-        $progressBar.css('width', '0')
-        $progressBar.show()
-      }, 1000)
-    }
-  }
-
-  /**
-   * 修改 view 状态
-   */
-  changeViewState() {
-    const editor = this.editorInstance
-
-    // 更新 DOM
-    // TODO 注意这里是否会有性能瓶颈?因为每次键盘输入,都会触发这里 —— 可单独测试大文件、多内容,如几万个字
-    updateView(this, editor)
-
-    // 处理 placeholder
-    handlePlaceholder(this, editor)
-
-    // 同步选区(异步,否则拿不到 DOM 渲染结果,vdom)
-    promiseResolveThen(() => {
-      editorSelectionToDOM(this, editor)
-    })
-  }
-
-  /**
-   * 销毁 textarea
-   */
-  destroy() {
-    // 销毁 DOM (只销毁最外层 DOM 即可)
-    this.$textAreaContainer.remove()
-  }
-}
-
-export default TextArea
diff --git a/packages/core/src/text-area/event-handlers/beforeInput.ts b/packages/core/src/text-area/event-handlers/beforeInput.ts
deleted file mode 100644
index 86a4c2be4..000000000
--- a/packages/core/src/text-area/event-handlers/beforeInput.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * @description 处理 beforeInput 事件
- * @author wangfupeng
- */
-
-import { Editor, Transforms, Range } from 'slate'
-import { DomEditor } from '../../editor/dom-editor'
-import { IDomEditor } from '../../editor/interface'
-import TextArea from '../TextArea'
-import { hasEditableTarget } from '../helpers'
-import { DOMStaticRange } from '../../utils/dom'
-import { HAS_BEFORE_INPUT_SUPPORT } from '../../utils/ua'
-import { EDITOR_TO_CAN_PASTE } from '../../utils/weak-maps'
-
-// 补充 beforeInput event 的属性
-interface BeforeInputEventType {
-  data: string | null
-  dataTransfer: DataTransfer | null
-  getTargetRanges(): DOMStaticRange[]
-  inputType: string
-  isComposing: boolean
-}
-
-function handleBeforeInput(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as Event & BeforeInputEventType
-  const { readOnly } = editor.getConfig()
-
-  if (!HAS_BEFORE_INPUT_SUPPORT) return // 有些浏览器完全不支持 beforeInput ,会用 keypress 和 keydown 兼容
-  if (readOnly) return
-  if (!hasEditableTarget(editor, event.target)) return
-
-  const { selection } = editor
-  const { inputType: type } = event
-  const data = event.dataTransfer || event.data || undefined
-
-  // These two types occur while a user is composing text and can't be
-  // cancelled. Let them through and wait for the composition to end.
-  if (type === 'insertCompositionText' || type === 'deleteCompositionText') {
-    return
-  }
-
-  // 阻止默认行为,劫持所有的富文本输入
-  event.preventDefault()
-
-  // COMPAT: For the deleting forward/backward input types we don't want
-  // to change the selection because it is the range that will be deleted,
-  // and those commands determine that for themselves.
-  if (!type.startsWith('delete') || type.startsWith('deleteBy')) {
-    const [targetRange] = event.getTargetRanges()
-
-    if (targetRange) {
-      const range = DomEditor.toSlateRange(editor, targetRange, {
-        exactMatch: false,
-        suppressThrow: false,
-      })
-      if (!selection || !Range.equals(selection, range)) {
-        Transforms.select(editor, range)
-      }
-    }
-  }
-
-  // COMPAT: If the selection is expanded, even if the command seems like
-  // a delete forward/backward command it should delete the selection.
-  if (selection && Range.isExpanded(selection) && type.startsWith('delete')) {
-    const direction = type.endsWith('Backward') ? 'backward' : 'forward'
-    Editor.deleteFragment(editor, { direction })
-    return
-  }
-
-  // 根据 beforeInput 的 event.inputType
-  switch (type) {
-    case 'deleteByComposition':
-    case 'deleteByCut':
-    case 'deleteByDrag': {
-      Editor.deleteFragment(editor)
-      break
-    }
-
-    case 'deleteContent':
-    case 'deleteContentForward': {
-      Editor.deleteForward(editor)
-      break
-    }
-
-    case 'deleteContentBackward': {
-      Editor.deleteBackward(editor)
-      break
-    }
-
-    case 'deleteEntireSoftLine': {
-      Editor.deleteBackward(editor, { unit: 'line' })
-      Editor.deleteForward(editor, { unit: 'line' })
-      break
-    }
-
-    case 'deleteHardLineBackward': {
-      Editor.deleteBackward(editor, { unit: 'block' })
-      break
-    }
-
-    case 'deleteSoftLineBackward': {
-      Editor.deleteBackward(editor, { unit: 'line' })
-      break
-    }
-
-    case 'deleteHardLineForward': {
-      Editor.deleteForward(editor, { unit: 'block' })
-      break
-    }
-
-    case 'deleteSoftLineForward': {
-      Editor.deleteForward(editor, { unit: 'line' })
-      break
-    }
-
-    case 'deleteWordBackward': {
-      Editor.deleteBackward(editor, { unit: 'word' })
-      break
-    }
-
-    case 'deleteWordForward': {
-      Editor.deleteForward(editor, { unit: 'word' })
-      break
-    }
-
-    case 'insertLineBreak':
-    case 'insertParagraph': {
-      Editor.insertBreak(editor)
-      break
-    }
-
-    case 'insertFromDrop':
-    case 'insertFromPaste':
-    case 'insertFromYank':
-    case 'insertReplacementText':
-    case 'insertText': {
-      if (type === 'insertFromPaste') {
-        if (!EDITOR_TO_CAN_PASTE.get(editor)) break // 不可默认粘贴
-      }
-
-      if (data instanceof DataTransfer) {
-        // 这里处理非纯文本(如 html 图片文件等)的粘贴。对于纯文本的粘贴,使用 paste 事件
-        editor.insertData(data)
-      } else if (typeof data === 'string') {
-        Editor.insertText(editor, data)
-      }
-      break
-    }
-  }
-}
-
-export default handleBeforeInput
diff --git a/packages/core/src/text-area/event-handlers/blur.ts b/packages/core/src/text-area/event-handlers/blur.ts
deleted file mode 100644
index 9a213f6b9..000000000
--- a/packages/core/src/text-area/event-handlers/blur.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * @description 处理 onblur 事件
- * @author wangfupeng
- */
-
-import { Element } from 'slate'
-import { DomEditor } from '../../editor/dom-editor'
-import { IDomEditor } from '../../editor/interface'
-import TextArea from '../TextArea'
-import { hasEditableTarget } from '../helpers'
-import { isDOMElement, isDOMNode } from '../../utils/dom'
-import { IS_FOCUSED } from '../../utils/weak-maps'
-import { IS_SAFARI } from '../../utils/ua'
-
-function handleOnBlur(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as FocusEvent
-
-  const { isUpdatingSelection, latestElement } = textarea
-  const { readOnly } = editor.getConfig()
-
-  if (readOnly) return
-  if (isUpdatingSelection) return
-  if (!hasEditableTarget(editor, event.target)) return
-  const root = DomEditor.findDocumentOrShadowRoot(editor)
-
-  // COMPAT: If the current `activeElement` is still the previous
-  // one, this is due to the window being blurred when the tab
-  // itself becomes unfocused, so we want to abort early to allow to
-  // editor to stay focused when the tab becomes focused again.
-  if (latestElement === root.activeElement) return
-
-  // relatedTarget 即 blur 之后又 focus 到了哪个元素,如果没有则是 null
-  const { relatedTarget } = event
-  const el = DomEditor.toDOMNode(editor, editor)
-
-  // COMPAT: The event should be ignored if the focus is returning
-  // to the editor from an embedded editable element (eg. an <input>
-  // element inside a void node).
-  if (relatedTarget === el) {
-    return
-  }
-
-  // COMPAT: The event should be ignored if the focus is moving from
-  // the editor to inside a void node's spacer element.
-  if (isDOMElement(relatedTarget) && relatedTarget.hasAttribute('data-slate-spacer')) {
-    return
-  }
-
-  // COMPAT: The event should be ignored if the focus is moving to a
-  // non- editable section of an element that isn't a void node (eg.
-  // a list item of the check list example).
-  if (
-    relatedTarget != null &&
-    isDOMNode(relatedTarget) &&
-    DomEditor.hasDOMNode(editor, relatedTarget)
-  ) {
-    const node = DomEditor.toSlateNode(editor, relatedTarget)
-    if (Element.isElement(node) && !editor.isVoid(node)) {
-      return
-    }
-  }
-
-  // COMPAT: Safari doesn't always remove the selection even if the content-
-  // editable element no longer has focus. Refer to:
-  // https://stackoverflow.com/questions/12353247/force-contenteditable-div-to-stop-accepting-input-after-it-loses-focus-under-web
-  // 修复在 Safari 下,即使 contenteditable 元素非聚焦状态,并不会删除所选内容
-  if (IS_SAFARI) {
-    const domSelection = root.getSelection()
-    domSelection?.removeAllRanges()
-  }
-
-  // 检验完毕,可正式触发 onblur
-  IS_FOCUSED.delete(editor)
-}
-
-export default handleOnBlur
diff --git a/packages/core/src/text-area/event-handlers/click.ts b/packages/core/src/text-area/event-handlers/click.ts
deleted file mode 100644
index 2e2e106c9..000000000
--- a/packages/core/src/text-area/event-handlers/click.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @description 处理 click 事件
- * @author wangfupeng
- */
-
-import { Editor, Path, Transforms, Node } from 'slate'
-import { IDomEditor } from '../../editor/interface'
-import { DomEditor } from '../../editor/dom-editor'
-import TextArea from '../TextArea'
-import { hasTarget } from '../helpers'
-import { isDOMNode } from '../../utils/dom'
-
-function handleOnClick(event: Event, textarea: TextArea, editor: IDomEditor) {
-  const { readOnly } = editor.getConfig()
-
-  if (readOnly) return
-  if (!hasTarget(editor, event.target)) return
-  if (!isDOMNode(event.target)) return
-
-  const node = DomEditor.toSlateNode(editor, event.target)
-  const path = DomEditor.findPath(editor, node)
-
-  // At this time, the Slate document may be arbitrarily different,
-  // because onClick handlers can change the document before we get here.
-  // Therefore we must check that this path actually exists,
-  // and that it still refers to the same node.
-  if (Editor.hasPath(editor, path)) {
-    const lookupNode = Node.get(editor, path)
-    if (lookupNode === node) {
-      const start = Editor.start(editor, path)
-      const end = Editor.end(editor, path)
-
-      const startVoid = Editor.void(editor, { at: start })
-      const endVoid = Editor.void(editor, { at: end })
-
-      if (startVoid && endVoid && Path.equals(startVoid[1], endVoid[1])) {
-        const range = Editor.range(editor, start)
-        Transforms.select(editor, range)
-      }
-    }
-  }
-}
-
-export default handleOnClick
diff --git a/packages/core/src/text-area/event-handlers/composition.ts b/packages/core/src/text-area/event-handlers/composition.ts
deleted file mode 100644
index 7aa20e965..000000000
--- a/packages/core/src/text-area/event-handlers/composition.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * @description 监听 composition 事件
- * @author wangfupeng
- */
-
-import { Editor, Range, Element } from 'slate'
-import { IDomEditor } from '../../editor/interface'
-import { DomEditor } from '../../editor/dom-editor'
-import TextArea from '../TextArea'
-import { hasEditableTarget } from '../helpers'
-import { IS_SAFARI, IS_CHROME, IS_FIREFOX } from '../../utils/ua'
-import { DOMNode } from '../../utils/dom'
-import { hidePlaceholder } from '../place-holder'
-import { editorSelectionToDOM } from '../syncSelection'
-
-const EDITOR_TO_TEXT: WeakMap<IDomEditor, string> = new WeakMap()
-const EDITOR_TO_START_CONTAINER: WeakMap<IDomEditor, DOMNode> = new WeakMap()
-
-/**
- * composition start 事件
- * @param e event
- * @param textarea textarea
- * @param editor editor
- */
-export function handleCompositionStart(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as CompositionEvent
-
-  if (!hasEditableTarget(editor, event.target)) return
-
-  const { selection } = editor
-  if (selection && Range.isExpanded(selection)) {
-    Editor.deleteFragment(editor)
-
-    Promise.resolve().then(() => {
-      // deleteFragment 会在一个 Promise 后更新 dom,导致浏览器选区不正确
-      // 因此这里延迟一下再设置选区,使选区在正确位置
-      // 这里 model 选区没有发生变化,不能使用 editor.restoreSelection
-      // restoreSelection 会对比前后 model 选区是否相同,相同就不更新了
-      editorSelectionToDOM(textarea, editor, true)
-    })
-  }
-
-  if (selection && Range.isCollapsed(selection)) {
-    // 记录下 dom text ,以便触发 maxLength 时使用
-    const domRange = DomEditor.toDOMRange(editor, selection)
-    const startContainer = domRange.startContainer
-    const curText = startContainer.textContent || ''
-    EDITOR_TO_TEXT.set(editor, curText)
-
-    // 记录下 dom range startContainer
-    EDITOR_TO_START_CONTAINER.set(editor, startContainer)
-  }
-  textarea.isComposing = true
-
-  // 隐藏 placeholder
-  hidePlaceholder(textarea, editor)
-}
-
-/**
- * composition update 事件
- * @param e event
- * @param textarea textarea
- * @param editor editor
- */
-export function handleCompositionUpdate(event: Event, textarea: TextArea, editor: IDomEditor) {
-  if (!hasEditableTarget(editor, event.target)) return
-
-  textarea.isComposing = true
-}
-
-/**
- * composition end 事件
- * @param e event
- * @param textarea textarea
- * @param editor editor
- */
-export function handleCompositionEnd(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as CompositionEvent
-
-  if (!hasEditableTarget(editor, event.target)) return
-  textarea.isComposing = false
-
-  const { selection } = editor
-  if (selection == null) return
-
-  // 清理可能暴露的 text 节点
-  // 例如 chrome 在链接后面,输入拼音,就会出现有暴露出来的 text node
-  if (IS_CHROME || IS_FIREFOX) {
-    DomEditor.cleanExposedTexNodeInSelectionBlock(editor)
-  }
-
-  // 在中文输入法下,浏览器的默认行为会使一些dom产生不可逆的变化
-  // 比如在 Safari 中 url 后面输入,初始是 a > span > spans
-  // 输入后变成 span > span > a
-  // 因此需要设置新的 key 来强刷整行
-  const start = Range.isBackward(selection) ? selection.focus : selection.anchor
-  const [paragraph] = Editor.node(editor, [start.path[0]])
-
-  for (let i = 0; i < start.path.length; i++) {
-    const [node] = Editor.node(editor, start.path.slice(0, i + 1))
-    if (Element.isElement(node)) {
-      if (((IS_SAFARI || IS_FIREFOX) && node.type === 'link') || node.type === 'code') {
-        DomEditor.setNewKey(paragraph)
-        break
-      }
-    }
-  }
-
-  const { data } = event
-  if (!data) return
-
-  // 检查 maxLength -【注意】这里只处理拼音输入的 maxLength 限制。其他限制,在插件 with-max-length.ts 中处理
-  const { maxLength } = editor.getConfig()
-  if (maxLength) {
-    const leftLengthOfMaxLength = DomEditor.getLeftLengthOfMaxLength(editor)
-    if (leftLengthOfMaxLength < data.length) {
-      const domRange = DomEditor.toDOMRange(editor, selection)
-      domRange.startContainer.textContent = EDITOR_TO_TEXT.get(editor) || ''
-      if (leftLengthOfMaxLength > 0) {
-        // 剩余长度 >0 ,但小于 data 长度,截取一部分插入
-        Editor.insertText(editor, data.slice(0, leftLengthOfMaxLength))
-      }
-      textarea.changeViewState() // 重新定位光标
-    } else {
-      Editor.insertText(editor, data)
-    }
-  } else {
-    Editor.insertText(editor, data)
-  }
-
-  // 检查拼音输入是否夸 DOM 节点了,解决 wangEditor-v5/issues/47
-  if (!IS_SAFARI) {
-    setTimeout(() => {
-      const { selection } = editor
-      if (selection == null) return
-      const oldStartContainer = EDITOR_TO_START_CONTAINER.get(editor) // 拼音输入开始时的 text node
-      if (oldStartContainer == null) return
-      const curStartContainer = DomEditor.toDOMRange(editor, selection).startContainer // 拼音输入结束时的 text node
-      if (curStartContainer === oldStartContainer) {
-        // 拼音输入的开始和结束,都在同一个 text node ,则不做处理
-        return
-      }
-      // 否则,拼音输入的开始和结束,不是同一个 text node ,则将第一个 text node 重新设置 text
-      oldStartContainer.textContent = EDITOR_TO_TEXT.get(editor) || ''
-    })
-  }
-}
diff --git a/packages/core/src/text-area/event-handlers/copy.ts b/packages/core/src/text-area/event-handlers/copy.ts
deleted file mode 100644
index 2b1a05271..000000000
--- a/packages/core/src/text-area/event-handlers/copy.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @description 处理 copy 事件
- * @author wangfupeng
- */
-
-import { IDomEditor } from '../../editor/interface'
-// import { DomEditor } from '../../editor/dom-editor'
-import TextArea from '../TextArea'
-import { hasEditableTarget } from '../helpers'
-
-function handleOnCopy(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as ClipboardEvent
-
-  if (!hasEditableTarget(editor, event.target)) return
-  event.preventDefault()
-
-  const data = event.clipboardData
-  if (data == null) return
-  editor.setFragmentData(data)
-}
-
-export default handleOnCopy
diff --git a/packages/core/src/text-area/event-handlers/cut.ts b/packages/core/src/text-area/event-handlers/cut.ts
deleted file mode 100644
index f0bff0374..000000000
--- a/packages/core/src/text-area/event-handlers/cut.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @description 处理 cut 事件
- * @author wangfupeng
- */
-
-import { Editor, Range, Node, Transforms } from 'slate'
-import { IDomEditor } from '../../editor/interface'
-import TextArea from '../TextArea'
-import { hasEditableTarget } from '../helpers'
-
-function handleOnCut(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as ClipboardEvent
-  const { selection } = editor
-  const { readOnly } = editor.getConfig()
-
-  if (readOnly) return
-  if (!hasEditableTarget(editor, event.target)) return
-
-  event.preventDefault()
-
-  const data = event.clipboardData
-  if (data == null) return
-  editor.setFragmentData(data)
-
-  if (selection) {
-    if (Range.isExpanded(selection)) {
-      Editor.deleteFragment(editor)
-    } else {
-      const node = Node.parent(editor, selection.anchor.path)
-      if (Editor.isVoid(editor, node)) {
-        Transforms.delete(editor)
-      }
-    }
-  }
-}
-
-export default handleOnCut
diff --git a/packages/core/src/text-area/event-handlers/drag.ts b/packages/core/src/text-area/event-handlers/drag.ts
deleted file mode 100644
index dde3a30c3..000000000
--- a/packages/core/src/text-area/event-handlers/drag.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @description 处理 dragover 事件
- * @author wangfupeng
- */
-
-import { Editor, Transforms } from 'slate'
-import { DomEditor } from '../../editor/dom-editor'
-import { IDomEditor } from '../../editor/interface'
-import TextArea from '../TextArea'
-import { hasTarget } from '../helpers'
-
-export function handleOnDragstart(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as DragEvent
-  if (!hasTarget(editor, event.target)) return
-
-  const { readOnly } = editor.getConfig()
-  if (readOnly) return
-
-  const node = DomEditor.toSlateNode(editor, event.target)
-  const path = DomEditor.findPath(editor, node)
-  const voidMatch = Editor.isVoid(editor, node) || Editor.void(editor, { at: path, voids: true })
-
-  // If starting a drag on a void node, make sure it is selected
-  // so that it shows up in the selection's fragment.
-  if (voidMatch) {
-    const range = Editor.range(editor, path)
-    Transforms.select(editor, range)
-  }
-
-  const data = event.dataTransfer
-  if (data == null) return
-
-  textarea.isDraggingInternally = true
-
-  editor.setFragmentData(data)
-}
-
-export function handleOnDragover(event: Event, textarea: TextArea, editor: IDomEditor) {
-  if (!hasTarget(editor, event.target)) return
-
-  // Only when the target is void, call `preventDefault` to signal
-  // that drops are allowed. Editable content is droppable by
-  // default, and calling `preventDefault` hides the cursor.
-  const node = DomEditor.toSlateNode(editor, event.target)
-  if (Editor.isVoid(editor, node)) {
-    event.preventDefault()
-  }
-}
-
-export function handleOnDragend(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as DragEvent
-  const { readOnly } = editor.getConfig()
-
-  if (readOnly) return
-  if (!textarea.isDraggingInternally) return
-  if (!hasTarget(editor, event.target)) return
-
-  textarea.isDraggingInternally = false
-}
diff --git a/packages/core/src/text-area/event-handlers/drop.ts b/packages/core/src/text-area/event-handlers/drop.ts
deleted file mode 100644
index 107fe11e7..000000000
--- a/packages/core/src/text-area/event-handlers/drop.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * @description 处理 drop 事件
- * @author wangfupeng
- */
-
-import { Transforms } from 'slate'
-import { IDomEditor } from '../../editor/interface'
-import { DomEditor } from '../../editor/dom-editor'
-import TextArea from '../TextArea'
-import { hasTarget } from '../helpers'
-import { HAS_BEFORE_INPUT_SUPPORT, IS_SAFARI } from '../../utils/ua'
-
-function handleOnDrop(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as DragEvent
-  const data = event.dataTransfer
-  const { readOnly } = editor.getConfig()
-
-  if (readOnly) return
-  if (!hasTarget(editor, event.target)) return
-  if (data == null) return
-
-  if (HAS_BEFORE_INPUT_SUPPORT) {
-    if (IS_SAFARI) {
-      // safari 不支持拖拽文件
-      if (data.files.length > 0) return
-    }
-  }
-
-  event.preventDefault()
-
-  // Keep a reference to the dragged range before updating selection
-  const draggedRange = editor.selection
-  const range = DomEditor.findEventRange(editor, event)
-  Transforms.select(editor, range)
-
-  if (textarea.isDraggingInternally) {
-    if (draggedRange) {
-      Transforms.delete(editor, {
-        at: draggedRange,
-      })
-    }
-
-    textarea.isDraggingInternally = false
-  }
-
-  editor.insertData(data)
-
-  // When dragging from another source into the editor, it's possible
-  // that the current editor does not have focus.
-  if (!editor.isFocused()) {
-    editor.focus()
-  }
-}
-
-export default handleOnDrop
diff --git a/packages/core/src/text-area/event-handlers/focus.ts b/packages/core/src/text-area/event-handlers/focus.ts
deleted file mode 100644
index 35f701464..000000000
--- a/packages/core/src/text-area/event-handlers/focus.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @description 处理 onfocus 事件
- * @author wangfupeng
- */
-
-import { IDomEditor } from '../../editor/interface'
-import { DomEditor } from '../../editor/dom-editor'
-import TextArea from '../TextArea'
-import { IS_FIREFOX } from '../../utils/ua'
-import { IS_FOCUSED } from '../../utils/weak-maps'
-
-function handleOnFocus(event: Event, textarea: TextArea, editor: IDomEditor) {
-  const el = DomEditor.toDOMNode(editor, editor)
-  const root = DomEditor.findDocumentOrShadowRoot(editor)
-  textarea.latestElement = root.activeElement
-
-  // COMPAT: If the editor has nested editable elements, the focus
-  // can go to them. In Firefox, this must be prevented because it
-  // results in issues with keyboard navigation. (2017/03/30)
-  if (IS_FIREFOX && event.target !== el) {
-    el.focus()
-    return
-  }
-
-  IS_FOCUSED.set(editor, true)
-}
-
-export default handleOnFocus
diff --git a/packages/core/src/text-area/event-handlers/index.ts b/packages/core/src/text-area/event-handlers/index.ts
deleted file mode 100644
index cc49b8666..000000000
--- a/packages/core/src/text-area/event-handlers/index.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * @description textarea event handlers entry
- * @author wangfupeng
- */
-
-import handleBeforeInput from './beforeInput'
-import handleOnBlur from './blur'
-import handleOnFocus from './focus'
-import handleOnClick from './click'
-import {
-  handleCompositionStart,
-  handleCompositionEnd,
-  handleCompositionUpdate,
-} from './composition'
-import handleOnKeydown from './keydown'
-import handleKeypress from './keypress'
-import handleOnCopy from './copy'
-import handleOnCut from './cut'
-import handleOnPaste from './paste'
-import { handleOnDragover, handleOnDragstart, handleOnDragend } from './drag'
-import handleOnDrop from './drop'
-
-const eventConf = {
-  beforeinput: handleBeforeInput,
-  blur: handleOnBlur,
-  focus: handleOnFocus,
-  click: handleOnClick,
-  compositionstart: handleCompositionStart,
-  compositionend: handleCompositionEnd,
-  compositionupdate: handleCompositionUpdate,
-  keydown: handleOnKeydown,
-  keypress: handleKeypress,
-  copy: handleOnCopy,
-  cut: handleOnCut,
-  paste: handleOnPaste,
-  dragover: handleOnDragover,
-  dragstart: handleOnDragstart,
-  dragend: handleOnDragend,
-  drop: handleOnDrop,
-}
-
-export default eventConf
diff --git a/packages/core/src/text-area/event-handlers/keydown.ts b/packages/core/src/text-area/event-handlers/keydown.ts
deleted file mode 100644
index f58a4865d..000000000
--- a/packages/core/src/text-area/event-handlers/keydown.ts
+++ /dev/null
@@ -1,262 +0,0 @@
-/**
- * @description 监听 onKeydown 事件
- * @author wangfupeng
- */
-
-import { isHotkey } from 'is-hotkey'
-import { Editor, Transforms, Range, Node, Element } from 'slate'
-import { IDomEditor } from '../../editor/interface'
-import TextArea from '../TextArea'
-import Hotkeys from '../../utils/hotkeys'
-import { hasEditableTarget } from '../helpers'
-import { HAS_BEFORE_INPUT_SUPPORT, IS_CHROME, IS_SAFARI } from '../../utils/ua'
-import { EDITOR_TO_TOOLBAR, EDITOR_TO_HOVER_BAR } from '../../utils/weak-maps'
-
-function preventDefault(event: Event) {
-  event.preventDefault()
-}
-
-// 触发 menu 快捷键
-function triggerMenuHotKey(editor: IDomEditor, event: KeyboardEvent) {
-  const toolbar = EDITOR_TO_TOOLBAR.get(editor)
-  const toolbarMenus = toolbar && toolbar.getMenus()
-  const hoverbar = EDITOR_TO_HOVER_BAR.get(editor)
-  const hoverbarMenus = hoverbar && hoverbar.getMenus()
-
-  // 合并所有 menus
-  const allMenus = { ...toolbarMenus, ...hoverbarMenus }
-  for (let key in allMenus) {
-    const menu = allMenus[key]
-    const { hotkey } = menu
-    if (hotkey && isHotkey(hotkey, event)) {
-      const disabled = menu.isDisabled(editor)
-      if (!disabled) {
-        const val = menu.getValue(editor)
-        menu.exec(editor, val) // 执行 menu 命令
-      }
-    }
-  }
-}
-
-function handleOnKeydown(e: Event, textarea: TextArea, editor: IDomEditor) {
-  const event = e as KeyboardEvent
-  const { selection } = editor
-  const { readOnly } = editor.getConfig()
-
-  if (readOnly) return
-  if (textarea.isComposing) return
-  if (!hasEditableTarget(editor, event.target)) return
-
-  // 触发 menu 快捷键
-  triggerMenuHotKey(editor, event)
-
-  // tab
-  if (Hotkeys.isTab(event)) {
-    preventDefault(event)
-    editor.handleTab()
-    return
-  }
-
-  // COMPAT: Since we prevent the default behavior on
-  // `beforeinput` events, the browser doesn't think there's ever
-  // any history stack to undo or redo, so we have to manage these
-  // hotkeys ourselves. (2019/11/06)
-  if (Hotkeys.isRedo(event)) {
-    preventDefault(event)
-    if (typeof editor.redo === 'function') {
-      editor.redo()
-    }
-    return
-  }
-  if (Hotkeys.isUndo(event)) {
-    preventDefault(event)
-    if (typeof editor.undo === 'function') {
-      editor.undo()
-    }
-    return
-  }
-
-  // COMPAT: Certain browsers don't handle the selection updates
-  // properly. In Chrome, the selection isn't properly extended.
-  // And in Firefox, the selection isn't properly collapsed.
-  // (2017/10/17)
-  if (Hotkeys.isMoveLineBackward(event)) {
-    preventDefault(event)
-    Transforms.move(editor, { unit: 'line', reverse: true }) // Transforms.move 修改 selection
-    return
-  }
-  if (Hotkeys.isMoveLineForward(event)) {
-    preventDefault(event)
-    Transforms.move(editor, { unit: 'line' })
-    return
-  }
-
-  if (Hotkeys.isExtendLineBackward(event)) {
-    preventDefault(event)
-    Transforms.move(editor, { unit: 'line', edge: 'focus', reverse: true })
-    return
-  }
-  if (Hotkeys.isExtendLineForward(event)) {
-    preventDefault(event)
-    Transforms.move(editor, { unit: 'line', edge: 'focus' })
-    return
-  }
-
-  // COMPAT: If a void node is selected, or a zero-width text node
-  // adjacent to an inline is selected, we need to handle these
-  // hotkeys manually because browsers won't be able to skip over
-  // the void node with the zero-width space not being an empty
-  // string.
-  // todo 移动 word 考虑 Node 排版模式是否为 rtl 的情况
-  if (Hotkeys.isMoveBackward(event)) {
-    preventDefault(event)
-
-    if (selection && Range.isCollapsed(selection)) {
-      Transforms.move(editor, { reverse: true })
-    } else {
-      Transforms.collapse(editor, { edge: 'start' })
-    }
-    return
-  }
-  if (Hotkeys.isMoveForward(event)) {
-    preventDefault(event)
-
-    if (selection && Range.isCollapsed(selection)) {
-      Transforms.move(editor)
-    } else {
-      Transforms.collapse(editor, { edge: 'end' })
-    }
-    return
-  }
-
-  if (Hotkeys.isMoveWordBackward(event)) {
-    preventDefault(event)
-
-    if (selection && Range.isExpanded(selection)) {
-      Transforms.collapse(editor, { edge: 'focus' })
-    }
-
-    Transforms.move(editor, { unit: 'word', reverse: true })
-    return
-  }
-  if (Hotkeys.isMoveWordForward(event)) {
-    preventDefault(event)
-
-    if (selection && Range.isExpanded(selection)) {
-      Transforms.collapse(editor, { edge: 'focus' })
-    }
-
-    Transforms.move(editor, { unit: 'word' })
-    return
-  }
-
-  if (Hotkeys.isSelectAll(event)) {
-    preventDefault(event)
-    editor.selectAll()
-    return
-  }
-
-  // COMPAT: Certain browsers don't support the `beforeinput` event, so we
-  // fall back to guessing at the input intention for hotkeys.
-  // COMPAT: In iOS, some of these hotkeys are handled in the
-  if (!HAS_BEFORE_INPUT_SUPPORT) {
-    // 这里是兼容不完全支持 beforeInput 的浏览器。对于支持 beforeInput 的浏览器,会用 beforeinput 事件处理
-    // 这里兼容了 beforeInput 的一些功能键(如回车、删除等)没有文本输入。文本输入使用 keypress 兼容。
-
-    // We don't have a core behavior for these, but they change the
-    // DOM if we don't prevent them, so we have to.
-    if (Hotkeys.isBold(event) || Hotkeys.isItalic(event) || Hotkeys.isTransposeCharacter(event)) {
-      preventDefault(event)
-      return
-    }
-
-    if (Hotkeys.isSplitBlock(event)) {
-      preventDefault(event)
-      Editor.insertBreak(editor)
-      return
-    }
-
-    if (Hotkeys.isDeleteBackward(event)) {
-      preventDefault(event)
-      if (selection && Range.isExpanded(selection)) {
-        Editor.deleteFragment(editor, { direction: 'backward' })
-      } else {
-        Editor.deleteBackward(editor)
-      }
-      return
-    }
-    if (Hotkeys.isDeleteForward(event)) {
-      preventDefault(event)
-      if (selection && Range.isExpanded(selection)) {
-        Editor.deleteFragment(editor, { direction: 'forward' })
-      } else {
-        Editor.deleteForward(editor)
-      }
-      return
-    }
-
-    if (Hotkeys.isDeleteLineBackward(event)) {
-      preventDefault(event)
-      if (selection && Range.isExpanded(selection)) {
-        Editor.deleteFragment(editor, { direction: 'backward' })
-      } else {
-        Editor.deleteBackward(editor, { unit: 'line' })
-      }
-      return
-    }
-    if (Hotkeys.isDeleteLineForward(event)) {
-      preventDefault(event)
-      if (selection && Range.isExpanded(selection)) {
-        Editor.deleteFragment(editor, { direction: 'forward' })
-      } else {
-        Editor.deleteForward(editor, { unit: 'line' })
-      }
-      return
-    }
-
-    if (Hotkeys.isDeleteWordBackward(event)) {
-      preventDefault(event)
-      if (selection && Range.isExpanded(selection)) {
-        Editor.deleteFragment(editor, { direction: 'backward' })
-      } else {
-        Editor.deleteBackward(editor, { unit: 'word' })
-      }
-      return
-    }
-    if (Hotkeys.isDeleteWordForward(event)) {
-      preventDefault(event)
-      if (selection && Range.isExpanded(selection)) {
-        Editor.deleteFragment(editor, { direction: 'forward' })
-      } else {
-        Editor.deleteForward(editor, { unit: 'word' })
-      }
-      return
-    }
-  } else {
-    if (IS_CHROME || IS_SAFARI) {
-      // COMPAT: Chrome and Safari support `beforeinput` event but do not fire
-      // an event when deleting backwards in a selected void inline node
-      // 修复在 Chrome 和 Safari 中删除内容时,内联空节点被选中
-      if (
-        selection &&
-        (Hotkeys.isDeleteBackward(event) || Hotkeys.isDeleteForward(event)) &&
-        Range.isCollapsed(selection)
-      ) {
-        const currentNode = Node.parent(editor, selection.anchor.path)
-
-        if (
-          Element.isElement(currentNode) &&
-          Editor.isVoid(editor, currentNode) &&
-          Editor.isInline(editor, currentNode)
-        ) {
-          event.preventDefault()
-          Transforms.delete(editor, { unit: 'block' })
-
-          return
-        }
-      }
-    }
-  }
-}
-
-export default handleOnKeydown
diff --git a/packages/core/src/text-area/event-handlers/keypress.ts b/packages/core/src/text-area/event-handlers/keypress.ts
deleted file mode 100644
index 9c42066f5..000000000
--- a/packages/core/src/text-area/event-handlers/keypress.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @description 监听 keypress 事件
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import { IDomEditor } from '../../editor/interface'
-import TextArea from '../TextArea'
-import { HAS_BEFORE_INPUT_SUPPORT } from '../../utils/ua'
-import { hasEditableTarget } from '../helpers'
-
-// 【注意】虽然 keypress 事件已经过时(建议用 keydown 取代),但这里是为了兼容 beforeinput ,所以不会在高级浏览器生效,不用升级 keydown
-
-function handleKeypress(event: Event, textarea: TextArea, editor: IDomEditor) {
-  // 这里是兼容不完全支持 beforeInput 的浏览器。对于支持 beforeInput 的浏览器,会用 beforeinput 事件处理
-  if (HAS_BEFORE_INPUT_SUPPORT) return
-
-  const { readOnly } = editor.getConfig()
-  if (readOnly) return
-  if (!hasEditableTarget(editor, event.target)) return
-
-  event.preventDefault()
-
-  const text = (event as any).key as string
-
-  // 这里只兼容 beforeInput 的 insertText 类型,其他的(如删除、换行)使用 keydown 来兼容
-  Editor.insertText(editor, text)
-}
-
-export default handleKeypress
diff --git a/packages/core/src/text-area/event-handlers/paste.ts b/packages/core/src/text-area/event-handlers/paste.ts
deleted file mode 100644
index 128bda27c..000000000
--- a/packages/core/src/text-area/event-handlers/paste.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @description 处理 paste 事件
- * @author wangfupeng
- */
-
-import { IDomEditor } from '../../editor/interface'
-import { DomEditor } from '../../editor/dom-editor'
-import TextArea from '../TextArea'
-import { hasEditableTarget } from '../helpers'
-import { isPlainTextOnlyPaste } from '../../utils/dom'
-import { HAS_BEFORE_INPUT_SUPPORT } from '../../utils/ua'
-import { EDITOR_TO_CAN_PASTE } from '../../utils/weak-maps'
-
-function handleOnPaste(e: Event, textarea: TextArea, editor: IDomEditor) {
-  EDITOR_TO_CAN_PASTE.set(editor, true) // 标记为:可执行默认粘贴
-
-  const event = e as ClipboardEvent
-  const { readOnly } = editor.getConfig()
-
-  if (readOnly) return
-  if (!hasEditableTarget(editor, event.target)) return
-
-  const { customPaste } = editor.getConfig()
-  if (customPaste) {
-    const res = customPaste(editor, event)
-    if (res === false) {
-      // 自行实现粘贴,不执行默认粘贴
-      EDITOR_TO_CAN_PASTE.set(editor, false) // 标记为:不可执行默认粘贴
-      return
-    }
-  }
-
-  // 如果支持 beforeInput 且不是纯粘贴文本(如 html、图片文件),则使用 beforeInput 来实现
-  // 这里只处理:不支持 beforeInput 或者 粘贴纯文本
-  if (HAS_BEFORE_INPUT_SUPPORT && !isPlainTextOnlyPaste(event)) return
-
-  event.preventDefault()
-
-  const data = event.clipboardData
-  if (data == null) return
-  editor.insertData(data)
-}
-
-export default handleOnPaste
diff --git a/packages/core/src/text-area/helpers.ts b/packages/core/src/text-area/helpers.ts
deleted file mode 100644
index fbf07743b..000000000
--- a/packages/core/src/text-area/helpers.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @description textarea helper fns
- * @author wangfupeng
- */
-
-import { Editor } from 'slate'
-import { DOMRange, DOMNode, isDOMNode } from '../utils/dom'
-import { IDomEditor } from '../editor/interface'
-import { DomEditor } from '../editor/dom-editor'
-
-/**
- * Check if two DOM range objects are equal.
- */
-export const isRangeEqual = (a: DOMRange, b: DOMRange) => {
-  return (
-    (a.startContainer === b.startContainer &&
-      a.startOffset === b.startOffset &&
-      a.endContainer === b.endContainer &&
-      a.endOffset === b.endOffset) ||
-    (a.startContainer === b.endContainer &&
-      a.startOffset === b.endOffset &&
-      a.endContainer === b.startContainer &&
-      a.endOffset === b.startOffset)
-  )
-}
-
-/**
- * Check if the target is editable and in the editor.
- */
-export function hasEditableTarget(
-  editor: IDomEditor,
-  target: EventTarget | null
-): target is DOMNode {
-  return isDOMNode(target) && DomEditor.hasDOMNode(editor, target, { editable: true })
-}
-
-/**
- * Check if the target is inside void and in an non-readonly editor.
- */
-export function isTargetInsideNonReadonlyVoid(
-  editor: IDomEditor,
-  target: EventTarget | null
-): boolean {
-  const { readOnly } = editor.getConfig()
-  if (readOnly) return false
-
-  const slateNode = hasTarget(editor, target) && DomEditor.toSlateNode(editor, target)
-  return Editor.isVoid(editor, slateNode)
-}
-
-/**
- * Check if the target is in the editor.
- */
-export function hasTarget(editor: IDomEditor, target: EventTarget | null): target is DOMNode {
-  return isDOMNode(target) && DomEditor.hasDOMNode(editor, target)
-}
-
-/**
- * Check if a DOM event is overrode by a handler.
- */
-export function isDOMEventHandled(event: Event, handler?: (event: Event) => void | boolean) {
-  if (!handler) {
-    return false
-  }
-
-  // The custom event handler may return a boolean to specify whether the event
-  // shall be treated as being handled or not.
-  const shouldTreatEventAsHandled = handler(event)
-
-  if (shouldTreatEventAsHandled != null) {
-    return shouldTreatEventAsHandled
-  }
-
-  return event.defaultPrevented
-}
diff --git a/packages/core/src/text-area/place-holder.ts b/packages/core/src/text-area/place-holder.ts
deleted file mode 100644
index 613649f0e..000000000
--- a/packages/core/src/text-area/place-holder.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * @description 显示/隐藏 placeholder
- * @author wangfupeng
- */
-
-import { IDomEditor } from '../editor/interface'
-import TextArea from './TextArea'
-import $ from '../utils/dom'
-
-/**
- * 处理 placeholder
- * @param textarea textarea
- * @param editor editor
- */
-export function handlePlaceholder(textarea: TextArea, editor: IDomEditor) {
-  const { placeholder } = editor.getConfig()
-  if (!placeholder) return
-
-  const isEmpty = editor.isEmpty()
-
-  // 内容为空,且目前未显示 placeholder ,则显示
-  if (isEmpty && !textarea.showPlaceholder && !textarea.isComposing) {
-    if (textarea.$placeholder == null) {
-      const $placeholder = $(`<div class="w-e-text-placeholder">${placeholder}</div>`)
-      textarea.$textAreaContainer.append($placeholder)
-      textarea.$placeholder = $placeholder
-    }
-    textarea.$placeholder.show()
-    textarea.showPlaceholder = true // 记录
-    return
-  }
-
-  // 内容不是空,且目前显示着 placeholder ,则隐藏
-  if (!isEmpty && textarea.showPlaceholder) {
-    textarea.$placeholder?.hide()
-    textarea.showPlaceholder = false // 记录
-    return
-  }
-}
-
-/**
- * 隐藏 placeholder (如拼音输入 compositionStart 时,要先隐藏,等 compositionEnd 时再判断是否显示)
- * @param textarea textarea
- * @param editor editor
- */
-export function hidePlaceholder(textarea: TextArea, editor: IDomEditor) {
-  const { placeholder } = editor.getConfig()
-  if (!placeholder) return
-
-  const isEmpty = editor.isEmpty()
-  if (!isEmpty) return
-
-  if (textarea.showPlaceholder) {
-    textarea.$placeholder?.hide()
-    textarea.showPlaceholder = false // 记录
-  }
-}
diff --git a/packages/core/src/text-area/syncSelection.ts b/packages/core/src/text-area/syncSelection.ts
deleted file mode 100644
index 21b87301e..000000000
--- a/packages/core/src/text-area/syncSelection.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-/**
- * @description 同步 selection
- * @author wangfupeng
- */
-
-import { Range, Transforms } from 'slate'
-import scrollIntoView from 'scroll-into-view-if-needed'
-
-import { IDomEditor } from '../editor/interface'
-import { DomEditor } from '../editor/dom-editor'
-import TextArea from './TextArea'
-import { EDITOR_TO_ELEMENT, IS_FOCUSED } from '../utils/weak-maps'
-import { IS_FIREFOX } from '../utils/ua'
-import { hasEditableTarget, isTargetInsideNonReadonlyVoid } from './helpers'
-import { DOMElement } from '../utils/dom'
-
-/**
- * editor onchange 时,将 editor selection 同步给 DOM
- * @param textarea textarea
- * @param editor editor
- * @param focus 是否强制更新选区
- */
-export function editorSelectionToDOM(textarea: TextArea, editor: IDomEditor, focus = false): void {
-  const { selection } = editor
-  const config = editor.getConfig()
-  const root = DomEditor.findDocumentOrShadowRoot(editor)
-  const domSelection = root.getSelection()
-
-  if (!domSelection) return
-  if (textarea.isComposing && !focus) return
-  if (!editor.isFocused()) return
-
-  const hasDomSelection = domSelection.type !== 'None'
-
-  // If the DOM selection is properly unset, we're done.
-  if (!selection && !hasDomSelection) return
-
-  // verify that the dom selection is in the editor
-  const editorElement = EDITOR_TO_ELEMENT.get(editor)!
-  let hasDomSelectionInEditor = false
-  if (
-    editorElement.contains(domSelection.anchorNode) &&
-    editorElement.contains(domSelection.focusNode)
-  ) {
-    hasDomSelectionInEditor = true
-  }
-
-  // If the DOM selection is in the editor and the editor selection is already correct, we're done.
-  if (hasDomSelection && hasDomSelectionInEditor && selection) {
-    const slateRange = DomEditor.toSlateRange(editor, domSelection, {
-      exactMatch: true,
-
-      // domSelection is not necessarily a valid Slate range
-      // (e.g. when clicking on contentEditable:false element)
-      suppressThrow: true,
-    })
-    if (slateRange && Range.equals(slateRange, selection)) {
-      let canReturn = true
-
-      // 选区在 table 时,需要特殊处理
-      if (Range.isCollapsed(selection)) {
-        const { anchorNode, anchorOffset } = domSelection
-        if (anchorNode === editorElement) {
-          const childNodes = editorElement.childNodes
-          let tableElem
-
-          // 光标在 table 前面时
-          tableElem = childNodes[anchorOffset] as DOMElement
-          if (tableElem && tableElem.matches('table')) {
-            canReturn = false // 不能就此结束,需要重置光标
-          }
-
-          // 光标在 table 后面时
-          tableElem = childNodes[anchorOffset - 1] as DOMElement
-          if (tableElem && tableElem.matches('table')) {
-            canReturn = false // 不能就此结束,需要重置光标
-          }
-        }
-      }
-
-      // 其他情况,就此结束
-      if (canReturn) return
-    }
-  }
-
-  // when <Editable/> is being controlled through external value
-  // then its children might just change - DOM responds to it on its own
-  // but Slate's value is not being updated through any operation
-  // and thus it doesn't transform selection on its own
-  if (selection && !DomEditor.hasRange(editor, selection)) {
-    editor.selection = DomEditor.toSlateRange(editor, domSelection, {
-      exactMatch: false,
-      suppressThrow: false,
-    })
-    return
-  }
-
-  // Otherwise the DOM selection is out of sync, so update it.
-  textarea.isUpdatingSelection = true
-
-  const newDomRange = selection && DomEditor.toDOMRange(editor, selection)
-  if (newDomRange) {
-    if (Range.isBackward(selection!)) {
-      domSelection.setBaseAndExtent(
-        newDomRange.endContainer,
-        newDomRange.endOffset,
-        newDomRange.startContainer,
-        newDomRange.startOffset
-      )
-    } else {
-      domSelection.setBaseAndExtent(
-        newDomRange.startContainer,
-        newDomRange.startOffset,
-        newDomRange.endContainer,
-        newDomRange.endOffset
-      )
-    }
-
-    // 滚动到选区
-    let leafEl = newDomRange.startContainer.parentElement! as Element
-    const spacer = leafEl.closest('[data-slate-spacer]')
-
-    // 这个 if 防止选中图片时发生滚动
-    if (!spacer) {
-      leafEl.getBoundingClientRect = newDomRange.getBoundingClientRect.bind(newDomRange)
-      const body = document.body
-      scrollIntoView(leafEl, {
-        scrollMode: 'if-needed',
-        boundary: config.scroll ? editorElement.parentElement : body, // issue 4215
-        block: 'end',
-        behavior: 'smooth',
-      })
-      // @ts-ignore
-      delete leafEl.getBoundingClientRect
-    }
-  } else {
-    domSelection.removeAllRanges()
-  }
-
-  setTimeout(() => {
-    // COMPAT: In Firefox, it's not enough to create a range, you also need
-    // to focus the contenteditable element too. (2016/11/16)
-    if (newDomRange && IS_FIREFOX) {
-      editorElement.focus()
-    }
-
-    textarea.isUpdatingSelection = false
-  })
-}
-
-/**
- * DOM selection change 时,把 DOM selection 同步给 slate
- * @param textarea textarea
- * @param editor editor
- */
-export function DOMSelectionToEditor(textarea: TextArea, editor: IDomEditor) {
-  const { isComposing, isUpdatingSelection, isDraggingInternally } = textarea
-  const config = editor.getConfig()
-
-  if (config.readOnly) return
-  if (isComposing) return
-  if (isUpdatingSelection) return
-  if (isDraggingInternally) return
-
-  const root = DomEditor.findDocumentOrShadowRoot(editor)
-  const { activeElement } = root
-  const el = DomEditor.toDOMNode(editor, editor)
-  const domSelection = root.getSelection()
-
-  if (activeElement === el) {
-    textarea.latestElement = activeElement
-    IS_FOCUSED.set(editor, true)
-  } else {
-    IS_FOCUSED.delete(editor)
-  }
-
-  if (!domSelection) {
-    return Transforms.deselect(editor)
-  }
-
-  const { anchorNode, focusNode } = domSelection
-
-  const anchorNodeSelectable =
-    hasEditableTarget(editor, anchorNode) || isTargetInsideNonReadonlyVoid(editor, anchorNode)
-  const focusNodeSelectable =
-    hasEditableTarget(editor, focusNode) || isTargetInsideNonReadonlyVoid(editor, focusNode)
-
-  if (anchorNodeSelectable && focusNodeSelectable) {
-    const range = DomEditor.toSlateRange(editor, domSelection, {
-      exactMatch: false,
-      suppressThrow: false,
-    })
-    Transforms.select(editor, range)
-  } else {
-    Transforms.deselect(editor)
-  }
-}
diff --git a/packages/core/src/text-area/update-view.ts b/packages/core/src/text-area/update-view.ts
deleted file mode 100644
index 33f706403..000000000
--- a/packages/core/src/text-area/update-view.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-/**
- * @description patch textarea view
- * @author wangfupeng
- */
-
-import { h, VNode } from 'snabbdom'
-import { IDomEditor } from '../editor/interface'
-import TextArea from './TextArea'
-import { genPatchFn, normalizeVnodeData } from '../utils/vdom'
-import $, { Dom7Array, getDefaultView, getElementById } from '../utils/dom'
-import { node2Vnode } from '../render/node2Vnode'
-import {
-  IS_FIRST_PATCH,
-  TEXTAREA_TO_PATCH_FN,
-  TEXTAREA_TO_VNODE,
-  EDITOR_TO_ELEMENT,
-  NODE_TO_ELEMENT,
-  ELEMENT_TO_NODE,
-  EDITOR_TO_WINDOW,
-} from '../utils/weak-maps'
-
-function genElemId(id: number) {
-  return `w-e-textarea-${id}`
-}
-
-/**
- * 生成编辑区域节点的 vnode
- * @param elemId elemId
- * @param readOnly readOnly
- */
-function genRootVnode(elemId: string, readOnly = false): VNode {
-  return h(`div#${elemId}`, {
-    props: {
-      contentEditable: readOnly ? false : true,
-    },
-  })
-  // 其他属性在 genRootElem 中定,这里不用重复写
-}
-
-/**
- * 生成编辑区域的 elem
- * @param elemId elemId
- * @param readOnly readOnly
- */
-function genRootElem(elemId: string, readOnly = false): Dom7Array {
-  const $elem = $(`<div
-        id="${elemId}"
-        data-slate-editor
-        data-slate-node="value"
-        suppressContentEditableWarning
-        role="textarea"
-        spellCheck="true"
-        autoCorrect="true"
-        autoCapitalize="true"
-    ></div>`)
-
-  // role="textarea" - 增强语义,div 语义太弱
-
-  return $elem
-}
-
-/**
- * 获取 editor.children 渲染 DOM
- * @param textarea textarea
- * @param editor editor
- */
-function updateView(textarea: TextArea, editor: IDomEditor) {
-  const $scroll = textarea.$scroll
-  const elemId = genElemId(textarea.id)
-  const { readOnly, autoFocus } = editor.getConfig()
-
-  // 生成 newVnode
-  const newVnode = genRootVnode(elemId, readOnly)
-  const content = editor.children || []
-  newVnode.children = content.map((node, i) => {
-    let vnode = node2Vnode(node, i, editor, editor)
-    normalizeVnodeData(vnode) // 整理 vnode.data 以符合 snabbdom 的要求
-    return vnode
-  })
-
-  let textareaElem
-  let isFirstPatch = IS_FIRST_PATCH.get(textarea)
-  if (isFirstPatch == null) isFirstPatch = true // 尚未赋值,也是第一次
-  if (isFirstPatch) {
-    // 第一次 patch ,先生成 elem
-    const $textArea = genRootElem(elemId, readOnly)
-    $scroll.append($textArea)
-    textarea.$textArea = $textArea // 存储下编辑区域的 DOM 节点
-    textareaElem = $textArea[0]
-
-    // 再生成 patch 函数,并执行
-    const patchFn = genPatchFn()
-    patchFn(textareaElem, newVnode)
-
-    // 存储相关信息
-    IS_FIRST_PATCH.set(textarea, false) // 不再是第一次 patch
-    TEXTAREA_TO_PATCH_FN.set(textarea, patchFn) // 存储 patch 函数
-  } else {
-    // 不是第一次 patch
-    const curVnode = TEXTAREA_TO_VNODE.get(textarea)
-    const patchFn = TEXTAREA_TO_PATCH_FN.get(textarea)
-    if (curVnode == null || patchFn == null) return
-    textareaElem = curVnode.elm
-
-    patchFn(curVnode, newVnode)
-  }
-
-  if (textareaElem == null) {
-    textareaElem = getElementById(elemId)
-
-    // 通过 getElementById 获取的有可能是 null (销毁、重建时,可能会发生这种情况)
-    if (textareaElem == null) return
-  }
-
-  // focus
-  let isFocused
-  if (isFirstPatch) {
-    // 初次渲染
-    isFocused = autoFocus
-  } else {
-    // 非初次渲染
-    isFocused = editor.isFocused()
-  }
-  if (isFocused) {
-    textareaElem.focus({
-      preventScroll: true, // 必须添加 preventScroll 选项,否则弹窗或者编辑器失焦会导致编辑区域自动滚动到顶部
-    })
-  }
-
-  // 存储相关信息
-  if (isFirstPatch) {
-    const window = getDefaultView(textareaElem)
-    window && EDITOR_TO_WINDOW.set(editor, window)
-  }
-
-  EDITOR_TO_ELEMENT.set(editor, textareaElem) // 存储 editor -> elem 对应关系
-  NODE_TO_ELEMENT.set(editor, textareaElem)
-  ELEMENT_TO_NODE.set(textareaElem, editor)
-  TEXTAREA_TO_VNODE.set(textarea, newVnode) // 存储 vnode
-}
-
-export default updateView
diff --git a/packages/core/src/to-html/README.md b/packages/core/src/to-html/README.md
deleted file mode 100644
index a86f48407..000000000
--- a/packages/core/src/to-html/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# to html
-
-把 content 为 html
diff --git a/packages/core/src/to-html/elem2html.ts b/packages/core/src/to-html/elem2html.ts
deleted file mode 100644
index 91b1320ad..000000000
--- a/packages/core/src/to-html/elem2html.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * @description elem -> html
- * @author wangfupeng
- */
-
-import { Editor, Element } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import node2html from './node2html'
-import { ElemToHtmlFnType, ELEM_TO_HTML_CONF, STYLE_TO_HTML_FN_LIST } from './index'
-
-/**
- * 默认的 toHtml 函数
- * @param elemNode elem node
- * @param childrenHtml children html
- * @param editor editor
- */
-function defaultParser(elemNode: Element, childrenHtml: string, editor: IDomEditor) {
-  const isInline = editor.isInline(elemNode)
-  const tag = isInline ? 'span' : 'div'
-  return `<${tag}>${childrenHtml}</${tag}>`
-}
-
-/**
- * 根据 type 获取 toHtml 函数
- * @param type node.type
- */
-function getParser(type: string): ElemToHtmlFnType {
-  const fn = ELEM_TO_HTML_CONF[type]
-  return fn || defaultParser
-}
-
-function elemToHtml(elemNode: Element, editor: IDomEditor): string {
-  const { type = '', children = [] } = elemNode
-  const isVoid = Editor.isVoid(editor, elemNode)
-
-  // 计算 children html
-  let childrenHtml = ''
-  if (!isVoid) {
-    // 非 void node
-    childrenHtml = children.map(child => node2html(child, editor)).join('')
-  }
-
-  // 生成 html
-  const toHtmlFn = getParser(type)
-  const res = toHtmlFn(elemNode, childrenHtml, editor)
-
-  let elemHtml = ''
-  if (typeof res === 'string') elemHtml = res
-  else elemHtml = res.html || ''
-
-  // 添加样式(如 text-align line-height 等)
-  if (!isVoid) {
-    STYLE_TO_HTML_FN_LIST.forEach(fn => (elemHtml = fn(elemNode, elemHtml)))
-  }
-
-  // 直接返回 html 字符串
-  if (typeof res === 'string') return elemHtml
-
-  // 解析 prefix suffix (如 list-item)
-  const { prefix = '', suffix = '' } = res
-  if (prefix) elemHtml = prefix + elemHtml
-  if (suffix) elemHtml = elemHtml + suffix
-  return elemHtml
-}
-
-export default elemToHtml
diff --git a/packages/core/src/to-html/index.ts b/packages/core/src/to-html/index.ts
deleted file mode 100644
index f3cda75f9..000000000
--- a/packages/core/src/to-html/index.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * @description to-html entry
- * @author wangfupeng
- */
-
-import { Element as SlateElement, Descendant } from 'slate'
-import { IDomEditor } from '../editor/interface'
-
-// ------------------------------------ style to html ------------------------------------
-
-export type styleToHtmlFnType = (node: Descendant, elemHtml: string) => string
-
-export const STYLE_TO_HTML_FN_LIST: styleToHtmlFnType[] = []
-
-/**
- * 注册 toHtml 处理文本样式的函数
- * @param fn 处理 toHtml 文本样式的函数
- */
-export function registerStyleToHtmlHandler(fn: styleToHtmlFnType) {
-  STYLE_TO_HTML_FN_LIST.push(fn)
-}
-
-// ------------------------------------ elem node to html ------------------------------------
-
-interface IElemToHtmlRes {
-  html: string
-  prefix?: string
-  suffix?: string
-}
-
-export type ElemToHtmlFnType = (
-  elemNode: SlateElement,
-  childrenHtml: string,
-  editor?: IDomEditor
-) => string | IElemToHtmlRes
-
-// 注册 element->html 配置
-export const ELEM_TO_HTML_CONF: {
-  [key: string]: ElemToHtmlFnType // key 要和 node.type 对应 !!!
-} = {}
-
-export interface IElemToHtmlConf {
-  type: string
-  elemToHtml: ElemToHtmlFnType
-}
-
-/**
- * 注册 elem to html 函数
- * @param conf { type, elemToHtml } ,type 即 node.type
- */
-export function registerElemToHtmlConf(conf: IElemToHtmlConf) {
-  const { type, elemToHtml } = conf
-  const key = type || ''
-
-  // key 如果重复了,就后者覆盖前者
-  ELEM_TO_HTML_CONF[key] = elemToHtml
-}
diff --git a/packages/core/src/to-html/node2html.ts b/packages/core/src/to-html/node2html.ts
deleted file mode 100644
index 54ecdcd3a..000000000
--- a/packages/core/src/to-html/node2html.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @description node -> html
- * @author wangfupeng
- */
-
-import { Element, Descendant } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import elemToHtml from './elem2html'
-import textToHtml from './text2html'
-
-function node2html(node: Descendant, editor: IDomEditor): string {
-  if (Element.isElement(node)) {
-    // elem node
-    return elemToHtml(node, editor)
-  } else {
-    // text node
-    return textToHtml(node, editor)
-  }
-}
-
-export default node2html
diff --git a/packages/core/src/to-html/text2html.ts b/packages/core/src/to-html/text2html.ts
deleted file mode 100644
index 40c94fb27..000000000
--- a/packages/core/src/to-html/text2html.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * @description text -> html
- * @author wangfupeng
- */
-
-import { Text } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import { DomEditor } from '../editor/dom-editor'
-import { STYLE_TO_HTML_FN_LIST } from './index'
-import { replaceHtmlSpecialSymbols } from '../utils/util'
-
-function textToHtml(textNode: Text, editor: IDomEditor): string {
-  const { text } = textNode
-  if (text == null) throw new Error(`Current node is not slate Text ${JSON.stringify(textNode)}`)
-  let textHtml = text
-
-  // 替换 html 特殊字符
-  textHtml = replaceHtmlSpecialSymbols(textHtml)
-
-  // 替换 \n 为 <br> (一定要在替换特殊字符之后)
-  const parents = DomEditor.getParentsNodes(editor, textNode)
-  const hasPre = parents.some(p => DomEditor.getNodeType(p) === 'pre') // 上级节点中,是否存在 <pre>
-  // 在 <pre> 标签不替换,其他都替换
-  if (!hasPre) {
-    textHtml = textHtml.replace(/\r\n|\r|\n/g, '<br>')
-  }
-
-  // 在 <pre> 内部,&nbsp; 替换为空格
-  if (hasPre) {
-    textHtml = textHtml.replace(/&nbsp;/g, ' ')
-  }
-
-  // 处理空字符串
-  if (textHtml === '') {
-    const parentNode = DomEditor.getParentNode(null, textNode)
-    if (parentNode && parentNode.children.length === 0) {
-      // textNode 是唯一的子节点,则改为 <br>
-      textHtml = '<br>'
-    } else {
-      // 其他情况的 空字符串 ,直接返回
-      return textHtml
-    }
-  }
-
-  // 增加文本样式,如 color bgColor
-  STYLE_TO_HTML_FN_LIST.forEach(fn => (textHtml = fn(textNode, textHtml)))
-
-  return textHtml
-}
-
-export default textToHtml
diff --git a/packages/core/src/upload/createUploader.ts b/packages/core/src/upload/createUploader.ts
deleted file mode 100644
index 8b03ad585..000000000
--- a/packages/core/src/upload/createUploader.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * @description gen uploader
- * @author wangfupeng
- */
-
-import Uppy from '@uppy/core'
-import XHRUpload from '@uppy/xhr-upload'
-import { IUploadConfig } from './interface'
-import { addQueryToUrl } from '../utils/util'
-
-function createUploader(config: IUploadConfig): Uppy {
-  // 获取配置
-  const {
-    server = '',
-    fieldName = '',
-    maxFileSize = 10 * 1024 * 1024, // 10M
-    maxNumberOfFiles = 100, // 最多多少个文件
-    meta = {},
-    metaWithUrl = false,
-    headers = {},
-    withCredentials = false,
-    timeout = 10 * 1000, // 10s
-    onBeforeUpload = files => files,
-    onSuccess = (file, res) => {
-      /* on success */
-    },
-    onError = (file, err, res?) => {
-      console.error(`${file.name} upload error`, err, res)
-    },
-    onProgress = progress => {
-      /* on progress */
-    },
-  } = config
-
-  // 判断配置项
-  if (!server) {
-    throw new Error('Cannot get upload server address\n没有配置上传地址')
-  }
-  if (!fieldName) {
-    throw new Error('Cannot get fieldName\n没有配置 fieldName')
-  }
-
-  // 是否要追加 url 参数
-  let url = server
-  if (metaWithUrl) {
-    url = addQueryToUrl(url, meta)
-  }
-
-  // 生成 uppy 实例,参考文档 https://uppy.io/docs/uppy/
-  const uppy = new Uppy({
-    onBeforeUpload,
-    restrictions: {
-      maxFileSize,
-      maxNumberOfFiles,
-    },
-    meta, // 自定义添加到 formData 中的参数
-  }).use(XHRUpload, {
-    endpoint: url, // 服务端 url
-    headers, // 自定义 headers
-    formData: true,
-    fieldName,
-    bundle: true,
-    withCredentials,
-    timeout,
-  })
-
-  // 各个 callback
-  uppy.on('upload-success', (file, response) => {
-    const { body = {} } = response
-    try {
-      // 有用户传入的第三方代码,得用 try catch 包裹
-      onSuccess(file, body)
-    } catch (err) {
-      console.error('wangEditor upload file - onSuccess error', err)
-    }
-    uppy.removeFile(file.id) // 清空文件
-  })
-
-  uppy.on('progress', progress => {
-    // progress 值范围: 0 - 100
-    if (progress < 1) return
-    onProgress(progress)
-  })
-
-  // uppy.on('error', error => {
-  //   console.error('wangEditor file upload error', error.stack)
-  // })
-
-  uppy.on('upload-error', (file, error, response) => {
-    try {
-      // 有用户传入的第三方代码,得用 try catch 包裹
-      onError(file, error, response)
-    } catch (err) {
-      console.error('wangEditor upload file - onError error', err)
-    }
-    uppy.removeFile(file.id) // 清空文件
-  })
-
-  uppy.on('restriction-failed', (file, error) => {
-    try {
-      // 有用户传入的第三方代码,得用 try catch 包裹
-      onError(file, error)
-    } catch (err) {
-      console.error('wangEditor upload file - onError error', err)
-    }
-    uppy.removeFile(file.id) // 清空文件
-  })
-
-  // 返回实例
-  return uppy
-}
-
-export default createUploader
diff --git a/packages/core/src/upload/index.ts b/packages/core/src/upload/index.ts
deleted file mode 100644
index 9361bda77..000000000
--- a/packages/core/src/upload/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @description upload entry
- * @author wangfupeng
- */
-
-import createUploader from './createUploader'
-import { IUploadConfig } from './interface'
-
-export { createUploader, IUploadConfig }
-
-// TODO upload 能力,写到文档中,二次开发使用
diff --git a/packages/core/src/upload/interface.ts b/packages/core/src/upload/interface.ts
deleted file mode 100644
index 1895fddaf..000000000
--- a/packages/core/src/upload/interface.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @description upload interface
- * @author wangfupeng
- */
-
-import { UppyFile } from '@uppy/core'
-
-type FilesType = { [key: string]: UppyFile<{}, {}> }
-
-/**
- * 配置参考 https://uppy.io/docs/uppy/
- */
-export interface IUploadConfig {
-  server: string
-  fieldName?: string
-  maxFileSize?: number
-  maxNumberOfFiles?: number
-  meta?: Record<string, unknown>
-  metaWithUrl: boolean
-  headers?:
-    | Headers
-    | ((file: UppyFile<Record<string, unknown>, Record<string, unknown>>) => Headers)
-    | undefined
-  withCredentials?: boolean
-  timeout?: number
-  onBeforeUpload?: (files: FilesType) => boolean | FilesType
-  onSuccess: (file: UppyFile<{}, {}>, response: any) => void
-  onProgress?: (progress: number) => void
-  onFailed: (file: UppyFile<{}, {}>, response: any) => void
-  onError: (file: UppyFile<{}, {}>, error: any, res: any) => void
-}
diff --git a/packages/core/src/utils/dom.ts b/packages/core/src/utils/dom.ts
deleted file mode 100644
index 0402d65b3..000000000
--- a/packages/core/src/utils/dom.ts
+++ /dev/null
@@ -1,381 +0,0 @@
-/**
- * @description DOM 操作 part1 - DOM7 文档 https://framework7.io/docs/dom7.html
- * @author wangfupeng
- */
-
-import { htmlVoidElements } from 'html-void-elements'
-import $, {
-  css,
-  append,
-  addClass,
-  removeClass,
-  hasClass,
-  on,
-  focus,
-  attr,
-  hide,
-  show,
-  // scrollTop,
-  // scrollLeft,
-  offset,
-  width,
-  height,
-  parent,
-  parents,
-  is,
-  dataset,
-  val,
-  text,
-  removeAttr,
-  children,
-  html,
-  remove,
-  find,
-  each,
-  empty,
-  Dom7Array,
-} from 'dom7'
-export { Dom7Array } from 'dom7'
-
-if (css) $.fn.css = css
-if (append) $.fn.append = append
-if (addClass) $.fn.addClass = addClass
-if (removeClass) $.fn.removeClass = removeClass
-if (hasClass) $.fn.hasClass = hasClass
-if (on) $.fn.on = on
-if (focus) $.fn.focus = focus
-if (attr) $.fn.attr = attr
-if (removeAttr) $.fn.removeAttr = removeAttr
-if (hide) $.fn.hide = hide
-if (show) $.fn.show = show
-// if (scrollTop) $.fn.scrollTop = scrollTop
-// if (scrollLeft) $.fn.scrollLeft = scrollLeft
-if (offset) $.fn.offset = offset
-if (width) $.fn.width = width
-if (height) $.fn.height = height
-if (parent) $.fn.parent = parent
-if (parents) $.fn.parents = parents
-if (is) $.fn.is = is
-if (dataset) $.fn.dataset = dataset
-if (val) $.fn.val = val
-if (text) $.fn.text = text
-if (html) $.fn.html = html
-if (children) $.fn.children = children
-if (remove) $.fn.remove = remove
-if (find) $.fn.find = find
-if (each) $.fn.each = each
-if (empty) $.fn.empty = empty
-
-export default $
-
-// ------------------------------- 分割线,以下内容参考 slate-react dom.ts -------------------------------
-
-// COMPAT: This is required to prevent TypeScript aliases from doing some very
-// weird things for Slate's types with the same name as globals. (2019/11/27)
-// https://github.com/microsoft/TypeScript/issues/35002
-import DOMNode = globalThis.Node
-import DOMComment = globalThis.Comment
-import DOMElement = globalThis.Element
-import DOMText = globalThis.Text
-import DOMRange = globalThis.Range
-import DOMSelection = globalThis.Selection
-import DOMStaticRange = globalThis.StaticRange
-export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange }
-
-export type DOMPoint = [Node, number]
-
-/**
- * Returns the host window of a DOM node
- */
-export const getDefaultView = (value: any): Window | null => {
-  return (value && value.ownerDocument && value.ownerDocument.defaultView) || null
-}
-
-/**
- * Check if a DOM node is a comment node.
- */
-export const isDOMComment = (value: any): value is DOMComment => {
-  return isDOMNode(value) && value.nodeType === 8
-}
-
-/**
- * Check if a DOM node is an element node.
- */
-export const isDOMElement = (value: any): value is DOMElement => {
-  return isDOMNode(value) && value.nodeType === 1
-}
-
-/**
- * Check if a value is a DOM node.
- */
-export const isDOMNode = (value: any): value is DOMNode => {
-  const window = getDefaultView(value)
-  return (
-    !!window &&
-    // @ts-ignore
-    value instanceof window.Node
-  )
-}
-
-/**
- * Check if a value is a DOM selection.
- */
-export const isDOMSelection = (value: any): value is DOMSelection => {
-  const window = value && value.anchorNode && getDefaultView(value.anchorNode)
-  return !!window && value instanceof window.Selection
-}
-
-/**
- * Check if a DOM node is an element node.
- */
-export const isDOMText = (value: any): value is DOMText => {
-  return isDOMNode(value) && value.nodeType === 3
-}
-
-/**
- * Checks whether a paste event is a plaintext-only event.
- */
-export const isPlainTextOnlyPaste = (event: ClipboardEvent) => {
-  return (
-    event.clipboardData &&
-    event.clipboardData.getData('text/plain') !== '' &&
-    event.clipboardData.types.length === 1
-  )
-}
-
-/**
- * Normalize a DOM point so that it always refers to a text node.
- */
-export const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => {
-  let [node, offset] = domPoint
-
-  // If it's an element node, its offset refers to the index of its children
-  // including comment nodes, so try to find the right text child node.
-  if (isDOMElement(node) && node.childNodes.length) {
-    let isLast = offset === node.childNodes.length
-    let index = isLast ? offset - 1 : offset
-    ;[node, index] = getEditableChildAndIndex(node, index, isLast ? 'backward' : 'forward')
-
-    // If the editable child found is in front of input offset, we instead seek to its end
-    // 如果编辑区域的内容被发现在输入光标位置前面,也就是光标位置不正常,则修正光标的位置到结尾
-    isLast = index < offset
-
-    // If the node has children, traverse until we have a leaf node. Leaf nodes
-    // can be either text nodes, or other void DOM nodes.
-    while (isDOMElement(node) && node.childNodes.length) {
-      const i = isLast ? node.childNodes.length - 1 : 0
-      node = getEditableChild(node, i, isLast ? 'backward' : 'forward')
-    }
-
-    // Determine the new offset inside the text node.
-    offset = isLast && node.textContent != null ? node.textContent.length : 0
-  }
-
-  // Return the node and offset.
-  return [node, offset]
-}
-
-/**
- * Determines wether the active element is nested within a shadowRoot
- */
-export const hasShadowRoot = () => {
-  return !!(window.document.activeElement && window.document.activeElement.shadowRoot)
-}
-
-/**
- * Get the element with the specified id
- */
-export const getElementById = (id: string): null | HTMLElement => {
-  return (
-    window.document.getElementById(id) ??
-    (window.document.activeElement?.shadowRoot?.getElementById(id) || null)
-  )
-}
-
-/**
- * Get the nearest editable child and index at `index` in a `parent`, preferring `direction`.
- */
-export const getEditableChildAndIndex = (
-  parent: DOMElement,
-  index: number,
-  direction: 'forward' | 'backward'
-): [DOMNode, number] => {
-  const { childNodes } = parent
-  let child = childNodes[index]
-  let i = index
-  let triedForward = false
-  let triedBackward = false
-
-  // While the child is a comment node, or an element node with no children,
-  // keep iterating to find a sibling non-void, non-comment node.
-  while (
-    isDOMComment(child) ||
-    (isDOMElement(child) && child.childNodes.length === 0) ||
-    (isDOMElement(child) && child.getAttribute('contenteditable') === 'false')
-  ) {
-    if (triedForward && triedBackward) {
-      break
-    }
-
-    if (i >= childNodes.length) {
-      triedForward = true
-      i = index - 1
-      direction = 'backward'
-      continue
-    }
-
-    if (i < 0) {
-      triedBackward = true
-      i = index + 1
-      direction = 'forward'
-      continue
-    }
-
-    child = childNodes[i]
-    index = i
-    i += direction === 'forward' ? 1 : -1
-  }
-
-  return [child, index]
-}
-
-/**
- * Get the nearest editable child at `index` in a `parent`, preferring
- * `direction`.
- */
-
-export const getEditableChild = (
-  parent: DOMElement,
-  index: number,
-  direction: 'forward' | 'backward'
-): DOMNode => {
-  const [child] = getEditableChildAndIndex(parent, index, direction)
-  return child
-}
-
-/**
- * Get a plaintext representation of the content of a node, accounting for block
- * elements which get a newline appended.
- *
- * The domNode must be attached to the DOM.
- */
-export const getPlainText = (domNode: DOMNode) => {
-  let text = ''
-
-  if (isDOMText(domNode) && domNode.nodeValue) {
-    return domNode.nodeValue
-  }
-
-  if (isDOMElement(domNode)) {
-    for (const childNode of Array.from(domNode.childNodes)) {
-      text += getPlainText(childNode)
-    }
-
-    const display = getComputedStyle(domNode).getPropertyValue('display')
-
-    if (
-      display === 'block' ||
-      display === 'list' ||
-      display === 'table-row' ||
-      domNode.tagName === 'BR'
-    ) {
-      text += '\n'
-    }
-  }
-
-  return text
-}
-
-/**
- * 在下级节点中找到第一个 void elem
- * @param elem elem
- */
-export function getFirstVoidChild(elem: DOMElement): DOMElement | null {
-  // 深度优先遍历
-  const stack: Array<DOMElement> = []
-  stack.push(elem)
-
-  let num = 0
-
-  // 开始遍历
-  while (stack.length > 0) {
-    const curElem = stack.pop()
-    if (curElem == null) break
-
-    num++
-    if (num > 10000) break
-
-    const { nodeName, nodeType } = curElem
-    if (nodeType === 1) {
-      const name = nodeName.toLowerCase()
-      if (
-        htmlVoidElements.includes(name) ||
-        // 补充一些
-        name === 'iframe' ||
-        name === 'video'
-      ) {
-        return curElem // 得到 void elem 并返回
-      }
-
-      // 继续遍历子节点
-      const children = curElem.children || []
-      const length = children.length
-      if (length) {
-        for (let i = length - 1; i >= 0; i--) {
-          // 注意,需要**逆序**追加自节点
-          stack.push(children[i])
-        }
-      }
-    }
-  }
-
-  // 未找到结果,返回 null
-  return null
-}
-
-/**
- * 遍历一个 elem 内所有的 text node ,执行函数
- * @param elem elem
- * @param handler handler
- */
-export function walkTextNodes(
-  elem: DOMElement,
-  handler: (textNode: DOMNode, parent: DOMElement) => void
-) {
-  // void elem 内部的 text 不处理
-  if (elem instanceof HTMLElement && elem.dataset.slateVoid === 'true') return
-
-  for (let nodes = elem.childNodes, i = nodes.length; i--; ) {
-    const node = nodes[i]
-    const nodeType = node.nodeType
-    if (nodeType == 3) {
-      // 匹配到 text node ,执行函数
-      handler(node, elem)
-    } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
-      // 继续遍历子节点
-      walkTextNodes(node as DOMElement, handler)
-    }
-  }
-}
-
-export enum NodeType {
-  ELEMENT_NODE = 1,
-  TEXT_NODE = 3,
-  CDATA_SECTION_NODE = 4,
-  PROCESSING_INSTRUCTION_NODE = 7,
-  COMMENT_NODE = 8,
-  DOCUMENT_NODE = 9,
-  DOCUMENT_TYPE_NODE = 10,
-  DOCUMENT_FRAGMENT_NODE = 11,
-}
-
-/**
- * 获取 tagName lower-case
- * @param $elem $elem
- */
-export function getTagName($elem: Dom7Array): string {
-  if ($elem.length === 0) return ''
-  const elem = $elem[0]
-  if (elem.nodeType !== NodeType.ELEMENT_NODE) return ''
-  return elem.tagName.toLowerCase()
-}
diff --git a/packages/core/src/utils/hotkeys.ts b/packages/core/src/utils/hotkeys.ts
deleted file mode 100644
index bffffe96a..000000000
--- a/packages/core/src/utils/hotkeys.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-/**
- * @description 快捷键
- * @author wangfupeng
- */
-
-import { isKeyHotkey } from 'is-hotkey'
-import { IS_APPLE } from './ua'
-
-interface KEYS {
-  [key: string]: string | string[]
-}
-
-/**
- * Hotkey mappings for each platform.
- */
-const HOTKEYS: KEYS = {
-  bold: 'mod+b',
-  compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
-  moveBackward: 'left',
-  moveForward: 'right',
-  moveWordBackward: 'ctrl+left',
-  moveWordForward: 'ctrl+right',
-  deleteBackward: 'shift?+backspace',
-  deleteForward: 'shift?+delete',
-  extendBackward: 'shift+left',
-  extendForward: 'shift+right',
-  italic: 'mod+i',
-  splitBlock: 'shift?+enter',
-  undo: 'mod+z',
-  tab: 'tab',
-  selectAll: 'mod+a',
-}
-
-const APPLE_HOTKEYS: KEYS = {
-  moveLineBackward: 'opt+up',
-  moveLineForward: 'opt+down',
-  moveWordBackward: 'opt+left',
-  moveWordForward: 'opt+right',
-  deleteBackward: ['ctrl+backspace', 'ctrl+h'],
-  deleteForward: ['ctrl+delete', 'ctrl+d'],
-  deleteLineBackward: 'cmd+shift?+backspace',
-  deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
-  deleteWordBackward: 'opt+shift?+backspace',
-  deleteWordForward: 'opt+shift?+delete',
-  extendLineBackward: 'opt+shift+up',
-  extendLineForward: 'opt+shift+down',
-  redo: 'cmd+shift+z',
-  transposeCharacter: 'ctrl+t',
-}
-
-const WINDOWS_HOTKEYS: KEYS = {
-  deleteWordBackward: 'ctrl+shift?+backspace',
-  deleteWordForward: 'ctrl+shift?+delete',
-  redo: ['ctrl+y', 'ctrl+shift+z'],
-}
-
-/**
- * Create a platform-aware hotkey checker.
- */
-const create = (key: string) => {
-  const generic = HOTKEYS[key]
-  const apple = APPLE_HOTKEYS[key]
-  const windows = WINDOWS_HOTKEYS[key]
-  const isGeneric = generic && isKeyHotkey(generic)
-  const isApple = apple && isKeyHotkey(apple)
-  const isWindows = windows && isKeyHotkey(windows)
-
-  return (event: KeyboardEvent) => {
-    if (isGeneric && isGeneric(event)) return true
-    if (IS_APPLE && isApple && isApple(event)) return true
-    if (!IS_APPLE && isWindows && isWindows(event)) return true
-    return false
-  }
-}
-
-/**
- * Hotkeys.
- */
-export default {
-  isBold: create('bold'),
-  isCompose: create('compose'),
-  isMoveBackward: create('moveBackward'),
-  isMoveForward: create('moveForward'),
-  isDeleteBackward: create('deleteBackward'),
-  isDeleteForward: create('deleteForward'),
-  isDeleteLineBackward: create('deleteLineBackward'),
-  isDeleteLineForward: create('deleteLineForward'),
-  isDeleteWordBackward: create('deleteWordBackward'),
-  isDeleteWordForward: create('deleteWordForward'),
-  isExtendBackward: create('extendBackward'),
-  isExtendForward: create('extendForward'),
-  isExtendLineBackward: create('extendLineBackward'),
-  isExtendLineForward: create('extendLineForward'),
-  isItalic: create('italic'),
-  isMoveLineBackward: create('moveLineBackward'),
-  isMoveLineForward: create('moveLineForward'),
-  isMoveWordBackward: create('moveWordBackward'),
-  isMoveWordForward: create('moveWordForward'),
-  isRedo: create('redo'),
-  isSplitBlock: create('splitBlock'),
-  isTransposeCharacter: create('transposeCharacter'),
-  isUndo: create('undo'),
-  isTab: create('tab'),
-  isSelectAll: create('selectAll'),
-}
diff --git a/packages/core/src/utils/key.ts b/packages/core/src/utils/key.ts
deleted file mode 100644
index 29578eb6b..000000000
--- a/packages/core/src/utils/key.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * An auto-incrementing identifier for keys.
- */
-
-let n = 0
-
-/**
- * A class that keeps track of a key string. We use a full class here because we
- * want to be able to use them as keys in `WeakMap` objects.
- */
-export class Key {
-  id: string
-
-  constructor() {
-    this.id = `${n++}`
-  }
-}
diff --git a/packages/core/src/utils/line.ts b/packages/core/src/utils/line.ts
deleted file mode 100644
index d6c404bc9..000000000
--- a/packages/core/src/utils/line.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * @description Utilities for single-line deletion
- */
-
-import { Range, Editor } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import { DomEditor } from '../editor/dom-editor'
-
-const doRectsIntersect = (rect: DOMRect, compareRect: DOMRect) => {
-  const middle = (compareRect.top + compareRect.bottom) / 2
-  return rect.top <= middle && rect.bottom >= middle
-}
-
-const areRangesSameLine = (editor: IDomEditor, range1: Range, range2: Range) => {
-  const rect1 = DomEditor.toDOMRange(editor, range1).getBoundingClientRect()
-  const rect2 = DomEditor.toDOMRange(editor, range2).getBoundingClientRect()
-  return doRectsIntersect(rect1, rect2) && doRectsIntersect(rect2, rect1)
-}
-
-/**
- * A helper utility that returns the end portion of a `Range`
- * which is located on a single line.
- *
- * @param {Editor} editor The editor object to compare against
- * @param {Range} parentRange The parent range to compare against
- * @returns {Range} A valid portion of the parentRange which is one a single line
- */
-export const findCurrentLineRange = (editor: IDomEditor, parentRange: Range): Range => {
-  const parentRangeBoundary = Editor.range(editor, Range.end(parentRange))
-  const positions = Array.from(Editor.positions(editor, { at: parentRange }))
-
-  let left = 0
-  let right = positions.length
-  let middle = Math.floor(right / 2)
-
-  if (areRangesSameLine(editor, Editor.range(editor, positions[left]), parentRangeBoundary)) {
-    return Editor.range(editor, positions[left], parentRangeBoundary)
-  }
-
-  if (positions.length < 2) {
-    return Editor.range(editor, positions[positions.length - 1], parentRangeBoundary)
-  }
-
-  while (middle !== positions.length && middle !== left) {
-    if (areRangesSameLine(editor, Editor.range(editor, positions[middle]), parentRangeBoundary)) {
-      right = middle
-    } else {
-      left = middle
-    }
-
-    middle = Math.floor((left + right) / 2)
-  }
-
-  return Editor.range(editor, positions[right], parentRangeBoundary)
-}
diff --git a/packages/core/src/utils/ua.ts b/packages/core/src/utils/ua.ts
deleted file mode 100644
index 80e46f933..000000000
--- a/packages/core/src/utils/ua.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @description 通过 UA 判断浏览器
- * @author wangfupeng
- */
-
-export const IS_IOS =
-  typeof globalThis.navigator !== 'undefined' &&
-  typeof globalThis.window !== 'undefined' &&
-  /iPad|iPhone|iPod/.test(navigator.userAgent) &&
-  !globalThis.window.MSStream
-
-export const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent)
-
-export const IS_FIREFOX =
-  typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent)
-
-export const IS_FIREFOX_LEGACY =
-  typeof navigator !== 'undefined' &&
-  /^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(navigator.userAgent)
-
-export const IS_SAFARI =
-  typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent) // eslint-disable-line
-
-// "modern" Edge was released at 79.x
-export const IS_EDGE_LEGACY =
-  typeof navigator !== 'undefined' &&
-  /Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent)
-
-// Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
-export const IS_CHROME_LEGACY =
-  typeof navigator !== 'undefined' &&
-  /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent)
-
-export const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent)
-
-// qq browser
-export const IS_QQBROWSER =
-  typeof navigator !== 'undefined' && /.*QQBrowser/.test(navigator.userAgent)
-
-// @ts-ignore 判断浏览器是否支持 beforeinput 事件 https://www.caniuse.com/?search=beforeinput
-// COMPAT: Firefox/Edge Legacy don't support the `beforeinput` event
-// Chrome Legacy doesn't support `beforeinput` correctly
-export const HAS_BEFORE_INPUT_SUPPORT =
-  !IS_CHROME_LEGACY &&
-  !IS_EDGE_LEGACY &&
-  // globalThis is undefined in older browsers
-  typeof globalThis !== 'undefined' &&
-  globalThis.InputEvent &&
-  // @ts-ignore The `getTargetRanges` property isn't recognized.
-  typeof globalThis.InputEvent.prototype.getTargetRanges === 'function'
diff --git a/packages/core/src/utils/util.ts b/packages/core/src/utils/util.ts
deleted file mode 100644
index 7a6827c35..000000000
--- a/packages/core/src/utils/util.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/**
- * @description 工具函数
- * @author wangfupeng
- */
-
-import forEach from 'lodash.foreach'
-import { nanoid } from 'nanoid'
-
-type PromiseCallback = (value: void) => void | PromiseLike<void>
-
-/**
- * 获取随机数字符串
- * @param prefix 前缀
- * @returns 随机数字符串
- */
-export function genRandomStr(prefix: string = 'r'): string {
-  return `${prefix}-${nanoid()}`
-}
-
-export function promiseResolveThen(fn: Function) {
-  Promise.resolve().then(fn as PromiseCallback)
-}
-
-/**
- * 追加 url query 参数
- * @param url url
- * @param data data
- */
-export function addQueryToUrl(url: string, data: object): string {
-  let [urlWithoutHash, hash] = url.split('#')
-
-  // 拼接 query string
-  const queryArr: string[] = []
-  forEach(data, (val, key) => {
-    queryArr.push(`${key}=${val}`)
-  })
-  const queryStr = queryArr.join('&')
-
-  // 拼接到 url
-  if (urlWithoutHash.indexOf('?') > 0) {
-    // 已有 query
-    urlWithoutHash = `${urlWithoutHash}&${queryStr}`
-  } else {
-    // 没有 query
-    urlWithoutHash = `${urlWithoutHash}?${queryStr}`
-  }
-
-  // 返回拼接好的 url
-  if (hash) {
-    return `${urlWithoutHash}#${hash}`
-  } else {
-    return urlWithoutHash
-  }
-}
-
-/**
- * 替换 html 特殊字符,如 > 替换为 &gt;
- * @param str html str
- */
-export function replaceHtmlSpecialSymbols(str: string) {
-  return (
-    str
-      /**
-       * 遇到两个空格时才替换,一个空格不替换
-       * 两个英文单词之间有一个空格,就不用替换,否则无法默认换行 issue #4403
-       */
-      .replace(/ {2}/g, ' &nbsp;')
-
-      .replace(/</g, '&lt;')
-      .replace(/>/g, '&gt;')
-      .replace(/®/g, '&reg;')
-      .replace(/©/g, '&copy;')
-      .replace(/™/g, '&trade;')
-  )
-}
-
-/**
- *【反转】替换 html 特殊字符,如 &gt; 替换为 >
- * @param str html str
- */
-export function deReplaceHtmlSpecialSymbols(str: string) {
-  return str
-    .replace(/&nbsp;/g, ' ')
-    .replace(/&lt;/g, '<')
-    .replace(/&gt;/g, '>')
-    .replace(/&reg;/g, '®')
-    .replace(/&copy;/g, '©')
-    .replace(/&trade;/g, '™')
-    .replace(/&quot;/g, '"')
-}
diff --git a/packages/core/src/utils/vdom.ts b/packages/core/src/utils/vdom.ts
deleted file mode 100644
index c3bb8c56f..000000000
--- a/packages/core/src/utils/vdom.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * @description vdom 相关方法
- * @author wangfupeng
- */
-
-import camelCase from 'lodash.camelcase'
-import {
-  VNode,
-  init,
-  classModule,
-  propsModule,
-  styleModule,
-  datasetModule,
-  VNodeStyle,
-  Props,
-  Dataset,
-  eventListenersModule,
-  attributesModule,
-} from 'snabbdom'
-
-export type PatchFn = (oldVnode: VNode | Element, vnode: VNode) => VNode
-
-/**
- * 创建 snabbdom patch
- * @returns snabbdom patch 函数
- */
-export function genPatchFn(): PatchFn {
-  const patch = init([
-    // Init patch function with chosen modules
-    classModule, // makes it easy to toggle classes
-    propsModule, // for setting properties on DOM elements
-    styleModule, // handles styling on elements with support for animations
-    datasetModule,
-    eventListenersModule, // attaches event listeners
-    attributesModule,
-  ])
-  return patch
-}
-
-// vnode.data 保留属性,参考 snabbdom VNodeData
-const DATA_PRESERVE_KEYS = ['props', 'attrs', 'style', 'dataset', 'on', 'hook']
-
-/**
- * 整理 vnode.data ,将暴露出来的零散属性(如 id className data-xxx)放在 data.props 或 data.dataset
- * @param vnode vnode
- */
-export function normalizeVnodeData(vnode: VNode) {
-  const { data = {}, children = [] } = vnode
-  const dataKeys = Object.keys(data)
-  dataKeys.forEach((key: string) => {
-    const value = data[key]
-
-    // 赋值 key
-    if (key === 'key') {
-      vnode.key = value
-      return
-    }
-
-    // 忽略 data 保留属性
-    if (DATA_PRESERVE_KEYS.includes(key)) return
-
-    // dataset
-    if (key.startsWith('data-')) {
-      let datasetKey = key.slice(5) // 截取掉最前面的 'data-'
-      datasetKey = camelCase(datasetKey) // 转为驼峰写法
-
-      // 存储到 data.dataset
-      addVnodeDataset(vnode, { [datasetKey]: value })
-
-      delete data[key] // 删掉原有的属性
-      return
-    }
-
-    // 其他的,都算 props ,存储到 props
-    addVnodeProp(vnode, { [key]: value })
-
-    delete data[key] // 删掉原有的属性
-  })
-
-  // 遍历 children
-  if (children.length > 0) {
-    children.forEach(child => {
-      if (typeof child === 'string') return
-      normalizeVnodeData(child)
-    })
-  }
-}
-
-/**
- * 给 vnode 添加 prop
- * @param vnode vnode
- * @param newProp { key: val }
- */
-export function addVnodeProp(vnode: VNode, newProp: Props) {
-  if (vnode.data == null) vnode.data = {}
-  const data = vnode.data
-  if (data.props == null) data.props = {}
-
-  Object.assign(data.props, newProp)
-}
-
-/**
- * 给 vnode 添加 dataset
- * @param vnode vnode
- * @param newDataset { key: val }
- */
-export function addVnodeDataset(vnode: VNode, newDataset: Dataset) {
-  if (vnode.data == null) vnode.data = {}
-  const data = vnode.data
-  if (data.dataset == null) data.dataset = {}
-
-  Object.assign(data.dataset, newDataset)
-}
-
-/**
- * 给 vnode 添加样式
- * @param vnode vnode
- * @param newStyle { key: val }
- */
-export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) {
-  if (vnode.data == null) vnode.data = {}
-  const data = vnode.data
-  if (data.style == null) data.style = {}
-
-  Object.assign(data.style, newStyle)
-}
diff --git a/packages/core/src/utils/weak-maps.ts b/packages/core/src/utils/weak-maps.ts
deleted file mode 100644
index dfdb49013..000000000
--- a/packages/core/src/utils/weak-maps.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * @description 对象关联关系(部分参考 slate-react weak-maps.ts)
- * @author wangfupeng
- */
-
-import { Emitter } from 'event-emitter'
-import { VNode } from 'snabbdom'
-import { Node, Ancestor, Editor, Path, Range } from 'slate'
-import { IDomEditor } from '../editor/interface'
-import TextArea from '../text-area/TextArea'
-import Toolbar from '../menus/bar/Toolbar'
-import HoverBar from '../menus/bar/HoverBar'
-import { IBarItem } from '../menus/bar-item/index'
-import { Key } from './key'
-import { PatchFn } from '../utils/vdom'
-import { IEditorConfig } from '../config/interface'
-import PanelAndModal from '../menus/panel-and-modal/BaseClass'
-
-// textarea - editor
-export const EDITOR_TO_TEXTAREA = new WeakMap<IDomEditor, TextArea>()
-export const TEXTAREA_TO_EDITOR = new WeakMap<TextArea, IDomEditor>()
-
-// bar - editor
-export const TOOLBAR_TO_EDITOR = new WeakMap<Toolbar, IDomEditor>()
-export const EDITOR_TO_TOOLBAR = new WeakMap<IDomEditor, Toolbar>()
-export const HOVER_BAR_TO_EDITOR = new WeakMap<HoverBar, IDomEditor>()
-export const EDITOR_TO_HOVER_BAR = new WeakMap<IDomEditor, HoverBar>()
-export const BAR_ITEM_TO_EDITOR = new WeakMap<IBarItem, IDomEditor>()
-export const EDITOR_TO_PANEL_AND_MODAL = new WeakMap<IDomEditor, Set<PanelAndModal>>()
-export const PANEL_OR_MODAL_TO_EDITOR = new WeakMap<PanelAndModal, IDomEditor>()
-
-// config
-export const EDITOR_TO_CONFIG = new WeakMap<IDomEditor, IEditorConfig>()
-
-// vdom 相关的属性
-export const IS_FIRST_PATCH = new WeakMap<TextArea, boolean>()
-export const TEXTAREA_TO_PATCH_FN = new WeakMap<TextArea, PatchFn>()
-export const TEXTAREA_TO_VNODE = new WeakMap<TextArea, VNode>()
-
-/**
- * Two weak maps that allow us rebuild a path given a node. They are populated
- * at render time such that after a render occurs we can always backtrack.
- */
-export const NODE_TO_INDEX: WeakMap<Node, number> = new WeakMap()
-export const NODE_TO_PARENT: WeakMap<Node, Ancestor> = new WeakMap()
-
-/**
- * Weak maps that allow us to go between Slate nodes and DOM nodes. These
- * are used to resolve DOM event-related logic into Slate actions.
- */
-export const EDITOR_TO_ELEMENT: WeakMap<Editor, HTMLElement> = new WeakMap()
-export const EDITOR_TO_PLACEHOLDER: WeakMap<Editor, string> = new WeakMap()
-export const ELEMENT_TO_NODE: WeakMap<HTMLElement, Node> = new WeakMap()
-export const KEY_TO_ELEMENT: WeakMap<Key, HTMLElement> = new WeakMap()
-export const NODE_TO_ELEMENT: WeakMap<Node, HTMLElement> = new WeakMap()
-export const NODE_TO_KEY: WeakMap<Node, Key> = new WeakMap()
-export const EDITOR_TO_WINDOW: WeakMap<Editor, Window> = new WeakMap()
-
-/**
- * Weak maps for storing editor-related state.
- */
-export const IS_FOCUSED: WeakMap<Editor, boolean> = new WeakMap()
-export const IS_DRAGGING: WeakMap<Editor, boolean> = new WeakMap()
-export const IS_CLICKING: WeakMap<Editor, boolean> = new WeakMap()
-
-// /**
-//  * Weak map for associating the context `onChange` context with the plugin.
-//  */
-// export const EDITOR_TO_ON_CHANGE = new WeakMap<Editor, () => void>()
-
-// 正在更新,但尚未更新完的节点 path ,临时记录下
-// 例如,table 插入 col ,需要一行一行的插入,在更新期间,不能收到其他的(如 normalize)干扰
-export const CHANGING_NODE_PATH: WeakMap<Editor, Path> = new WeakMap()
-
-// 保存 editor -> selection ,用于还原 editor 选区
-export const EDITOR_TO_SELECTION: WeakMap<Editor, Range> = new WeakMap()
-
-// editor -> eventEmitter 自定义事件
-export const EDITOR_TO_EMITTER: WeakMap<Editor, Emitter> = new WeakMap()
-
-// editor 是否可执行粘贴
-export const EDITOR_TO_CAN_PASTE: WeakMap<Editor, boolean> = new WeakMap()
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
deleted file mode 100644
index 9bef938c9..000000000
--- a/packages/core/tsconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "compilerOptions": {},
-  "extends": "../../tsconfig.json",
-  "include": [
-    "./src/**/*",
-    "../custom-types.d.ts"
-  ]
-}
\ No newline at end of file
diff --git a/packages/custom-types.d.ts b/packages/custom-types.d.ts
deleted file mode 100644
index 88ccf6ab6..000000000
--- a/packages/custom-types.d.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * @description 自定义扩展 slate 接口属性
- * @author wangfupeng
- */
-import { StyledText } from './basic-modules/src/modules/text-style/custom-types'
-import { ColorText } from './basic-modules/src/modules/color/custom-types'
-import { FontSizeAndFamilyText } from './basic-modules/src/modules/font-size-family/custom-types'
-import { LineHeightElement } from './basic-modules/src/modules/line-height/custom-types'
-import { JustifyElement } from './basic-modules/src/modules/justify/custom-types'
-import { IndentElement } from './basic-modules/src/modules/indent/custom-types'
-import { ParagraphElement } from './basic-modules/src/modules/paragraph/custom-types'
-import { LinkElement } from './basic-modules/src/modules/link/custom-types'
-import { BlockQuoteElement } from './basic-modules/src/modules/blockquote/custom-types'
-import {
-  Header1Element,
-  Header2Element,
-  Header3Element,
-  Header4Element,
-  Header5Element,
-} from './basic-modules/src/modules/header/custom-types'
-import { DividerElement } from './basic-modules/src/modules/divider/custom-types'
-import { ImageElement } from './basic-modules/src/modules/image/custom-types'
-import { TodoElement } from './basic-modules/src/modules/todo/custom-types'
-import { PreElement, CodeElement } from './basic-modules/src/modules/code-block/custom-types'
-import { VideoElement } from './video-module/src/module/custom-types'
-import {
-  TableCellElement,
-  TableRowElement,
-  TableElement,
-} from './table-module/src/module/custom-types'
-import { ListItemElement } from './list-module/src/module/custom-types'
-
-type PureText = {
-  text: string
-}
-
-type CustomText = PureText | StyledText | FontSizeAndFamilyText | ColorText
-
-type BaseElement = {
-  type: string
-  children: Array<CustomElement | CustomText>
-}
-
-type CustomElement =
-  | BaseElement
-  | LineHeightElement
-  | JustifyElement
-  | IndentElement
-  | ParagraphElement
-  | LinkElement
-  | BlockQuoteElement
-  | Header1Element
-  | Header2Element
-  | Header3Element
-  | Header4Element
-  | Header5Element
-  | DividerElement
-  | ImageElement
-  | TodoElement
-  | PreElement
-  | CodeElement
-  | VideoElement
-  | TableCellElement
-  | TableRowElement
-  | TableElement
-  | ListItemElement
-
-declare module 'slate' {
-  interface CustomTypes {
-    // 扩展 Text
-    Text: CustomText
-
-    // 扩展 Element
-    Element: CustomElement
-  }
-}
diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md
deleted file mode 100644
index 4d3bb47b1..000000000
--- a/packages/editor/CHANGELOG.md
+++ /dev/null
@@ -1,326 +0,0 @@
-# Change Log
-
-All notable changes to this project will be documented in this file.
-See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [5.1.23](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.22...@wangeditor/editor@5.1.23) (2022-11-14)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.22](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.21...@wangeditor/editor@5.1.22) (2022-10-18)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.21](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.20...@wangeditor/editor@5.1.21) (2022-10-04)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.20](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.19...@wangeditor/editor@5.1.20) (2022-09-27)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.19](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.18...@wangeditor/editor@5.1.19) (2022-09-27)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.18](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.17...@wangeditor/editor@5.1.18) (2022-09-16)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.17](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.16...@wangeditor/editor@5.1.17) (2022-09-15)
-
-
-### Bug Fixes
-
-* customInsert 不触发 onSuccess ([d6f4a1b](https://github.com/wangeditor-team/wangEditor/commit/d6f4a1b1494864b116a1310cce2d9e8632c92c6f))
-* focus table 时 isFocused 异常 ([5c52bf3](https://github.com/wangeditor-team/wangEditor/commit/5c52bf33e91b1a4677e7bbc04c5d80698abfeeab))
-* 上传视频 - customBrowseAndUpload 缺少 poster ([c24627a](https://github.com/wangeditor-team/wangEditor/commit/c24627aaa4c173c5d435e3077dfe8f6b4a9a87b1))
-
-
-
-
-
-## [5.1.16](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.15...@wangeditor/editor@5.1.16) (2022-09-14)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.15](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.14...@wangeditor/editor@5.1.15) (2022-08-30)
-
-
-### Bug Fixes
-
-* checkVideo 增加 poster 参数 ([c0402e1](https://github.com/wangeditor-team/wangEditor/commit/c0402e155470233d256e037d863dab74c026b7f6))
-
-
-
-
-
-## [5.1.14](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.13...@wangeditor/editor@5.1.14) (2022-07-27)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.13](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.12...@wangeditor/editor@5.1.13) (2022-07-27)
-
-
-### Bug Fixes
-
-* setHtml 支持空字符串 ([d438157](https://github.com/wangeditor-team/wangEditor/commit/d43815766320d9cb0548bae0415c54ce7b147efb))
-* upload file callback error ([bf20e07](https://github.com/wangeditor-team/wangEditor/commit/bf20e07f12ed242b0ab4bb2290d876153a822972))
-
-
-
-
-
-## [5.1.12](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.11...@wangeditor/editor@5.1.12) (2022-07-22)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.11](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.10...@wangeditor/editor@5.1.11) (2022-07-18)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.10](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.9...@wangeditor/editor@5.1.10) (2022-07-16)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.9](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.8...@wangeditor/editor@5.1.9) (2022-07-14)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.8](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.7...@wangeditor/editor@5.1.8) (2022-07-14)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.7](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.6...@wangeditor/editor@5.1.7) (2022-07-13)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.6](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.5...@wangeditor/editor@5.1.6) (2022-07-12)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.5](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.3...@wangeditor/editor@5.1.5) (2022-07-11)
-
-
-### Bug Fixes
-
-* 尝试修复 nuxt 报错 issue[#4409](https://github.com/wangeditor-team/wangEditor/issues/4409) ([912f888](https://github.com/wangeditor-team/wangEditor/commit/912f8889a11d962b3ac2c65cb5835f4e8c58c372))
-
-
-
-
-
-## [5.1.4](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.3...@wangeditor/editor@5.1.4) (2022-07-11)
-
-
-### Bug Fixes
-
-* 尝试修复 nuxt 报错 issue[#4409](https://github.com/wangeditor-team/wangEditor/issues/4409) ([912f888](https://github.com/wangeditor-team/wangEditor/commit/912f8889a11d962b3ac2c65cb5835f4e8c58c372))
-
-
-
-
-
-## [5.1.3](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.2...@wangeditor/editor@5.1.3) (2022-07-11)
-
-
-### Bug Fixes
-
-* scroll 滚动问题 ([bc133e1](https://github.com/wangeditor-team/wangEditor/commit/bc133e1e4ca89ab5042cbc0971578ad144499805))
-
-
-
-
-
-## [5.1.2](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.1...@wangeditor/editor@5.1.2) (2022-07-11)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-## [5.1.1](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.1.0...@wangeditor/editor@5.1.1) (2022-06-02)
-
-**Note:** Version bump only for package @wangeditor/editor
-
-
-
-
-
-# [5.1.0](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/editor@5.0.1...@wangeditor/editor@5.1.0) (2022-05-25)
-
-
-### Features
-
-* editVideoSize ([375eecb](https://github.com/wangeditor-team/wangEditor/commit/375eecba826eac681268c55c47bcd922f7157d63))
-* enter menu ([988fc31](https://github.com/wangeditor-team/wangEditor/commit/988fc31f31de3d37dffbf54abb784cceb8e6118d))
-* setHtml ([f4f91b8](https://github.com/wangeditor-team/wangEditor/commit/f4f91b883298091e3679ca6b206ae0d796003772))
-
-
-
-
-
-## 5.0.1 (2022-04-18)
-
-
-### Bug Fixes
-
-* 不支持 IE 浏览器的提醒 ([70c5cae](https://github.com/wangeditor-team/wangEditor/commit/70c5caefd8f6f663225b7a0b796a035d274ef4e1))
-* 打包问题 ([c4e87cc](https://github.com/wangeditor-team/wangEditor/commit/c4e87ccac82bcf90d20b7304aff83745e52fb1b1))
-* 更新各包之间依赖版本 ([75c552c](https://github.com/wangeditor-team/wangEditor/commit/75c552cc8ed54765bebb86a7ec5329a7fc79e85f))
-* 兼容 AggregateError ([0cbd82d](https://github.com/wangeditor-team/wangEditor/commit/0cbd82d30d350b2313f6373e2b5f6d168e47e1bc))
-* 兼容next.js及nuxt.js ([233728e](https://github.com/wangeditor-team/wangEditor/commit/233728eb984f541437c62a1390fa0542b2cc6227))
-* 开放几个第三方用的 API ([bdf3e70](https://github.com/wangeditor-team/wangEditor/commit/bdf3e70c52bac71e2056e21237fe4ac9e2b0818f))
-* 拼音隐藏 placeholder ([aec1a9f](https://github.com/wangeditor-team/wangEditor/commit/aec1a9f62af8944b7894beeca953076ec73545d5))
-* 上传图片 - base64 仍触发上传 + 超出 maxSize 的报错提醒 ([a1d469a](https://github.com/wangeditor-team/wangEditor/commit/a1d469accb7f87f8ea0282a1699d002aaaa4e79a))
-* 添加QQ浏览器polyfill ([a1b476a](https://github.com/wangeditor-team/wangEditor/commit/a1b476a0bed52315f3e398c586d73f85996f9431))
-* 图片上传,提示 ([3754012](https://github.com/wangeditor-team/wangEditor/commit/37540129dff1212c5ebfd4ca3f4d4e8def735e73))
-* 修复 node 环境下报错问题 ([5a635a5](https://github.com/wangeditor-team/wangEditor/commit/5a635a5e8fac942ee214dd22b097e09057abc69c))
-* 修复取消链接后撤销再重做报错的问题 ([9b233a9](https://github.com/wangeditor-team/wangEditor/commit/9b233a92c95571235248623a6ca5212eb4237f2a))
-* 移除了每个包下的 publishConfig directory 配置 ([16559f0](https://github.com/wangeditor-team/wangEditor/commit/16559f052545c111318be760e64291a521bdcc65))
-* 优化选中代码块不应该展示 hoverbar 的交互 ([33dcbd6](https://github.com/wangeditor-team/wangEditor/commit/33dcbd6560dccfbe77e18cfbce8c9f077f19f6cd))
-* delete divider ([f04cbd6](https://github.com/wangeditor-team/wangEditor/commit/f04cbd6009099629e3cd41be19d20b6788fe7f28))
-* divider - 键盘删除 ([31db059](https://github.com/wangeditor-team/wangEditor/commit/31db0593dbc77fba9b4a719bc0f48f1223afd680))
-* example/code-hightlight ([7885988](https://github.com/wangeditor-team/wangEditor/commit/78859884cefc18d15ce2f87507380a78c2ad65e5))
-* globalThis 兼容性 ([7a47f4b](https://github.com/wangeditor-team/wangEditor/commit/7a47f4b904815516d3b5749ab652ff80478411bc))
-* group-menu 考虑 excludeKeys ([ecc29f3](https://github.com/wangeditor-team/wangEditor/commit/ecc29f3b24992c8dc0adf006d81b0d4a252683c5))
-* hoverbar config - 同时选中文字和 table ([8f6b4d1](https://github.com/wangeditor-team/wangEditor/commit/8f6b4d1a20e3b1b75da69b20bd5893ce08a27185))
-* hoverbarKeys - text ([59b4840](https://github.com/wangeditor-team/wangEditor/commit/59b48406b4c373ef029a5f5bdb0d15d925a91a0f))
-* html 特殊字符 ([b3eb81b](https://github.com/wangeditor-team/wangEditor/commit/b3eb81bc9c4aa15c2ff7451c173de15d6c4552bc))
-* i18n - 获取多语言配置 ([9f81597](https://github.com/wangeditor-team/wangEditor/commit/9f815970f8c3c6dddb6bf846ecb672325e80444b))
-* i18n 切换语言 ([b3b4642](https://github.com/wangeditor-team/wangEditor/commit/b3b4642c6e72ab0b13b05657745abb87e71c633d))
-* insertKeys ([0a89420](https://github.com/wangeditor-team/wangEditor/commit/0a8942050bd0b39afb5bbc55ca7842461a5b98eb))
-* link, text hoverbar 选区问题 ([e0b7438](https://github.com/wangeditor-team/wangEditor/commit/e0b7438c89a347f1b0b940d9c11150b72d595529))
-* menu 点击多次才能生效 ([6497e39](https://github.com/wangeditor-team/wangEditor/commit/6497e39225a993c4d87f9ffddf20086446a4fbc2))
-* normalize when create editor ([2b51962](https://github.com/wangeditor-team/wangEditor/commit/2b5196244a93ad7beb316bfa42e557221967d063))
-* parse html - v4 video ([8dca822](https://github.com/wangeditor-team/wangEditor/commit/8dca822f9f1b52fd71dd6e17f0954d6aa016324b))
-* qq 浏览器报错 ([8a09ed5](https://github.com/wangeditor-team/wangEditor/commit/8a09ed5d810fc1e2c4d0c529aa1269ed0c06425e))
-* readOnly 时菜单还可操作 ([0d4a29b](https://github.com/wangeditor-team/wangEditor/commit/0d4a29bb5ba8b62ac11a09d3f814abcb1fcf46be))
-* registerModule ([189981c](https://github.com/wangeditor-team/wangEditor/commit/189981c73db07d5b15ee4c46b1639f76f6f63ba1))
-* rename es module filename ([1821d4e](https://github.com/wangeditor-team/wangEditor/commit/1821d4eef49e64efcb41b848849ca7a5e6472044))
-* shadow dom 样式缺失 ([2fcb69c](https://github.com/wangeditor-team/wangEditor/commit/2fcb69c866266cc5b0265cff031ae9279d368b84))
-* style-to-html - 输入 a 会删除外部的 <a> 标签 ([af1f523](https://github.com/wangeditor-team/wangEditor/commit/af1f523983f2bc4b7eaf9726d4b8a35227ab27dc))
-* table - elemToHtml ([e36e609](https://github.com/wangeditor-team/wangEditor/commit/e36e6092ef721723169afc8bf0560a47ac9f4dfc))
-* tableCell 中 br 报错 ([8604db7](https://github.com/wangeditor-team/wangEditor/commit/8604db751b622c01fa5391af59328236cf13effc))
-* text hoverbar ([c7de4f8](https://github.com/wangeditor-team/wangEditor/commit/c7de4f815d6f5b9e009a3149ed042052576c424e))
-* text hoverbar ([efe9a34](https://github.com/wangeditor-team/wangEditor/commit/efe9a34d85f8baaeced27543a7bcd508b50f6bca))
-* video - 键盘删除 ([5a6bedd](https://github.com/wangeditor-team/wangEditor/commit/5a6bedd80fa0d758270731f62115637ad7f313d0))
-
-
-### Features
-
-* 两端对齐 ([e5080d3](https://github.com/wangeditor-team/wangEditor/commit/e5080d3dd102f7a951d8e1f370db834778ecbdfa))
-* 上标 下标 ([40dab08](https://github.com/wangeditor-team/wangEditor/commit/40dab085a061ea3e838f0cfa86260c6c6f894c69))
-* 上传图片 metaWithUrl ([2485157](https://github.com/wangeditor-team/wangEditor/commit/24851576a1dcc07b1a8931d17a147c3640222e85))
-* 增加 enable disable API(删除 setConfig setMenuConfig API) ([984fc50](https://github.com/wangeditor-team/wangEditor/commit/984fc50520061fc34ea08f4136bdeb93dee46564))
-* 支持 nodejs 环境 ([484f18c](https://github.com/wangeditor-team/wangEditor/commit/484f18c3abc70d19e51c556f48491c18d390b1e1))
-* basic text style module ([005b343](https://github.com/wangeditor-team/wangEditor/commit/005b343573ba98f2d0b8480d034ff6807a499aa3))
-* block quote ([c3c87a5](https://github.com/wangeditor-team/wangEditor/commit/c3c87a5c09b311eb14c799df94fc4826aa3f4262))
-* bold & header ([8130c23](https://github.com/wangeditor-team/wangEditor/commit/8130c23ad84485a68cf9ca4b53d52fab1cec4e96))
-* clearStyle menu ([8002f70](https://github.com/wangeditor-team/wangEditor/commit/8002f707ed04b914180ec36fdca0edf48c815e01))
-* code highlight ([42b2f8d](https://github.com/wangeditor-team/wangEditor/commit/42b2f8d192e2433593c11ad0b8424737f6cffb58))
-* code-block - part ([a8bcd63](https://github.com/wangeditor-team/wangEditor/commit/a8bcd63d882832ac05a32878df0f767d145e0fa7))
-* create editor ([12d98e4](https://github.com/wangeditor-team/wangEditor/commit/12d98e4bee179e9d277ec3ec2ecb827962ed0e75))
-* create mode ([63c2eef](https://github.com/wangeditor-team/wangEditor/commit/63c2eef9a9a0a2838dfadd23483de35a76f88b0b))
-* customPaste ([0f25f5c](https://github.com/wangeditor-team/wangEditor/commit/0f25f5cae3a2cd5ae5832f3fc1026b3ab6d047e0))
-* divider menu ([5262634](https://github.com/wangeditor-team/wangEditor/commit/526263445616725541bf374b80260e73b1d4c6ec))
-* drag resize image ([cd72028](https://github.com/wangeditor-team/wangEditor/commit/cd72028f1786e2e53079ad5cbef1b8569731ca79))
-* editor 生命周期,自定义事件 ([00e9bc2](https://github.com/wangeditor-team/wangEditor/commit/00e9bc2cfcb8b622764db1c76394491d72ffd93e))
-* editor with-selection plugin ([9f0a39f](https://github.com/wangeditor-team/wangEditor/commit/9f0a39fecf6d92888d2a97929820d3be038efb31))
-* editor.isSelectedAll ([960c845](https://github.com/wangeditor-team/wangEditor/commit/960c8455f85a6bc7350f9944be80b3997bc1fea1))
-* editor.showProgressBar ([51761d4](https://github.com/wangeditor-team/wangEditor/commit/51761d466ab3ef7c99e872954d4724ab51d8e28c))
-* emotion ([736f955](https://github.com/wangeditor-team/wangEditor/commit/736f955211287bafca2375de3c8193cd0aa0856f))
-* font-size + font-family ([cc649e0](https://github.com/wangeditor-team/wangEditor/commit/cc649e0918ce58e78b4d5ee49a400197b9d04b70))
-* fullScreen ([e7ccd88](https://github.com/wangeditor-team/wangEditor/commit/e7ccd88a7dd58f64b7bd484de428e3a76cc994f7))
-* getElemsByTypePrefix (删掉 getHeaders) ([c18834b](https://github.com/wangeditor-team/wangEditor/commit/c18834b3ebfd97fb36ccbe0faa84e6fe8c30eb67))
-* header button menu ([6413135](https://github.com/wangeditor-team/wangEditor/commit/64131354d54705e11fd6992fcf5a4389371c3560))
-* hover bar ([107356e](https://github.com/wangeditor-team/wangEditor/commit/107356eff7bfaf53ce25e39244f8133c80518375))
-* i18n ([c11b244](https://github.com/wangeditor-team/wangEditor/commit/c11b2440f91b99d40bca18b675c66a22b6e160c9))
-* image menu - width 50% 100% ([f9b4c68](https://github.com/wangeditor-team/wangEditor/commit/f9b4c68dff3232b50491b07949c20eb4c18baa6b))
-* image menus & position ([bf5beba](https://github.com/wangeditor-team/wangEditor/commit/bf5beba7b3014d63f0b9fe0063530c8b101a5011))
-* indent menu + groupMenu ([08db901](https://github.com/wangeditor-team/wangEditor/commit/08db901cd3a3f2ddb2173cc4b36d471e4e68237e))
-* insert link ([b04242f](https://github.com/wangeditor-team/wangEditor/commit/b04242ffa252d4088f5360c3de45c24d6f493552))
-* justify ([2ed7b88](https://github.com/wangeditor-team/wangEditor/commit/2ed7b883ca759dc4a9e0eefbd23cfe603a0f46fd))
-* line-height menu ([755a752](https://github.com/wangeditor-team/wangEditor/commit/755a752d76803423f2794b85004d75055c9b54ec))
-* list menu ([fe6c083](https://github.com/wangeditor-team/wangEditor/commit/fe6c0830b2c43e335e5972f85096f490694bbe19))
-* menu color - part ([3a6cc86](https://github.com/wangeditor-team/wangEditor/commit/3a6cc86a7f9133d0862310c408abafb30c531734))
-* menu color & dropPanel & menu config ([5d0d41b](https://github.com/wangeditor-team/wangEditor/commit/5d0d41b9a765a7deb583393f129925414c36ef35))
-* modal appendTo body ([fc0ab06](https://github.com/wangeditor-team/wangEditor/commit/fc0ab06d5c7177eceb04643234a8c301ca4de396))
-* parse html ([2a5eace](https://github.com/wangeditor-team/wangEditor/commit/2a5eace00f33cded50b68e8164748ec2480213fd))
-* parse src (link image video) ([715a841](https://github.com/wangeditor-team/wangEditor/commit/715a841fc6c730ee2b448a1799a07ce778128aad))
-* selectList ([b7366ab](https://github.com/wangeditor-team/wangEditor/commit/b7366ab2dafd379145d85881052d6f400bd13c85))
-* shadow dom example ([c55f38d](https://github.com/wangeditor-team/wangEditor/commit/c55f38dab7886b9115c4353118d1f182885878ae))
-* table module ([a397116](https://github.com/wangeditor-team/wangEditor/commit/a397116de73e088232d9c41828f30f8d56a22dd4))
-* table module - header + fullWidth ([9a8a0e0](https://github.com/wangeditor-team/wangEditor/commit/9a8a0e093af944ee7deab674f47c2ec7baae0e63))
-* text and toolbar ([3ae5d0c](https://github.com/wangeditor-team/wangEditor/commit/3ae5d0c4138fec7397ac8629e0012affe6b7dfa4))
-* todo ([9608fef](https://github.com/wangeditor-team/wangEditor/commit/9608fef2ff86368cdcbb950a74af1246a58709de))
-* toHtml 机制 ([1c4d872](https://github.com/wangeditor-team/wangEditor/commit/1c4d8729f84aaab6a448f23064b34a20596305e9))
-* toolbar config - insertKeys ([a2f3c4b](https://github.com/wangeditor-team/wangEditor/commit/a2f3c4be3762831723495bbc9d50eb6c9b05d195))
-* toolbar excludeKeys ([09bd196](https://github.com/wangeditor-team/wangEditor/commit/09bd196ea24c19b04e5e7e38227ca94332847bf8))
-* tooltip ([994d875](https://github.com/wangeditor-team/wangEditor/commit/994d875fee81cf01271c2e440c1df202aa067d0e))
-* undo redo - menu ([bfb3014](https://github.com/wangeditor-team/wangEditor/commit/bfb3014791cfcb2d7897f916bf280a57b1906e4d))
-* updateLink + unLink + viewLink ([254d554](https://github.com/wangeditor-team/wangEditor/commit/254d55466b3c8527dd9f0bf34681abd801c8c8ce))
-* upload image ([0a0564b](https://github.com/wangeditor-team/wangEditor/commit/0a0564bf14edd4dea6eb958e653272a9a216cec1))
-* upload video ([ac8e6f8](https://github.com/wangeditor-team/wangEditor/commit/ac8e6f8b5258e593714676a6f6be359ba525833c))
-* video menu ([c1faa1c](https://github.com/wangeditor-team/wangEditor/commit/c1faa1cfa896e1d240f5a2a100e1fd9b89dbef0b))
diff --git a/packages/editor/README-en.md b/packages/editor/README-en.md
deleted file mode 100644
index 77035f58d..000000000
--- a/packages/editor/README-en.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# wangEditor editor
-
-[中文](./README.md)
-
-Open source web rich text editor, run right out of the box. Support JS Vue React.
-
-- [Document](https://www.wangeditor.com/en/)
-- [Demo](https://www.wangeditor.com/demo/?lang=en)
-
-![](../../docs/images/editor-en.png)
-
-You can [commit an issue]((https://github.com/wangeditor-team/wangEditor/issues)) if you have any question.
diff --git a/packages/editor/README.md b/packages/editor/README.md
deleted file mode 100644
index e95bab6fa..000000000
--- a/packages/editor/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# wangEditor editor
-
-[English](./README-en.md)
-
-开源 Web 富文本编辑器,开箱即用,配置简单。支持 JS Vue React 。
-
-- [文档](https://www.wangeditor.com/)
-- [demo](https://www.wangeditor.com/demo/)
-
-![](../../docs/images/editor.png)
-
-交流
-- [提交问题和建议](https://github.com/wangeditor-team/wangEditor/issues)
-- 加入 QQ 群([官网](https://www.wangeditor.com/)有群号)
diff --git a/packages/editor/__tests__/create.test.ts b/packages/editor/__tests__/create.test.ts
deleted file mode 100644
index 8f6240ea9..000000000
--- a/packages/editor/__tests__/create.test.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * @description create editor and toolbar test
- * @author wangfupeng
- */
-
-import { createEditor, createToolbar } from '../../../packages/editor/src/index'
-import { ICreateEditorOption, ICreateToolbarOption } from '../../../packages/editor/src/create'
-
-function customCreateEditor(config: Partial<ICreateEditorOption> = {}) {
-  const editorContainer = document.createElement('div')
-  document.body.appendChild(editorContainer)
-
-  // create editor
-  const editor = createEditor({
-    selector: editorContainer,
-    ...config,
-  })
-
-  return editor
-}
-
-function customCreateToolbar(config: Partial<ICreateToolbarOption> = {}) {
-  const toolbarContainer = document.createElement('div')
-  document.body.appendChild(toolbarContainer)
-
-  // create editor
-  const editor = customCreateEditor()
-
-  // create toolbar
-  const toolbar = createToolbar({
-    editor,
-    selector: toolbarContainer,
-    ...config,
-  })
-
-  return toolbar
-}
-
-describe('create editor and toolbar', () => {
-  test('create editor with default mode', () => {
-    const editor = customCreateEditor()
-
-    expect(editor.id).not.toBeNull()
-  })
-
-  test('create editor with default mode that has text hoverbar', () => {
-    const editor = customCreateEditor()
-    const config = editor.getConfig()
-
-    expect(config.hoverbarKeys!.text).not.toBeNull()
-  })
-
-  test('create editor with simple mode', () => {
-    const editor = customCreateEditor({
-      mode: 'simple',
-    })
-    expect(editor.id).not.toBeNull()
-  })
-
-  test('create editor with simple mode that does not has text hoverbar', () => {
-    const editor = customCreateEditor({
-      mode: 'simple',
-    })
-    const config = editor.getConfig()
-
-    expect(config.hoverbarKeys!.text).toBeUndefined()
-  })
-
-  test('create editor can not be called twice with same container', () => {
-    const editorContainer = document.createElement('div')
-    document.body.appendChild(editorContainer)
-    // create editor
-    customCreateEditor({
-      selector: editorContainer,
-    })
-
-    try {
-      customCreateEditor({
-        selector: editorContainer,
-      })
-    } catch (ex) {
-      expect(ex.message.indexOf('Repeated create editor by selector')).not.toBe(-1)
-    }
-  })
-
-  test('create toolbar with default mode', () => {
-    const toolbar = customCreateToolbar()
-    expect(toolbar.$box).not.toBeNull()
-  })
-
-  test('create toolbar with simple mode', () => {
-    const toolbar = customCreateToolbar({
-      mode: 'simple',
-    })
-    expect(toolbar.$box).not.toBeNull()
-  })
-
-  test('create toolbar with simple mode that the config hoverbarKeys is different from default mode', () => {
-    const simpleToolbar = customCreateToolbar({
-      mode: 'simple',
-    })
-    const defaultToolbar = customCreateToolbar()
-    expect(simpleToolbar.getConfig().toolbarKeys).not.toEqual(
-      defaultToolbar.getConfig().toolbarKeys
-    )
-  })
-
-  test('create toolbar can not be called twice with same container', () => {
-    const toolbarContainer = document.createElement('div')
-    document.body.appendChild(toolbarContainer)
-
-    customCreateToolbar({
-      selector: toolbarContainer,
-    })
-    try {
-      customCreateToolbar({
-        selector: toolbarContainer,
-      })
-    } catch (ex) {
-      expect(ex.message.indexOf('Repeated create toolbar by selector')).not.toBe(-1)
-    }
-  })
-
-  test('create editor with html', () => {
-    const html = `<h1>header</h1>
-<p>hello&nbsp;<strong>world</strong>
-</p><p><br></p>`
-
-    const editor = customCreateEditor({ html })
-    expect(editor.children).toEqual([
-      { type: 'header1', children: [{ text: 'header' }] },
-      {
-        type: 'paragraph',
-        children: [{ text: 'hello ' }, { text: 'world', bold: true }],
-      },
-      { type: 'paragraph', children: [{ text: '' }] },
-    ])
-  })
-})
diff --git a/packages/editor/demo/README.md b/packages/editor/demo/README.md
deleted file mode 100644
index 82337e6aa..000000000
--- a/packages/editor/demo/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# wangEditor demo
-
-修改左侧目录,在 demo 目录搜索 `MENU_CONF`
-
-demo 部署参考 `deploy-demos.yml` 配置
diff --git a/packages/editor/demo/catalog.html b/packages/editor/demo/catalog.html
deleted file mode 100644
index 3dda8a823..000000000
--- a/packages/editor/demo/catalog.html
+++ /dev/null
@@ -1,128 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor catalog</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-
-  <style>
-    #header-container {
-      list-style-type: none;
-      padding-left: 20px;
-    }
-
-    #header-container li {
-      color: #333;
-      margin: 10px 0;
-      cursor: pointer;
-    }
-
-    #header-container li:hover {
-      text-decoration: underline;
-    }
-
-    #header-container li[type="header1"] {
-      font-size: 20px;
-      font-weight: bold;
-    }
-
-    #header-container li[type="header2"] {
-      font-size: 16px;
-      padding-left: 15px;
-      font-weight: bold;
-    }
-
-    #header-container li[type="header3"] {
-      font-size: 14px;
-      padding-left: 30px;
-    }
-
-    #header-container li[type="header4"] {
-      font-size: 12px;
-      padding-left: 45px;
-    }
-
-    #header-container li[type="header5"] {
-      font-size: 12px;
-      padding-left: 60px;
-    }
-  </style>
-</head>
-
-<body>
-  <demo-nav title="wangEditor catalog"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right" style="display: flex;">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc; flex: 1; width: calc(100vw - 370px);">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 600px"></div>
-      </div>
-
-      <!-- 标题目录 -->
-      <div style="width: 200px; background-color: #f1f1f1;">
-        <ul id="header-container"></ul>
-      </div>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    // 标题 DOM 容器
-    const headerContainer = document.getElementById('header-container')
-    headerContainer.addEventListener('mousedown', event => {
-      if (event.target.tagName !== 'LI') return
-      event.preventDefault()
-      const id = event.target.id
-      editor.scrollToElem(id) // 滚动到标题
-    })
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<h1>标题</h1><h2>标题A</h2><p>文本</p><p>文本</p><p>文本</p><h3>标题A1</h3><p>文本</p><p>文本</p><p>文本</p><h3>标题A2</h3><p>文本</p><p>文本</p><p>文本</p><h2>标题B</h2><p>文本</p><p>文本</p><p>文本</p><h3>标题B1</h3><p>文本</p><p>文本</p><p>文本</p><h3>标题B2</h3><p>文本</p><p>文本</p><p>文本</p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          const headers = editor.getElemsByTypePrefix('header')
-          headerContainer.innerHTML = headers.map(header => {
-            const text = E.SlateNode.string(header)
-            const { id, type } = header
-            return `<li id="${id}" type="${type}">${text}</li>`
-          }).join('')
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {}
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/code-highlight.html b/packages/editor/demo/code-highlight.html
deleted file mode 100644
index 33a3fb56e..000000000
--- a/packages/editor/demo/code-highlight.html
+++ /dev/null
@@ -1,72 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor code highlight</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-
-  <!-- 引入 prism css -->
-  <!-- <link href="https://cdn.jsdelivr.net/npm/prismjs@latest/themes/prism.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/prismjs@latest/themes/prism.css" rel="stylesheet">
-</head>
-
-<body>
-  <demo-nav title="wangEditor code highlight"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-
-      <!-- 静态 -->
-      <div style="border: 1px solid #ccc; padding: 0 10px; border-radius: 5px;">
-        <p>wangEditor 输出的 Javascript 代码:</p>
-        <pre><code class="language-javascript">const a = 100;
-function fn(x) { return x + 10 };
-// 注释
-</code></pre>
-
-        <p>wangEditor 输出的 HTML 代码:</p>
-        <pre><code class="language-html">&lt;div&gt;text1&lt;/div&gt;</code></pre>
-      </div>
-
-      <!-- 动态 -->
-      <div id="content-container"
-        style="border: 1px solid #ccc; padding: 0 10px; margin-top: 15px; border-radius: 5px;"></div>
-      <div style="margin-top: 15px;">
-        <button id="btn-render">Dynamic render HTML</button>
-      </div>
-    </div>
-  </div>
-
-  <!-- 引入 prism js -->
-  <!-- <script src="https://cdn.jsdelivr.net/npm/prismjs@latest/prism.min.js"></script> -->
-  <script src="https://unpkg.com/prismjs@latest/prism.js"></script>
-  <!-- <script src="https://cdn.jsdelivr.net/npm/prismjs@latest/components/prism-core.min.js"></script> -->
-  <script src="https://unpkg.com/prismjs@latest/components/prism-core.js"></script>
-  <!-- <script src="https://cdn.jsdelivr.net/npm/prismjs@latest/plugins/autoloader/prism-autoloader.min.js"></script> -->
-  <script src="https://unpkg.com/prismjs@latest/plugins/autoloader/prism-autoloader.js"></script>
-
-  <script>
-    const html = `<p>wangEditor&nbsp;输入的 Javascript 代码:</p><pre><code class="language-javascript">function fn(a) {
-  if (typeof a === 'number') {
-      return a + 100 // comments
-  }
-  return 0
-}</code></pre><p><br></p>`
-
-    document.getElementById('btn-render').addEventListener('click', () => {
-      document.getElementById('content-container').innerHTML = html
-      Prism.highlightAll()
-    })
-
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/css/layout.css b/packages/editor/demo/css/layout.css
deleted file mode 100644
index 235657934..000000000
--- a/packages/editor/demo/css/layout.css
+++ /dev/null
@@ -1,19 +0,0 @@
-/* body {
-  margin: 20px;
-} */
-
-.page-container {
-  margin-top: 15px;
-  display: flex;
-}
-
-.page-left {
-  width: 150px;
-  padding: 0 10px;
-}
-
-.page-right {
-  padding: 0 10px;
-  flex: 1;
-  width: calc(100vw - 170px);
-}
\ No newline at end of file
diff --git a/packages/editor/demo/css/view.css b/packages/editor/demo/css/view.css
deleted file mode 100644
index ba6e29a12..000000000
--- a/packages/editor/demo/css/view.css
+++ /dev/null
@@ -1,52 +0,0 @@
-.editor-content-view {
-  border: 3px solid #ccc;
-  border-radius: 5px;
-  padding: 0 10px;
-  margin-top: 20px;
-  overflow-x: auto;
-}
-
-.editor-content-view p,
-.editor-content-view li {
-  white-space: pre-wrap; /* 保留空格 */
-}
-
-.editor-content-view blockquote {
-  border-left: 8px solid #d0e5f2;
-  padding: 10px 10px;
-  margin: 10px 0;
-  background-color: #f1f1f1;
-}
-
-.editor-content-view code {
-  font-family: monospace;
-  background-color: #eee;
-  padding: 3px;
-  border-radius: 3px;
-}
-.editor-content-view pre>code {
-  display: block;
-  padding: 10px;
-}
-
-.editor-content-view table {
-  border-collapse: collapse;
-}
-.editor-content-view td,
-.editor-content-view th {
-  border: 1px solid #ccc;
-  min-width: 50px;
-  height: 20px;
-}
-.editor-content-view th {
-  background-color: #f1f1f1;
-}
-
-.editor-content-view ul,
-.editor-content-view ol {
-  padding-left: 20px;
-}
-
-.editor-content-view input[type="checkbox"] {
-  margin-right: 5px;
-}
\ No newline at end of file
diff --git a/packages/editor/demo/extend-menu-drop-panel.html b/packages/editor/demo/extend-menu-drop-panel.html
deleted file mode 100644
index b7377dbd8..000000000
--- a/packages/editor/demo/extend-menu-drop-panel.html
+++ /dev/null
@@ -1,153 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor extend dropPanel menu</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-  <style>
-    .w-e-panel-my-list {
-      text-align: left;
-    }
-
-    .w-e-panel-my-list li {
-      display: inline;
-      cursor: pointer;
-      padding: 3px 5px;
-    }
-
-    .w-e-panel-my-list li:hover {
-      background-color: #f1f1f1;
-    }
-  </style>
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor extend dropPanel menu"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 500px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>;
-        Selected text length: <span id="selected-length"></span>;
-      </p>
-    </div>
-  </div>
-
-  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG) // 切换语言
-
-
-
-    // Extend menu
-    class MyMenu {
-      constructor() {
-        this.title = 'My menu'
-        // this.iconSvg = '<svg >...</svg>'
-        this.tag = 'button'
-        this.showDropPanel = true
-      }
-      getValue(editor) {
-        return ''
-      }
-      isActive(editor) {
-        return false // or true
-      }
-      isDisabled(editor) {
-        return false // or true
-      }
-      exec(editor, value) {
-        // do nothing 什么都不用做
-      }
-      getPanelContentElem(editor) {
-        const $list = $(`<ul class="w-e-panel-my-list">
-            <li>北京</li>
-            <li>上海</li>
-            <li>深圳</li>
-            <li>广州</li>
-            <li>天津</li>
-            <li>成都</li>
-            <li>南京</li>
-            <li>郑州</li>
-          </ul>`)
-
-        $list.on('click', 'li', function () {
-          editor.insertText(this.innerHTML)
-          editor.insertText(' ')
-        })
-
-        return $list[0]
-
-        // PS:也可以把 $list 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
-      }
-    }
-    const myMenuConf = {
-      key: 'myMenu',
-      factory() {
-        return new MyMenu()
-      }
-    }
-    E.Boot.registerMenu(myMenuConf)
-
-
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<p><br></p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          console.log(editor.getHtml())
-
-          // 选中文字
-          const selectionText = editor.getSelectionText()
-          document.getElementById('selected-length').innerHTML = selectionText.length
-          // 全部文字
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {
-        insertKeys: {
-          index: 0,
-          keys: ['myMenu'], // show menu in toolbar
-        }
-      }
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/extend-menu-modal.html b/packages/editor/demo/extend-menu-modal.html
deleted file mode 100644
index 484ee4e2d..000000000
--- a/packages/editor/demo/extend-menu-modal.html
+++ /dev/null
@@ -1,156 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor extend modal menu</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor extend modal menu"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 500px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>;
-        Selected text length: <span id="selected-length"></span>;
-      </p>
-    </div>
-  </div>
-
-  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG) // 切换语言
-
-
-
-    // Extend menu
-    class MyMenu {
-      constructor() {
-        this.title = 'My menu'
-        // this.iconSvg = '<svg >...</svg>'
-        this.tag = 'button'
-        this.showModal = true
-        this.modalWidth = 300
-      }
-      getValue(editor) {
-        return ''
-      }
-      isActive(editor) {
-        return false // or true
-      }
-      isDisabled(editor) {
-        return false // or true
-      }
-      exec(editor, value) {
-        // do nothing 什么都不用做
-      }
-      getModalPositionNode(editor) {
-        return null // modal 依据选区定位
-      }
-      getModalContentElem(editor) {
-        const $container = $('<div></div>')
-
-        const inputId = `input-${Math.random().toString(16).slice(-8)}`
-        const buttonId = `button-${Math.random().toString(16).slice(-8)}`
-
-        const $inputContainer = $(`<label class="babel-container">
-            <span>Text</span>
-            <input type="text" id="${inputId}" value="hello world">
-          </label>`)
-        const $buttonContainer = $(`<div class="button-container">
-            <button id="${buttonId}">insert text</button>
-          </div>`)
-
-        $container.append($inputContainer).append($buttonContainer)
-
-        $container.on('click', `#${buttonId}`, e => {
-          e.preventDefault()
-
-          const text = $(`#${inputId}`).val()
-          if (!text) return
-
-          editor.restoreSelection() // 恢复选区
-          editor.insertText(text)
-          editor.insertText(' ')
-        })
-
-        setTimeout(() => {
-          $(`#${inputId}`).focus()
-        })
-
-        return $container[0]
-
-        // PS:也可以把 $container 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
-      }
-    }
-    const myMenuConf = {
-      key: 'myMenu',
-      factory() {
-        return new MyMenu()
-      }
-    }
-    E.Boot.registerMenu(myMenuConf)
-
-
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<p><br></p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          console.log(editor.getHtml())
-
-          // 选中文字
-          const selectionText = editor.getSelectionText()
-          document.getElementById('selected-length').innerHTML = selectionText.length
-          // 全部文字
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {
-        insertKeys: {
-          index: 0,
-          keys: ['myMenu'], // show menu in toolbar
-        }
-      }
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/extend-menu-select.html b/packages/editor/demo/extend-menu-select.html
deleted file mode 100644
index 9174c5880..000000000
--- a/packages/editor/demo/extend-menu-select.html
+++ /dev/null
@@ -1,127 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor extend select menu</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor extend select menu"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 500px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>;
-        Selected text length: <span id="selected-length"></span>;
-      </p>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG) // 切换语言
-
-
-
-    class MyMenuClass {
-      constructor() {
-        this.title = 'My Select Menu',
-          // this.iconSvg = '<svg>...</svg>'
-          this.tag = 'select'
-        this.width = 60
-      }
-
-      getOptions(editor) {
-        const options = [
-          { value: 'beijing', text: '北京', styleForRenderMenuList: { 'font-size': '32px', 'font-weight': 'bold' } },
-          { value: 'shanghai', text: '上海', selected: true },
-          { value: 'shenzhen', text: '深圳' }
-        ]
-        return options
-      }
-
-      getValue(editor) {
-        return 'shanghai' // 匹配 options 其中一个 value
-      }
-      isActive(editor) {
-        return false // or true
-      }
-      isDisabled(editor) {
-        return false // or true
-      }
-      exec(editor, value) {
-        editor.insertText(value) // value 即 this.getValue(editor) 的返回值
-        editor.insertText(' ')
-      }
-    }
-
-    const myMenuConf = {
-      key: 'myMenu',
-      factory() {
-        return new MyMenuClass()
-      }
-    }
-    E.Boot.registerMenu(myMenuConf)
-
-
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<p><br></p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          console.log(editor.getHtml())
-
-          // 选中文字
-          const selectionText = editor.getSelectionText()
-          document.getElementById('selected-length').innerHTML = selectionText.length
-          // 全部文字
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {
-        insertKeys: {
-          index: 0,
-          keys: ['myMenu'], // show menu in toolbar
-        }
-      }
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/extend-menu.html b/packages/editor/demo/extend-menu.html
deleted file mode 100644
index 2bfb23e3a..000000000
--- a/packages/editor/demo/extend-menu.html
+++ /dev/null
@@ -1,115 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor extend menu</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor extend menu"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 500px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>;
-        Selected text length: <span id="selected-length"></span>;
-      </p>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG) // 切换语言
-
-
-
-    // Extend menu
-    class MyMenu {
-      constructor() {
-        this.title = 'My menu'
-        // this.iconSvg = '<svg >...</svg>'
-        this.tag = 'button'
-      }
-      getValue(editor) {
-        return ' hello '
-      }
-      isActive(editor) {
-        return false // or true
-      }
-      isDisabled(editor) {
-        return false // or true
-      }
-      exec(editor, value) {
-        editor.insertText(value) // value 即 this.getValue(editor) 的返回值
-      }
-    }
-    const myMenuConf = {
-      key: 'myMenu',
-      factory() {
-        return new MyMenu()
-      }
-    }
-    E.Boot.registerMenu(myMenuConf)
-
-
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<p><br></p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          console.log(editor.getHtml())
-
-          // 选中文字
-          const selectionText = editor.getSelectionText()
-          document.getElementById('selected-length').innerHTML = selectionText.length
-          // 全部文字
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {
-        insertKeys: {
-          index: 0,
-          keys: ['myMenu'], // show menu in toolbar
-        }
-      }
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/get-html.html b/packages/editor/demo/get-html.html
deleted file mode 100644
index e46057f3a..000000000
--- a/packages/editor/demo/get-html.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor get HTML</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor get HTML"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 350px"></div>
-      </div>
-
-      <!-- 显示内容 -->
-      <div style="margin-top: 20px;">
-        <textarea id="editor-content-textarea" style="width: 100%; height: 100px; outline: none;" readonly></textarea>
-      </div>
-      <div id="editor-content-view" class="editor-content-view"></div>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<p>hello&nbsp;world</p><p><br></p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          const html = editor.getHtml()
-          document.getElementById('editor-content-view').innerHTML = html
-          document.getElementById('editor-content-textarea').value = html
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {}
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/huge-doc.html b/packages/editor/demo/huge-doc.html
deleted file mode 100644
index 3a0796b24..000000000
--- a/packages/editor/demo/huge-doc.html
+++ /dev/null
@@ -1,73 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor huge doc</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-  <script src="./js/huge-content.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor huge doc"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 700px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>
-      </p>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      content: window.HUGE_CONTENT,
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {}
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/index.html b/packages/editor/demo/index.html
deleted file mode 100644
index d23466b8b..000000000
--- a/packages/editor/demo/index.html
+++ /dev/null
@@ -1,79 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor default mode</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor default mode"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 500px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>;
-        Selected text length: <span id="selected-length"></span>;
-      </p>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<p><br></p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          console.log(editor.getHtml())
-
-          // 选中文字
-          const selectionText = editor.getSelectionText()
-          document.getElementById('selected-length').innerHTML = selectionText.length
-          // 全部文字
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {}
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/js/custom-elem.js b/packages/editor/demo/js/custom-elem.js
deleted file mode 100644
index f3e1ed67a..000000000
--- a/packages/editor/demo/js/custom-elem.js
+++ /dev/null
@@ -1,300 +0,0 @@
-/**
- * @description 自定义 elem
- * @author wangfupeng
- */
-
-// ------------------------------------------ native-shim start ------------------------------------------
-
-// 参考 https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js
-/**
- * @license
- * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
- * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
- * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
- * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
- * Code distributed by Google as part of the polymer project is also
- * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
- */
-
-/**
- * This shim allows elements written in, or compiled to, ES5 to work on native
- * implementations of Custom Elements v1. It sets new.target to the value of
- * this.constructor so that the native HTMLElement constructor can access the
- * current under-construction element's definition.
- */
-;(function () {
-  if (
-    // No Reflect, no classes, no need for shim because native custom elements
-    // require ES2015 classes or Reflect.
-    window.Reflect === undefined ||
-    window.customElements === undefined ||
-    // The webcomponentsjs custom elements polyfill doesn't require
-    // ES2015-compatible construction (`super()` or `Reflect.construct`).
-    window.customElements.polyfillWrapFlushCallback
-  ) {
-    return
-  }
-  const BuiltInHTMLElement = HTMLElement
-  /**
-   * With jscompiler's RECOMMENDED_FLAGS the function name will be optimized away.
-   * However, if we declare the function as a property on an object literal, and
-   * use quotes for the property name, then closure will leave that much intact,
-   * which is enough for the JS VM to correctly set Function.prototype.name.
-   */
-  const wrapperForTheName = {
-    HTMLElement: /** @this {!Object} */ function HTMLElement() {
-      return Reflect.construct(BuiltInHTMLElement, [], /** @type {!Function} */ this.constructor)
-    },
-  }
-  window.HTMLElement = wrapperForTheName['HTMLElement']
-  HTMLElement.prototype = BuiltInHTMLElement.prototype
-  HTMLElement.prototype.constructor = HTMLElement
-  Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement)
-})()
-// ------------------------------------------ native-shim end ------------------------------------------
-
-// ------------------------------------------ 顶部导航 start ------------------------------------------
-!(function () {
-  // 当前语言
-  const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-
-  // 自定义组件
-  class MyNav extends HTMLElement {
-    constructor() {
-      super()
-
-      const shadow = this.attachShadow({ mode: 'open' })
-      const document = shadow.ownerDocument
-
-      const style = document.createElement('style')
-      style.innerHTML = `
-      .container {
-        display: flex;
-        padding: 10px;
-        background-color: #4474c8;
-        color: #fff;
-      }
-      .container a {
-        color: #fff;
-        text-decoration: none;
-      }
-      .container h1 {
-        flex: 1;
-        margin: 0;
-        font-size: 26px;
-      }
-      .container .right-container {
-        width: 200px;
-        text-align: right;
-        line-height: 26px;
-      }
-    `
-      shadow.appendChild(style)
-
-      // 容器
-      const container = document.createElement('div')
-      container.className = 'container'
-
-      // 标题
-      const header = document.createElement('h1')
-      header.textContent = ''
-      this.header = header
-
-      // 右侧链接
-      const rightContainer = document.createElement('div')
-      rightContainer.className = 'right-container'
-      if (LANG === 'en') {
-        rightContainer.innerHTML = `
-        <a href="https://www.wangeditor.com/en/">Document</a>
-        &nbsp;
-        <a href="https://github.com/wangeditor-team/wangEditor/tree/master/packages/editor/demo">Source</a>
-      `
-      } else {
-        rightContainer.innerHTML = `
-        <a href="https://www.wangeditor.com/">文档</a>
-        &nbsp;
-        <a href="https://github.com/wangeditor-team/wangEditor/tree/master/packages/editor/demo">源码</a>
-      `
-      }
-
-      container.appendChild(header)
-      container.appendChild(rightContainer)
-
-      shadow.appendChild(container)
-    }
-
-    attributeChangedCallback(name, oldValue, newValue) {
-      if (name === 'title') {
-        if (oldValue == newValue) return
-        this.header.textContent = newValue
-      }
-    }
-  }
-  MyNav.observedAttributes = ['title']
-  window.customElements.define('demo-nav', MyNav)
-})()
-// ------------------------------------------ 顶部导航 end ------------------------------------------
-
-// ------------------------------------------ 左侧菜单 start ------------------------------------------
-// 菜单配置
-const MENU_CONF = [
-  {
-    'zh-CN': { text: '默认模式', link: './index.html' },
-    en: { text: 'Default mode', link: './index.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '简洁模式', link: './simple-mode.html' },
-    en: { text: 'Simple mode', link: './simple-mode.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '获取 HTML', link: './get-html.html' },
-    en: { text: 'Get HTML', link: './get-html.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '设置 HTML', link: './set-html.html' },
-    en: { text: 'Set HTML', link: './set-html.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '模拟腾讯文档', link: './like-qq-doc.html' },
-    en: { text: 'Like QQ doc', link: './like-qq-doc.html?lang=en' },
-  },
-  {
-    'zh-CN': {
-      text: '上传图片',
-      link: 'https://github.com/wangeditor-team/server',
-    },
-    en: {
-      text: 'Upload Image',
-      link: 'https://github.com/wangeditor-team/server',
-    },
-  },
-  {
-    'zh-CN': {
-      text: '上传视频',
-      link: 'https://github.com/wangeditor-team/server',
-    },
-    en: {
-      text: 'Upload Video',
-      link: 'https://github.com/wangeditor-team/server',
-    },
-  },
-  {
-    'zh-CN': { text: '代码高亮', link: './code-highlight.html' },
-    en: { text: 'Code highlight', link: './code-highlight.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '多个编辑器', link: './multi-editor.html' },
-    en: { text: 'Multi editor', link: './multi-editor.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '标题目录', link: './catalog.html' },
-    en: { text: 'Catalog', link: './catalog.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: 'Max Length', link: './max-length.html' },
-    en: { text: 'Max Length', link: './max-length.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '大文件 10w 字', link: './huge-doc.html' },
-    en: { text: 'Huge doc', link: './huge-doc.html?lang=en' },
-  },
-  {
-    'zh-CN': {
-      text: 'Shadow DOM',
-      link: 'https://github.com/wangeditor-team/wangEditor/blob/master/packages/editor/examples/shadow-dom.html',
-    },
-    en: {
-      text: 'Shadow DOM',
-      link: 'https://github.com/wangeditor-team/wangEditor/blob/master/packages/editor/examples/shadow-dom.html',
-    },
-  },
-  {
-    'zh-CN': { text: '扩展菜单 Button', link: './extend-menu.html' },
-    en: { text: 'Extend Button menu', link: './extend-menu.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '扩展菜单 select', link: './extend-menu-select.html' },
-    en: { text: 'Extend select menu', link: './extend-menu-select.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '扩展菜单 dropPanel', link: './extend-menu-drop-panel.html' },
-    en: { text: 'Extend dropPanel menu', link: './extend-menu-drop-panel.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: '扩展菜单 modal', link: './extend-menu-modal.html' },
-    en: { text: 'Extend modal menu', link: './extend-menu-modal.html?lang=en' },
-  },
-  {
-    'zh-CN': { text: 'Vue2 demo', link: 'https://www.wangeditor.com/v5/for-frame.html#vue2' },
-    en: { text: 'Vue2 demo', link: 'https://www.wangeditor.com/en/v5/for-frame.html#vue2' },
-  },
-  {
-    'zh-CN': { text: 'Vue3 demo', link: 'https://www.wangeditor.com/v5/for-frame.html#vue3' },
-    en: { text: 'Vue3 demo', link: 'https://www.wangeditor.com/en/v5/for-frame.html#vue3' },
-  },
-  {
-    'zh-CN': {
-      text: 'React demo',
-      link: 'https://www.wangeditor.com/v5/for-frame.html#react',
-    },
-    en: {
-      text: 'React demo',
-      link: 'https://www.wangeditor.com/en/v5/for-frame.html#react',
-    },
-  },
-  {
-    'zh-CN': {
-      text: 'Webpack demo',
-      link: 'https://github.com/wangfupeng1988/webpack-wangeditor-demo',
-    },
-    en: { text: 'Webpack demo', link: 'https://github.com/wangfupeng1988/webpack-wangeditor-demo' },
-  },
-]
-
-!(function () {
-  // 当前语言
-  const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-
-  // 自定义组件
-  class MyMenu extends HTMLElement {
-    constructor() {
-      super()
-
-      const shadow = this.attachShadow({ mode: 'open' })
-      const document = shadow.ownerDocument
-
-      const style = document.createElement('style')
-      style.innerHTML = `
-        ul {
-          list-style-type: none;
-          margin: 0;
-          padding: 0;
-        }
-        ul li {
-          margin: 0;
-          margin-bottom: 18px;
-        }
-        a {
-          color: #333;
-          text-decoration: none;
-        }
-        a:hover {
-          text-decoration: underline;
-        }
-      `
-      shadow.appendChild(style)
-
-      const container = document.createElement('div')
-      container.innerHTML = `<ul>
-        ${MENU_CONF.map(item => {
-          const { link, text } = item[LANG]
-          return `<li><a href="${link}">${text}</a></li>`
-        }).join('')}
-      </ul>`
-
-      shadow.appendChild(container)
-    }
-  }
-  window.customElements.define('demo-menu', MyMenu)
-})()
-// ------------------------------------------ 左侧菜单 end ------------------------------------------
diff --git a/packages/editor/demo/js/huge-content.js b/packages/editor/demo/js/huge-content.js
deleted file mode 100644
index 54c25608e..000000000
--- a/packages/editor/demo/js/huge-content.js
+++ /dev/null
@@ -1,46 +0,0 @@
-;(function () {
-  function deepClone(obj) {
-    const str = JSON.stringify(obj)
-    return JSON.parse(str)
-  }
-
-  const header = {
-    type: 'header1',
-    children: [
-      {
-        text: '水浒传简介',
-      },
-    ],
-  }
-  const text1 =
-    '全书通过描写梁山好汉反抗欺压、水泊梁山壮大和受宋朝招安,以及受招安后为宋朝征战,最终消亡的宏大故事,艺术地反映了中国历史上宋江起义从发生、发展直至失败的全过程,深刻揭示了起义的社会根源,满腔热情地歌颂了起义英雄的反抗斗争和他们的社会理想,也具体揭示了起义失败的内在历史原因。'
-  const text2 =
-    '《水浒传》是中国古典四大名著之一,问世后,在社会上产生了巨大的影响,成了后世中国小说创作的典范。《水浒传》是中国历史上最早用白话文写成的章回小说之一,流传极广,脍炙人口;同时也是汉语言文学中具备史诗特征的作品之一,对中国乃至东亚的叙事文学都有深远的影响。'
-  const p1 = {
-    type: 'paragraph',
-    children: [{ text: text1 }],
-  }
-  const p2 = {
-    type: 'paragraph',
-    children: [{ text: text2 }],
-  }
-  // const code = {
-  //   type: 'pre',
-  //   children: [
-  //     {
-  //       type: 'code',
-  //       language: 'javascript',
-  //       children: [{ text: 'const a = 100;' }],
-  //     },
-  //   ],
-  // }
-
-  // 拼接大文件
-  window.HUGE_CONTENT = []
-  for (let i = 0; i < 370; i++) {
-    window.HUGE_CONTENT.push(deepClone(header))
-    window.HUGE_CONTENT.push(deepClone(p1))
-    window.HUGE_CONTENT.push(deepClone(p2))
-    // window.HUGE_CONTENT.push(deepClone(code))
-  }
-})()
diff --git a/packages/editor/demo/like-qq-doc.html b/packages/editor/demo/like-qq-doc.html
deleted file mode 100644
index 50ee9e8e7..000000000
--- a/packages/editor/demo/like-qq-doc.html
+++ /dev/null
@@ -1,136 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor 仿腾讯文档</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <style>
-    html,
-    body {
-      background-color: #fff;
-      height: 100%;
-      overflow: hidden;
-      color: #333;
-    }
-
-    #top-container {
-      border-bottom: 1px solid #e8e8e8;
-      padding-left: 30px;
-    }
-
-    #editor-toolbar {
-      width: 1350px;
-      background-color: #FCFCFC;
-      margin: 0 auto;
-    }
-
-    #content {
-      height: calc(100% - 40px);
-      background-color: rgb(245, 245, 245);
-      overflow-y: auto;
-      position: relative;
-    }
-
-    #editor-container {
-      width: 850px;
-      margin: 30px auto 150px auto;
-      background-color: #fff;
-      padding: 20px 50px 50px 50px;
-      border: 1px solid #e8e8e8;
-      box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
-    }
-
-    #title-container {
-      padding: 20px 0;
-      border-bottom: 1px solid #e8e8e8;
-    }
-
-    #title-container input {
-      font-size: 30px;
-      border: 0;
-      outline: none;
-      width: 100%;
-      line-height: 1;
-    }
-
-    #editor-text-area {
-      min-height: 900px;
-      margin-top: 20px;
-    }
-  </style>
-</head>
-
-<body>
-  <div id="top-container">
-    <p>
-      <a href="./index.html">&lt;&lt; 返回 Back to demo</a>
-    </p>
-  </div>
-  <div style="border-bottom: 1px solid #e8e8e8;">
-    <div id="editor-toolbar"></div>
-  </div>
-  <div id="content">
-    <div id="editor-container">
-      <div id="title-container">
-        <input placeholder="Page title...">
-      </div>
-      <div id="editor-text-area"></div>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    const editorConfig = {
-      placeholder: 'Type here...',
-      scroll: false, // 禁止编辑器滚动
-      MENU_CONF: {
-        uploadImage: {
-          fieldName: 'your-fileName',
-          base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-        }
-      },
-      onChange(editor) {
-        console.log(editor.getHtml())
-      }
-    }
-
-    // 先创建 editor
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      content: [],
-      // html: '',
-      config: editorConfig
-    })
-
-    // 创建 toolbar
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {
-        excludeKeys: 'fullScreen',
-      }
-    })
-
-    // 点击空白处 focus 编辑器
-    document.getElementById('editor-text-area').addEventListener('click', e => {
-      if (e.target.id === 'editor-text-area') {
-        editor.blur()
-        editor.focus(true) // focus 到末尾
-      }
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/max-length.html b/packages/editor/demo/max-length.html
deleted file mode 100644
index 9aec6a6ab..000000000
--- a/packages/editor/demo/max-length.html
+++ /dev/null
@@ -1,86 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor maxlength</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor maxlength"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 500px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>;
-        Selected text length: <span id="selected-length"></span>;
-      </p>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    // 定义最大长度
-    const MAX_LENGTH = 30
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: `<p>MaxLength: ${MAX_LENGTH}</p><p><br></p>`,
-      config: {
-        placeholder: 'Type here...',
-        maxLength: MAX_LENGTH,
-        onMaxLength(editor) {
-          alert('Trigger maxlength callback')
-        },
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          console.log(editor.getHtml())
-
-          // 选中文字
-          const selectionText = editor.getSelectionText()
-          document.getElementById('selected-length').innerHTML = selectionText.length
-          // 全部文字
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {}
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/multi-editor.html b/packages/editor/demo/multi-editor.html
deleted file mode 100644
index b42f4ed4b..000000000
--- a/packages/editor/demo/multi-editor.html
+++ /dev/null
@@ -1,106 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor multi editor</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor multi editor"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-
-      <div style="display: flex;">
-        <div style="flex: 1">
-          <div style="border: 1px solid #ccc; margin-right: 5px;">
-            <div id="editor-toolbar-1" style="border-bottom: 1px solid #ccc;"></div>
-            <div id="editor-text-area-1" style="height: 400px;"></div>
-          </div>
-          <div id="content-view-1" class="editor-content-view"></div>
-        </div>
-        <div style="flex: 1">
-          <div style="border: 1px solid #ccc; margin-left: 5px;">
-            <div id="editor-toolbar-2" style="border-bottom: 1px solid #ccc;"></div>
-            <div id="editor-text-area-2" style="height: 400px;"></div>
-          </div>
-          <div id="content-view-2" class="editor-content-view"></div>
-        </div>
-      </div>
-
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    // 第一个编辑器
-    const editor1 = E.createEditor({
-      selector: '#editor-text-area-1',
-      config: {
-        placeholder: 'Type here...',
-        autoFocus: false,
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-file-name1',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          document.getElementById('content-view-1').innerHTML = editor.getHtml()
-        }
-      },
-      html: '<p>editor1</p><p><br></p>'
-    })
-    const toolbar1 = E.createToolbar({
-      editor: editor1,
-      selector: '#editor-toolbar-1',
-      config: {}
-    })
-
-    // 第二个编辑器
-    const editor2 = E.createEditor({
-      selector: '#editor-text-area-2',
-      config: {
-        placeholder: 'Type here...',
-        autoFocus: false,
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-file-name2',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          document.getElementById('content-view-2').innerHTML = editor.getHtml()
-        }
-      },
-      html: '<p>editor2</p><p><br></p>',
-      mode: 'simple'
-    })
-    const toolbar2 = E.createToolbar({
-      editor: editor2,
-      selector: '#editor-toolbar-2',
-      config: {}
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/set-html.html b/packages/editor/demo/set-html.html
deleted file mode 100644
index 8ef582053..000000000
--- a/packages/editor/demo/set-html.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor set HTML</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor set HTML"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <textarea id="editor-content-textarea" style="width: 100%; height: 100px; outline: none;"></textarea>
-      <div style="margin-top: 10px;">
-        <button id="btn-set-html">Set HTML</button>
-      </div>
-
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc; margin-top: 20px;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 350px"></div>
-      </div>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html: '<p>编辑器创建时的默认内容。</p><p>Default content set when editor created.</p><p><br></p>',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange(editor) {
-          const html = editor.getHtml()
-          console.log(html)
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {}
-    })
-
-    // textarea 初始化值
-    const textarea = document.getElementById('editor-content-textarea')
-    textarea.value = '<p>wangEditor 只识别 editor.getHtml() 生成的 html 格式,不可以随意自定义 html 代码(html 格式太灵活了,不会全部兼容)</p>\n<p>wangEditor can only understand the HTML format from editor.getHtml() , but not all HTML formats.</p>\n<p><br></p>'
-
-    // Set HTML
-    document.getElementById('btn-set-html').addEventListener('click', () => {
-      editor.setHtml(textarea.value)
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/demo/simple-mode.html b/packages/editor/demo/simple-mode.html
deleted file mode 100644
index b86fe37ed..000000000
--- a/packages/editor/demo/simple-mode.html
+++ /dev/null
@@ -1,86 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor simple mode</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <!-- <link href="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> -->
-  <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
-  <link href="./css/layout.css" rel="stylesheet">
-
-  <script src="./js/custom-elem.js"></script>
-</head>
-
-<body>
-  <demo-nav title="wangEditor simple mode"></demo-nav>
-  <div class="page-container">
-    <div class="page-left">
-      <demo-menu></demo-menu>
-    </div>
-    <div class="page-right">
-      <!-- 编辑器 DOM -->
-      <div style="border: 1px solid #ccc;">
-        <div id="editor-toolbar" style="border-bottom: 1px solid #ccc;"></div>
-        <div id="editor-text-area" style="height: 500px"></div>
-      </div>
-
-      <!-- 内容状态 -->
-      <p style="background-color: #f1f1f1;">
-        Text length: <span id="total-length"></span>;
-        Selected text length: <span id="selected-length"></span>;
-      </p>
-    </div>
-  </div>
-
-  <!-- <script src="https://cdn.jsdelivr.net/npm/@wangeditor/editor@latest/dist/index.min.js"></script> -->
-  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // 切换语言
-    const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
-    E.i18nChangeLanguage(LANG)
-
-    // 默认内容
-    let html = `<h1>简洁模式:</h1><ol><li>简化工具栏菜单</li><li>取消选中文字的悬浮菜单</li></ol><p><br></p>`
-    if (LANG === 'en') html = `<h1>Simple&nbsp;mode.</h1><ol><li>Simplify&nbsp;toolbar&nbsp;menus</li><li>Hide&nbsp;hover-bar&nbsp;when&nbsp;selected&nbsp;text</li></ol><p><br></p>`
-
-    window.editor = E.createEditor({
-      selector: '#editor-text-area',
-      html,
-      mode: 'simple',
-      config: {
-        placeholder: 'Type here...',
-        MENU_CONF: {
-          uploadImage: {
-            fieldName: 'your-fileName',
-            base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-          }
-        },
-        onChange() {
-          console.log(editor.getHtml())
-
-          // 选中文字
-          const selectionText = editor.getSelectionText()
-          document.getElementById('selected-length').innerHTML = selectionText.length
-          // 全部文字
-          // 全部文字
-          const text = editor.getText().replace(/\n|\r/mg, '')
-          document.getElementById('total-length').innerHTML = text.length
-        }
-      }
-    })
-
-    window.toolbar = E.createToolbar({
-      editor,
-      mode: 'simple',
-      selector: '#editor-toolbar',
-      config: {}
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/README.md b/packages/editor/examples/README.md
deleted file mode 100644
index 6edc6ee2a..000000000
--- a/packages/editor/examples/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# examples
-
-- 本地测试
-- 提交 `master` 会发布到测试机
diff --git a/packages/editor/examples/batch-destroy.html b/packages/editor/examples/batch-destroy.html
deleted file mode 100644
index fc833a861..000000000
--- a/packages/editor/examples/batch-destroy.html
+++ /dev/null
@@ -1,91 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>destroy demo</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-
-<body>
-  <p>执行大量循环,频繁执行 创建/销毁 ,监控内存是否明显增加</p>
-  <p>循环次数 <input id="input-num" type="number" value="100" /> <button id="btn-go">开始</button></p>
-  <p>可使用 Chrome devTools 的 Performance 和 Memory 工具来检测 js 内存</p>
-  <!-- 编辑器 -->
-  <div style="width: 950px; margin: 0 auto;">
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-</body>
-
-<script src="js/init-content.js"></script>
-<script src="../dist/index.js"></script>
-<script>
-  (function () {
-    const E = window.wangEditor
-
-    document.getElementById('btn-go')?.addEventListener('click', () => {
-      let editor, toolbar
-
-      // ----------------------------- editor config -----------------------------
-      const editorConfig = {}
-      editorConfig.placeholder = '请输入内容'
-      editorConfig.onCreated = (editor) => {
-        console.log('on created', editor.id)
-      }
-      editorConfig.onDestroyed = (editor) => {
-        console.log('on destroyed', editor.id)
-      }
-
-      // ----------------------------- 获取 循环次数 -----------------------------
-      const maxNum = parseInt(document.getElementById('input-num').value)
-      if (!maxNum) {
-        console.error('循环次数,值非法 ', maxNum)
-        return
-      }
-      if (maxNum >= 1000) {
-        console.error('循环次数太多,耗时会比较长!')
-        return
-      }
-
-      // ----------------------------- 开始循环 -----------------------------
-      console.log('go...')
-      let num = 0
-      const intervalId = setInterval(() => {
-        if (num >= maxNum) {
-          clearInterval(intervalId)
-          return
-        }
-
-        if (editor == null) {
-          console.log('createCore', num)
-
-          // 先创建 editor 再创建 toolbar
-          editor = E.createEditor({
-            selector: '#editor-text-area',
-            config: editorConfig,
-            content: window.content1
-          })
-          toolbar = E.createToolbar({
-            editor,
-            selector: '#editor-toolbar'
-          })
-        } else {
-          console.log('destroyCore', num)
-          editor.destroy()
-          editor = null
-          num++ // 计数
-        }
-      }, 100)
-      console.log('end...')
-    })
-  })()
-</script>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/check.html b/packages/editor/examples/check.html
deleted file mode 100644
index 4abca8c40..000000000
--- a/packages/editor/examples/check.html
+++ /dev/null
@@ -1,222 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>check demo</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>视频、图片等资源校验,自定义 alert,XSS 预防</p>
-
-  <!-- 编辑器 -->
-  <div style="width: 950px; margin: 0 auto;">
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const editorConfig = { MENU_CONF: {} }
-    editorConfig.placeholder = '请输入内容'
-    
-    editorConfig.customAlert = (info, type) => {
-      alert(`customAlert: \n${type}:\n${info}`)
-    }
-
-    editorConfig.MENU_CONF['uploadImage'] = {
-      fieldName: 'your-fileName',
-      base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-    }
-
-    editorConfig.MENU_CONF['insertImage'] = {
-      onInsertedImage(imageNode) {
-        console.log('inserted image', imageNode)
-      },
-      checkImage(src, alt, url) {
-        if (src.indexOf('http') !== 0) {
-          return '图片网址必须以 http/https 开头'
-        }
-        return true
-      },
-
-      // // 异步检查
-      // async checkImage(src, alt, url) {
-      //   return new Promise((resolve, reject) => {
-      //     setTimeout(() => {
-      //       if (src.indexOf('http') !== 0) {
-      //         resolve('图片网址必须以 http/https 开头')
-      //         return
-      //       }
-      //       resolve(true)
-      //     }, 1000)
-      //   })
-      // },
-
-      parseImageSrc(src) {
-        return src + '#123'
-      },
-
-      // // 异步转换
-      // async parseImageSrc(src) {
-      //   return new Promise((resolve, reject) => {
-      //     setTimeout(() => {
-      //       resolve(src + '#abc')
-      //     }, 1000)
-      //   })
-      // },
-    }
-    editorConfig.MENU_CONF['editImage'] = {
-      onUpdatedImage(imageNode) {
-        console.log('updated image', imageNode)
-      },
-      checkImage(src, alt, url) {
-        if (src.indexOf('http') !== 0) {
-          return '图片网址必须以 http/https 开头'
-        }
-        return true
-      },
-
-      // // 异步检查
-      // async checkImage(src, alt, url) {
-      //   return new Promise((resolve, reject) => {
-      //     setTimeout(() => {
-      //       if (src.indexOf('http') !== 0) {
-      //         resolve('图片网址必须以 http/https 开头')
-      //         return
-      //       }
-      //       resolve(true)
-      //     }, 1000)
-      //   })
-      // },
-
-      parseImageSrc(src) {
-        return src + '#123'
-      },
-    }
-    editorConfig.MENU_CONF['insertLink'] = {
-      checkLink(text, url) {
-        console.log('check insert link - ', text, url)
-
-        if (url.indexOf('http') !== 0) {
-          return '链接必须以 http/https 开头'
-        }
-        return true
-      },
-
-      // // 异步检查
-      // async checkLink(text, url) {
-      //   console.log('check insert link - ', text, url)
-
-      //   return new Promise((resolve, reject) => {
-      //     setTimeout(() => {
-      //       if (url.indexOf('http') !== 0) {
-      //         resolve('链接必须以 http/https 开头')
-      //         return
-      //       }
-      //       resolve(true)
-      //     }, 1000)
-      //   })
-      // },
-
-      parseLinkUrl(url) {
-        return url + '#123'
-      },
-
-      // // 异步转换
-      // async parseLinkUrl(url) {
-      //   console.log('parse insert link - ', url)
-
-      //   return new Promise((resolve, reject) => {
-      //     setTimeout(() => {
-      //       resolve(url + '#123')
-      //     }, 1000)
-      //   })
-      // },
-    }
-    editorConfig.MENU_CONF['editLink'] = {
-      checkLink(text, url) {
-        if (url.indexOf('http') !== 0) {
-          return '链接必须以 http/https 开头'
-        }
-        return true
-      },
-
-      // // 异步检查
-      // async checkLink(text, url) {
-      //   console.log('check insert link - ', text, url)
-
-      //   return new Promise((resolve, reject) => {
-      //     setTimeout(() => {
-      //       if (url.indexOf('http') !== 0) {
-      //         resolve('链接必须以 http/https 开头')
-      //         return
-      //       }
-      //       resolve(true)
-      //     }, 1000)
-      //   })
-      // },
-
-      parseLinkUrl(url) {
-        return url + '#123'
-      },
-    }
-    editorConfig.MENU_CONF['insertVideo'] = {
-      onInsertedVideo(videoNode) {
-        console.log('inserted video', videoNode)
-      },
-
-      checkVideo(src, poster) {
-        console.log('video src', src)
-        console.log('video poster', poster)
-        if (src.indexOf('http') !== 0) {
-          return '视频地址必须以 http/https 开头'
-        }
-        return true
-      },
-
-      // // 异步检查
-      // async checkVideo(src) {
-      //   return new Promise((resolve, reject) => {
-      //     setTimeout(() => {
-      //       if (src.indexOf('http') !== 0) {
-      //         resolve('视频地址必须以 http/https 开头')
-      //         return
-      //       }
-      //       resolve(true)
-      //     }, 1000)
-      //   })
-      // },
-
-      // 也支持 promise
-      parseVideoSrc(src) {
-        if (src.includes('.bilibili.com')) {
-          // 转换 bilibili url 为 iframe
-          const arr = location.pathname.split('/')
-          const vid = arr[arr.length - 1]
-          return `<iframe src="//player.bilibili.com/player.html?aid=421814407&bvid=${vid}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>`
-        }
-        return src
-      }
-    }
-
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      content: window.content1,
-      config: editorConfig
-    })
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/code-highlight.html b/packages/editor/examples/code-highlight.html
deleted file mode 100644
index c1f9fcf91..000000000
--- a/packages/editor/examples/code-highlight.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>code-highlight demo</title>
-
-  <!-- 引入 prism css -->
-  <link href="https://unpkg.com/prismjs@latest/themes/prism.css" rel="stylesheet">
-</head>
-<body>
-  <p>由 wangEditor 生成的代码,可支持代码高亮显示。使用 <a href="https://prismjs.com/" target="_blank">prism.js</a> ,支持多主题</p>
-  <p>【注意】异步设置 html 内容时,需要执行 <code>Prism.highlightAll()</code> <button id="btn-test">测试一下</button></p>
-
-  <div>
-    <p>javascript</p>
-    <pre>
-      <code id="code1" class="language-javascript">const a = 100;
-      function fn(x) { return x + 10 };
-      // 注释
-      </code>
-    </pre>
-
-    <p>html</p>
-    <pre>
-      <code id="code2" class="language-html">&lt;div&gt;text1&lt;/div&gt;</code>
-    </pre>
-  </div>
-
-  <!-- 引入 prism js -->
-  <script src="https://unpkg.com/prismjs@latest/prism.js"></script>
-  <script src="https://unpkg.com/prismjs@latest/components/prism-core.js"></script>
-  <script src="https://unpkg.com/prismjs@latest/plugins/autoloader/prism-autoloader.js"></script>
-  <script>
-    document.getElementById('btn-test').addEventListener('click', () => {
-      document.getElementById('code1').innerHTML = 'const b = 200;\nfunction fn(y) { return y + 20 };\n// comment'
-      document.getElementById('code2').innerHTML = '&lt;p&gt;text2&lt;/p&gt;'
-      Prism.highlightAll()
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/content-to-html.html b/packages/editor/examples/content-to-html.html
deleted file mode 100644
index 7cd3fe779..000000000
--- a/packages/editor/examples/content-to-html.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>content to html</title>
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>content to html</p>
-
-  <div style="margin-top: 10px;">
-    <textarea id="text-content" style="width: 100%; height: 300px;">[
-  {
-    "type": "paragraph",
-    "children": [
-      {
-        "text": "你好"
-      }
-    ]
-  }
-]</textarea>
-  </div>
-
-  <button id="btn-convert">convert</button>
-
-  <div style="margin-top: 20px;">
-    <textarea id="text-html" readonly style="width: 100%; height: 300px;"></textarea>
-  </div>
-
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    document.getElementById('btn-convert').addEventListener('click', () => {
-      const contentStr = document.getElementById('text-content').value
-      const content = JSON.parse(contentStr)
-      const editor = E.createEditor({ content })
-      document.getElementById('text-html').innerHTML = editor.getHtml()
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/css/editor.css b/packages/editor/examples/css/editor.css
deleted file mode 100644
index 857edf045..000000000
--- a/packages/editor/examples/css/editor.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
-  margin: 0 10px;
-}
-
-.editor-toolbar {
-  border: 1px solid #ccc;
-}
-
-.editor-text-area {
-  border: 1px solid #ccc;
-  border-top: 0;
-  height: 400px;
-}
\ No newline at end of file
diff --git a/packages/editor/examples/css/view.css b/packages/editor/examples/css/view.css
deleted file mode 100644
index 1dd8ed4e4..000000000
--- a/packages/editor/examples/css/view.css
+++ /dev/null
@@ -1,42 +0,0 @@
-.editor-content-view {
-  border: 1px solid #ccc;
-  padding: 10px;
-  margin-top: 30px;
-  overflow-x: auto;
-}
-
-.editor-content-view p,
-.editor-content-view li {
-  white-space: pre-wrap; /* 保留空格 */
-}
-
-.editor-content-view blockquote {
-  border-left: 8px solid #d0e5f2;
-  padding: 10px 10px;
-  margin: 10px 0;
-  background-color: #f1f1f1;
-}
-
-.editor-content-view code {
-  font-family: monospace;
-  background-color: #eee;
-  padding: 3px;
-  border-radius: 3px;
-}
-.editor-content-view pre>code {
-  display: block;
-  padding: 10px;
-}
-
-.editor-content-view table {
-  border-collapse: collapse;
-}
-.editor-content-view td,
-.editor-content-view th {
-  border: 1px solid #ccc;
-  min-width: 50px;
-  height: 20px;
-}
-.editor-content-view th {
-  background-color: #f1f1f1;
-}
\ No newline at end of file
diff --git a/packages/editor/examples/default-mode.html b/packages/editor/examples/default-mode.html
deleted file mode 100644
index bb577e788..000000000
--- a/packages/editor/examples/default-mode.html
+++ /dev/null
@@ -1,138 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<meta charset="UTF-8">
-<meta http-equiv="X-UA-Compatible" content="IE=edge">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<title>default mode</title>
-<link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-<link href="./css/view.css" rel="stylesheet">
-<link href="./css/editor.css" rel="stylesheet">
-
-<link href="../dist/css/style.css" rel="stylesheet">
-</head>
-
-<body>
-  <div style="width: 1000px; margin: 0 auto;">
-    <p>
-      <button id="btn-create">create editor</button>
-      <button id="btn-toggle-enable">disable/enable</button>
-      <button id="btn-destroy">destroy editor</button>
-    </p>
-  
-    <!-- 编辑器 -->
-    <div>
-      <div id="editor-toolbar" class="editor-toolbar"></div>
-      <div id="editor-text-area" class="editor-text-area"></div>
-    </div>
-  
-    <!-- 内容状态 -->
-    <p style="background-color: #f1f1f1;">
-      当前文字数量:<span id="total-length"></span>;
-      选中文字数量:<span id="selected-length"></span>;
-      选中文字:"<span id="selected-text"></span>"
-    </p>
-  
-    <!-- 显示内容 -->
-    <div id="editor-content-view" class="editor-content-view"></div>
-  </div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js?t=14"></script>
-  <script>
-    const E = window.wangEditor
-
-    const editorConfig = { MENU_CONF: {} }
-    // editorConfig.autoFocus = false
-    // editorConfig.readOnly = true
-    // editorConfig.scroll = false
-    editorConfig.placeholder = '请输入内容'
-    editorConfig.MENU_CONF['uploadImage'] = {
-      fieldName: 'your-fileName',
-      base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-    }
-    console.log('测试上传图片,请使用 upload-image.html')
-    editorConfig.onCreated = (editor) => {
-      console.log('on created', editor)
-    }
-    editorConfig.onChange = (editor) => {
-      // console.log(editor.children)
-
-      const html = editor.getHtml()
-      document.getElementById('editor-content-view').innerHTML = html
-
-      // 选中文字
-      const selectionText = editor.getSelectionText()
-      document.getElementById('selected-text').innerHTML = selectionText
-      document.getElementById('selected-length').innerHTML = selectionText.length
-      // 全部文字
-      document.getElementById('total-length').innerHTML = editor.getText().length
-
-      // // isSelectedAll
-      // console.log('isSelectedAll', editor.isSelectedAll())
-    }
-    editorConfig.onDestroyed = (editor) => {
-      console.log('on destroyed', editor)
-    }
-    editorConfig.onFocus = (editor) => {
-      console.log('onFocus', editor.isFocused())
-    }
-    editorConfig.onBlur = (editor) => {
-      console.log('onBlur', editor.isFocused())
-    }
-
-    // editorConfig.customPaste = (editor, event) => {
-    //   console.log('customPage')
-    //   // editor.insertText('xxx------') // 同步
-    //   setTimeout(() => {
-    //     editor.insertText('yyy------') // 异步
-    //   }, 1000)
-    //   return false // 阻止默认粘贴,自定义实现粘贴
-
-    //   // return true // 执行默认粘贴
-    // }
-
-    const toolbarConfig = {
-      // excludeKeys: ['uploadVideo'],
-    }
-
-    // create
-    let editor, toolbar
-    document.getElementById('btn-create').addEventListener('click', () => {
-      editor = E.createEditor({
-        selector: '#editor-text-area',
-        // selector: document.getElementById('editor-text-area'),
-        content: window.content1,
-        config: editorConfig
-      })
-
-      toolbar = E.createToolbar({
-        editor,
-        selector: '#editor-toolbar',
-        // selector: document.getElementById('editor-toolbar')
-        config: toolbarConfig
-      })
-    })
-    console.log(`如果页面没有编辑器,点击 'create' 按钮创建`)
-
-    // toggle enable
-    document.getElementById('btn-toggle-enable').addEventListener('mousedown', (e) => {
-      // 使用 mousedown ,且 preventDefault ,否则触发编辑器 blur ,导致无法正确 focus
-      // TODO 文档中说明(以及其他的 editState)
-      e.preventDefault()
-
-      if (editor.getConfig().readOnly) {
-        editor.enable()
-      } else {
-        editor.disable()
-      }
-    })
-
-    // destroy
-    document.getElementById('btn-destroy').addEventListener('click', () => {
-      editor.destroy()
-      editor = undefined
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/dom7-demo.html b/packages/editor/examples/dom7-demo.html
deleted file mode 100644
index 7038c5a79..000000000
--- a/packages/editor/examples/dom7-demo.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>dom7 demo</title>
-</head>
-<body>
-  <p>dom7 demo</p>
-
-  <script src="https://unpkg.com/dom7@latest/dom7.js"></script>
-  <script>
-    const $text = Dom7('<span data-a><b>hello</b></span>')
-    const $p = Dom7('<p style="line-height: 2.5; color: red;"><span>行高文字 line-height</span></p>')
-    // Dom7('body').append(p)
-
-    const tableStr = `
-      <table border="0" width="100%" cellpadding="0" cellspacing="0"><tbody><tr><th></th><th></th><th></th><th></th><th></th></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table>
-    `
-    const $table = Dom7(tableStr)
-    const $tbody = $table.find('tbody')
-    const $tr = $table.find('tr')
-    $table.append($tr)
-    $tbody.remove()
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/headers.html b/packages/editor/examples/headers.html
deleted file mode 100644
index f012e9d71..000000000
--- a/packages/editor/examples/headers.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>headers demo</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-
-<body>
-  <p id="p1">获取标题、滚动到标题</p>
-
-  <!-- 编辑器 -->
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <!-- 显示 headers -->
-  <div style="margin-top: 20px;">
-    <div style="margin-bottom: 10px;">
-      <input id="input-id" placeholder="header id" />
-      <button id="btn-scroll-to">scrollTo</button>
-    </div>
-    <textarea readonly id="text-headers" style="width: 800px; height: 400px;"></textarea>
-  </div>
-
-  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const editorConfig = {}
-    editorConfig.onCreated = (editor) => {
-      console.log('on created', editor)
-    }
-    editorConfig.onChange = (editor) => {
-      // 获取并展示 headers
-      const headers = editor.getElemsByTypePrefix('header')
-      $('#text-headers').val(JSON.stringify(headers, null, 4))
-    }
-
-    // 先创建 editor ,再创建 toolbar
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      config: editorConfig,
-      content: window.content1
-    })
-    const toolbar =E.createToolbar({
-      editor,
-      selector: '#editor-toolbar'
-    })
-    
-    // scrollTo
-    const $inputId = $('#input-id')
-    $('#btn-scroll-to').on('click', () => {
-      const id = $inputId.val()
-      editor.scrollToElem(id)
-      $inputId.val('')
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/huge-doc.html b/packages/editor/examples/huge-doc.html
deleted file mode 100644
index 418c453cc..000000000
--- a/packages/editor/examples/huge-doc.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>huge doc</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>huge doc 大文件</p>
-
-  <!-- 编辑器 -->
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area" style="height: 800px;"></div>
-  </div>
-
-  <!-- 内容状态 -->
-  <p style="background-color: #f1f1f1;">
-    当前文字数量:<span id="total-length"></span>;
-    选中文字数量:<span id="selected-length"></span>;
-    选中文字:"<span id="selected-text"></span>"
-  </p>
-
-  <script src="./js/huge-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const editorConfig = { MENU_CONF: {} }
-    editorConfig.placeholder = '请输入内容'
-    editorConfig.onChange = (editor) => {
-      // 选中文字
-      const selectionText = editor.getSelectionText()
-      document.getElementById('selected-text').innerHTML = selectionText
-      document.getElementById('selected-length').innerHTML = selectionText.length
-      // 全部文字
-      document.getElementById('total-length').innerHTML = editor.getText().length
-    }
-
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      content: window.content2,
-      config: editorConfig
-    })
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/i18n.html b/packages/editor/examples/i18n.html
deleted file mode 100644
index e0bacd0d9..000000000
--- a/packages/editor/examples/i18n.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>i18n</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>i18n demo</p>
-
-  <!-- 编辑器 -->
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // // 添加新语言,如日语 ja
-    // E.i18nAddResources('ja', {
-    //     // 标题
-    //     header: {
-    //         title: 'ヘッダー',
-    //         text: 'テキスト',
-    //     },
-    //     // ... 其他语言词汇,下文说明 ...
-    // })
-    // // 切换为日语 ja
-    // E.i18nChangeLanguage('ja')
-
-    // 切换语言 'en' 'zh-CN'
-    E.i18nChangeLanguage('en')
-
-    // 使用多语言
-    console.log('使用多语言', E.t('editor.image'))
-
-    // editor 配置
-    const editorConfig = {}
-    editorConfig.placeholder = '请输入内容...'
-
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      config: editorConfig,
-      content: window.content1,
-    })
-
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {},
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/index.html b/packages/editor/examples/index.html
deleted file mode 100644
index ea9d17e8e..000000000
--- a/packages/editor/examples/index.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>wangEditor examples</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <style>
-    body {
-      margin: 20px;
-    }
-    ul {
-      padding-left: 20px;
-    }
-    li {
-      margin: 10px 0;
-    }
-  </style>
-</head>
-<body>
-  <h1>wangEditor examples</h1>
-  <ul>
-    <li><a href="./default-mode.html">Default mode 默认模式</a></li>
-    <li><a href="./simple-mode.html">Simple mode 简洁模式</a></li>
-    <li><a href="./parse-html.html">Parse html 回显使用 html</a></li>
-    <li><a href="./menu.html">Menu config 菜单配置</a></li>
-    <li><a href="./like-yuque.html">Like QQ Doc 模仿腾讯文档编辑器</a></li>
-    <li><a href="./simple-mode.html">Sync to textarea 同步到 textarea</a></li>
-    <li><a href="./maxlength.html">Maxlength</a></li>
-    <li><a href="./upload-image.html">Upload images 上传图片</a></li>
-    <li><a href="./upload-video.html">Upload videos 上传视频</a></li>
-    <li><a href="./check.html">Check callback and custom alert 资源校验、回调、自定义 alert</a></li>
-    <li><a href="./multi-editors.html">Multi editors 多个编辑器</a></li>
-    <li><a href="./headers.html">Get headers and scroll 获取标题/滚动到标题</a></li>
-    <li><a href="./huge-doc.html">Huge doc 大文件(几万个字)</a></li>
-    <li><a href="./modal-appendTo-body.html">Modal appendTo body</a></li>
-    <li><a href="./i18n.html">i18n 国际化</a></li>
-    <li><a href="./theme.html">Theme 主题</a></li>
-    <li><a href="./code-highlight.html">Code highlight 代码高亮</a></li>
-    <li><a href="./shadow-dom.html">Shadow DOM</a></li>
-    <li><a href="./batch-destroy.html">Batch destroy, test memory leak 批量销毁,测试内存泄漏</a></li>
-    <li><a href="./content-to-html.html">Content to html</a></li>
-    <li><a href="./new-menu.html">New menu 新注册菜单</a></li>
-  </ul>
-</body>
-</html>
diff --git a/packages/editor/examples/js/huge-content.js b/packages/editor/examples/js/huge-content.js
deleted file mode 100644
index 79f4d4397..000000000
--- a/packages/editor/examples/js/huge-content.js
+++ /dev/null
@@ -1,46 +0,0 @@
-;(function () {
-  function deepClone(obj) {
-    const str = JSON.stringify(obj)
-    return JSON.parse(str)
-  }
-
-  const header = {
-    type: 'header1',
-    children: [
-      {
-        text: '水浒传简介',
-      },
-    ],
-  }
-  const text1 =
-    '全书通过描写梁山好汉反抗欺压、水泊梁山壮大和受宋朝招安,以及受招安后为宋朝征战,最终消亡的宏大故事,艺术地反映了中国历史上宋江起义从发生、发展直至失败的全过程,深刻揭示了起义的社会根源,满腔热情地歌颂了起义英雄的反抗斗争和他们的社会理想,也具体揭示了起义失败的内在历史原因。'
-  const text2 =
-    '《水浒传》是中国古典四大名著之一,问世后,在社会上产生了巨大的影响,成了后世中国小说创作的典范。《水浒传》是中国历史上最早用白话文写成的章回小说之一,流传极广,脍炙人口;同时也是汉语言文学中具备史诗特征的作品之一,对中国乃至东亚的叙事文学都有深远的影响。'
-  const p1 = {
-    type: 'paragraph',
-    children: [{ text: text1 }],
-  }
-  const p2 = {
-    type: 'paragraph',
-    children: [{ text: text2 }],
-  }
-  // const code = {
-  //   type: 'pre',
-  //   children: [
-  //     {
-  //       type: 'code',
-  //       language: 'javascript',
-  //       children: [{ text: 'const a = 100;' }],
-  //     },
-  //   ],
-  // }
-
-  // 拼接大文件
-  window.content2 = []
-  for (let i = 0; i < 370; i++) {
-    window.content2.push(deepClone(header))
-    window.content2.push(deepClone(p1))
-    window.content2.push(deepClone(p2))
-    // window.content2.push(deepClone(code))
-  }
-})()
diff --git a/packages/editor/examples/js/init-content.js b/packages/editor/examples/js/init-content.js
deleted file mode 100644
index 04d3d5ff1..000000000
--- a/packages/editor/examples/js/init-content.js
+++ /dev/null
@@ -1,124 +0,0 @@
-/**
- * @description demo 页,初始化内容
- * @author wangfupeng
- */
-
-window.content1 = [
-  {
-    type: 'header1',
-    textAlign: 'center',
-    children: [
-      {
-        text: '一行标题',
-      },
-    ],
-  },
-
-  {
-    type: 'paragraph',
-    children: [
-      { text: 'hello world ~~~ ' },
-      {
-        type: 'link',
-        url: 'https://www.slatejs.org/examples/links',
-        children: [{ text: 'slate examples' }],
-      },
-      { text: '!' },
-    ],
-  },
-  {
-    type: 'pre',
-    children: [
-      {
-        type: 'code',
-        language: 'javascript',
-        children: [{ text: 'const a = 100;' }],
-      },
-    ],
-  },
-  {
-    type: 'paragraph',
-    children: [
-      { text: '图片' },
-      {
-        type: 'image',
-        src: 'https://www.wangeditor.com/imgs/logo.png',
-        children: [{ text: '' }], // void node 要有一个空 text
-      },
-      { text: 'image' },
-    ],
-  },
-  {
-    type: 'paragraph',
-    children: [{ text: '结束' }],
-  },
-  {
-    type: 'paragraph',
-    children: [{ text: '一行文字' }],
-  },
-  {
-    type: 'header2',
-    children: [
-      {
-        text: '二级标题',
-      },
-    ],
-  },
-  {
-    type: 'pre',
-    children: [
-      {
-        type: 'code',
-        language: 'html',
-        children: [{ text: '<div>text</div>' }],
-      },
-    ],
-  },
-  {
-    type: 'paragraph',
-    children: [{ text: '一行文字' }],
-  },
-  {
-    type: 'paragraph',
-    children: [
-      { text: '一行文字' },
-      {
-        type: 'image',
-        src: 'https://www.baidu.com/img/flexible/logo/pc/result@2.png',
-        alt: '百度',
-        url: 'https://www.baidu.com/',
-        style: { width: '101px', height: '33px' },
-        children: [{ text: '' }], // void node 要有一个空 text
-      },
-      { text: '一行文字' },
-    ],
-  },
-  {
-    type: 'blockquote',
-    children: [{ text: '一行文字' }],
-  },
-  {
-    type: 'paragraph',
-    children: [{ text: '一行文字' }],
-  },
-  {
-    type: 'divider',
-    children: [{ text: '' }],
-  },
-  {
-    type: 'header3',
-    children: [
-      {
-        text: '三级标题',
-      },
-    ],
-  },
-  {
-    type: 'paragraph',
-    children: [{ text: '一行文字' }],
-  },
-  {
-    type: 'paragraph',
-    children: [{ text: '一行文字' }],
-  },
-]
diff --git a/packages/editor/examples/like-yuque.html b/packages/editor/examples/like-yuque.html
deleted file mode 100644
index f2fd07f33..000000000
--- a/packages/editor/examples/like-yuque.html
+++ /dev/null
@@ -1,136 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>like yuque</title>
-
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="../dist/css/style.css" rel="stylesheet">
-  <style>
-    html,
-    body {
-      background-color: #fff;
-      height: 100%;
-      overflow: hidden;
-      color: #333;
-    }
-
-    #top-container {
-      border-bottom: 1px solid #e8e8e8;
-      padding-left: 50px;
-    }
-
-    #editor-toolbar {
-      width: 1300px;
-      background-color: #FCFCFC;
-      margin: 0 auto;
-    }
-
-    #content {
-      height: calc(100% - 40px);
-      background-color: rgb(245, 245, 245);
-      overflow-y: auto;
-      position: relative;
-    }
-
-    #editor-container {
-      width: 850px;
-      margin: 30px auto 150px auto;
-      background-color: #fff;
-      padding: 20px 50px 50px 50px;
-      border: 1px solid #e8e8e8;
-      box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
-    }
-
-    #title-container {
-      padding: 20px 0;
-      border-bottom: 1px solid #e8e8e8;
-    }
-
-    #title-container input {
-      font-size: 30px;
-      border: 0;
-      outline: none;
-      width: 100%;
-      line-height: 1;
-    }
-
-    #editor-text-area {
-      min-height: 900px;
-      margin-top: 20px;
-    }
-  </style>
-
-</head>
-
-<body>
-  <div id="top-container">
-    <p>
-      <svg width="16" height="16" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
-        <path
-          d="M648.533333 85.333333L853.333333 290.133333V938.666667H170.666667V85.333333h477.866666m34.133334-85.333333H85.333333v1024h853.333334V256l-256-256z">
-        </path>
-        <path d="M256 341.333333h512v85.333334H256zM256 512h512v85.333333H256zM256 682.666667h512v85.333333H256z">
-        </path>
-      </svg>
-      文章标题信息
-    </p>
-  </div>
-  <div style="border-bottom: 1px solid #e8e8e8;">
-    <div id="editor-toolbar"></div>
-  </div>
-  <div id="content">
-    <div id="editor-container">
-      <div id="title-container">
-        <input value="请输入标题">
-      </div>
-      <div id="editor-text-area"></div>
-    </div>
-  </div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const editorConfig = { MENU_CONF: {} }
-    editorConfig.placeholder = '请输入内容'
-    editorConfig.scroll = false // 禁止编辑器滚动
-    editorConfig.MENU_CONF['uploadImage'] = {
-      fieldName: 'your-fileName',
-      base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-    }
-    editorConfig.onChange = (editor) => {
-      // console.log('content', editor.children)
-    }
-
-    // 先创建 editor
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      content: window.content1,
-      config: editorConfig
-    })
-
-    // 创建 toolbar
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {
-        excludeKeys: 'fullScreen',
-      }
-    })
-
-    // 点击空白处 focus 编辑器
-    document.getElementById('editor-text-area').addEventListener('click', e => {
-      if (e.target.id === 'editor-text-area') {
-        editor.blur()
-        editor.focus(true) // focus 到末尾
-      }
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/maxlength.html b/packages/editor/examples/maxlength.html
deleted file mode 100644
index f6f56166e..000000000
--- a/packages/editor/examples/maxlength.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>maxLength demo</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>maxLength(慎用,可能影响性能)</p>
-
-  <!-- 编辑器 -->
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <!-- 内容状态 -->
-  <p style="background-color: #f1f1f1;">
-    当前文字数量:<span id="total-length"></span>;
-    选中文字数量:<span id="selected-length"></span>;
-    选中文字:"<span id="selected-text"></span>"
-  </p>
-
-  <!-- 显示内容 -->
-  <div id="editor-content-view" class="editor-content-view"></div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const editorConfig = { MENU_CONF: {} }
-    editorConfig.placeholder = '请输入内容'
-
-    editorConfig.maxLength = 100 // 慎用,可能影响性能,需在文档中说明
-    editorConfig.onMaxLength = (editor) => {
-      console.log('onMaxLength')
-    }
-
-    editorConfig.onChange = (editor) => {
-      const html = editor.getHtml()
-      document.getElementById('editor-content-view').innerHTML = html
-
-      // 选中文字
-      const selectionText = editor.getSelectionText()
-      document.getElementById('selected-text').innerHTML = selectionText
-      document.getElementById('selected-length').innerHTML = selectionText.length
-      // 全部文字
-      document.getElementById('total-length').innerHTML = editor.getText().length
-    }
-
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      // content: window.content1,
-      config: editorConfig
-    })
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/menu.html b/packages/editor/examples/menu.html
deleted file mode 100644
index 2f179d610..000000000
--- a/packages/editor/examples/menu.html
+++ /dev/null
@@ -1,105 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>menu config demo</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>menu 配置,包括:hoverbar toolbar</p>
-
-  <!-- 编辑器 -->
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-    const DomEditor = window.wangEditor.DomEditor
-
-    const editorConfig = { MENU_CONF: {} }
-    editorConfig.placeholder = '请输入内容'
-
-    editorConfig.hoverbarKeys = {
-      text: {
-        menuKeys: ['bold', 'insertLink'],
-      },
-      'link': {
-        menuKeys: ['editLink', 'unLink', 'viewLink'],
-      },
-      'image': {
-        menuKeys: [
-          'imageWidth30',
-          'imageWidth50',
-          'imageWidth100',
-          'editImage',
-          'viewImageLink',
-          'deleteImage',
-        ],
-      }
-      // 其他参考 https://github.com/wangeditor-team/wangEditor/blob/master/packages/editor/src/init-default-config/config/hoverbar.ts
-    }
-
-    // 各个菜单的配置
-    editorConfig.MENU_CONF['color'] = {
-      colors: ['#000', '#333', '#999', '#ccc']
-    }
-    editorConfig.MENU_CONF['fontSize'] = {
-      fontSizeList: ['12px', '16px', '24px', '40px']
-    }
-    // 其他菜单配置项可通过 editor.getMenuConfig(menuKey) 查询,然后使用 editorConfig.MENU_CONF[menuKey] 修改
-
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      content: window.content1,
-      config: editorConfig
-    })
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: {
-        toolbarKeys: [
-          '|', // 第一个是 `|` 不显示
-          'headerSelect',
-          'blockquote',
-          '|', '|', '|', // 多个紧挨者的 `|` 只显示一个
-          'bold',
-          'underline',
-          'italic',
-          {
-            key: 'group-more-style', // 以 group 开头
-            title: '更多样式',
-            iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M505.6 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M806.4 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>',
-            menuKeys: ['through', 'code'],
-          },
-          'color',
-          'bgColor',
-          'clearStyle',
-          '|',
-          'fontSize',
-          'fontFamily',
-          'lineHeight',
-          '|',
-          'undo',
-          'redo',
-          '|', // 最后一个是 `|` 不显示
-        ],
-        // insertKeys: {
-        //   index: 5,
-        //   keys: ['insertImage', 'insertVideo']
-        // },
-        // excludeKeys: ['headerSelect', 'underline', 'clearStyle', 'fontFamily', 'group-image']
-      },
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/modal-appendTo-body.html b/packages/editor/examples/modal-appendTo-body.html
deleted file mode 100644
index 5126c82ae..000000000
--- a/packages/editor/examples/modal-appendTo-body.html
+++ /dev/null
@@ -1,97 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>modal appendTo body - demo</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-  <link href="../dist/css/style.css" rel="stylesheet">
-
-  <style>
-    #mask {
-      position: fixed;
-      top: 0;
-      left: 0;
-      right: 0;
-      bottom: 0;
-      z-index: 999;
-      background-color: #00000073;
-      display: none;
-    }
-  </style>
-</head>
-
-<body>
-  <p>
-    modal appendTo body
-  </p>
-
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <!-- mask div 蒙层 -->
-  <div id="mask"></div>
-
-  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    const editorConfig = { MENU_CONF: {} }
-
-    const toolbarConfig = {
-      modalAppendToBody: true
-    }
-
-    const editor = E.createEditor({
-      selector: '#editor-text-area',
-      content: window.content1,
-      config: editorConfig
-    })
-
-    const toolbar = E.createToolbar({
-      editor,
-      selector: '#editor-toolbar',
-      config: toolbarConfig
-    })
-
-    editor.on('modalOrPanelShow', modalOrPanel => {
-      if (modalOrPanel.type !== 'modal') return
-
-      const { $elem } = modalOrPanel // modal element
-      const width = $elem.width()
-      const height = $elem.height()
-
-      // set modal position z-index
-      $elem.css({
-        left: '50%',
-        top: '50%',
-        marginLeft: `-${width / 2}px`,
-        marginTop: `-${height / 2}px`,
-        zIndex: 1000
-      })
-
-      // show mask div
-      document.getElementById('mask').style.display = 'block'
-    })
-    editor.on('modalOrPanelHide', () => {
-      console.log('hide')
-
-      // hide mask div
-      document.getElementById('mask').style.display = 'none'
-    })
-    // click mask div to hide modal
-    document.getElementById('mask').addEventListener('click', () => {
-      editor.hidePanelOrModal()
-    })
-  </script>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/multi-editors.html b/packages/editor/examples/multi-editors.html
deleted file mode 100644
index a9211ea7f..000000000
--- a/packages/editor/examples/multi-editors.html
+++ /dev/null
@@ -1,99 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>多编辑器 demo</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/view.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-  <style>
-    .container {
-      display: flex;
-    }
-    .container-item {
-      flex: 1;
-      margin: 0 5px;
-      max-width: 50%;
-    }
-  </style>
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>多编辑器 demo</p>
-
-  <div class="container">
-    <div class="container-item">
-      <div id="editor-toolbar-1" class="editor-toolbar"></div>
-      <div id="editor-text-area-1" class="editor-text-area"></div>
-      <div id="content-view-1" class="editor-content-view"></div>
-    </div>
-    <div class="container-item">
-      <div id="editor-toolbar-2" class="editor-toolbar"></div>
-      <div id="editor-text-area-2" class="editor-text-area"></div>
-      <div id="content-view-2" class="editor-content-view"></div>
-    </div>
-  </div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // --------------------- editor1 ---------------------
-    const editorConfig1 = { MENU_CONF: {} }
-    editorConfig1.placeholder = '请输入内容1...'
-    editorConfig1.MENU_CONF['uploadImage'] = {
-      base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-    }
-    editorConfig1.onChange = (editor) => {
-      Promise.resolve().then(() => {
-        document.getElementById('content-view-1').innerHTML = editor1.getHtml()
-      })
-    }
-
-    const editor1 = E.createEditor({
-      selector: '#editor-text-area-1',
-      config: editorConfig1,
-      content: [{ type: 'paragraph', children: [{ text: '编辑器1' }] }]
-    })
-    const toolbar1 = E.createToolbar({
-      editor: editor1,
-      selector: '#editor-toolbar-1',
-      config: {}
-    })
-
-
-    // --------------------- editor2 ---------------------
-    const editorConfig2 = { MENU_CONF: {} }
-    editorConfig2.placeholder = '请输入内容2...'
-    editorConfig2.hoverbarKeys = {
-      // 禁用部分 hoverbar
-      'link': [],
-      'table': []
-    }
-    editorConfig2.MENU_CONF['uploadImage'] = {
-      fieldName: 'your-fileName',
-      base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
-    }
-    editorConfig2.onChange = (editor) => {
-      Promise.resolve().then(() => {
-        document.getElementById('content-view-2').innerHTML = editor2.getHtml()
-      })
-    }
-
-    const editor2 = E.createEditor({
-      selector: '#editor-text-area-2',
-      config: editorConfig2,
-      content: [{ type: 'paragraph', children: [{ text: '编辑器2' }] }],
-      mode: 'simple'
-    })
-    const toolbar2 = E.createToolbar({
-      editor: editor2,
-      selector: '#editor-toolbar-2',
-      mode: 'simple'
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/new-menu.html b/packages/editor/examples/new-menu.html
deleted file mode 100644
index 2a6932102..000000000
--- a/packages/editor/examples/new-menu.html
+++ /dev/null
@@ -1,91 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>New menu</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p>New menu</p>
-  <p>
-    <button id="btn-create">create editor</button>
-    <button id="btn-destroy">destroy editor</button>
-  </p>
-
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <script src="js/init-content.js"></script>
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-
-    // ---------- 注册新菜单 start ----------
-    class MyButtonMenu {
-        constructor() {
-            this.title = 'menu1',
-            this.tag = 'button'
-        }
-        getValue() { return '' }
-        isActive() { return false }
-        isDisabled() { return false }
-        exec(editor) {
-            console.log(editor)
-            alert('menu1 exec')
-        }
-    }
-    const menuConf = {
-      key: 'my-menu-1', // menu key ,唯一。注册之后,需通过 toolbarKeys 配置到工具栏
-      factory() {
-        return new MyButtonMenu()
-      },
-    }
-    E.Boot.registerMenu(menuConf)
-    // ---------- 注册新菜单 end ----------
-
-    // editor 配置
-    const editorConfig = {
-      placeholder: '请输入内容...',
-      onChange(editor) {}
-    }
-
-    // toolbar 配置
-    const toolbarConfig = {
-        // toolbarKeys: ['headerSelect', 'bold', 'my-menu-1'],
-        // excludeKeys: [],
-        insertKeys: {
-            index: 3,
-            keys: 'my-menu-1'
-        }
-    }
-
-    let editor, toolbar
-
-    // create
-    document.getElementById('btn-create').addEventListener('click', () => {
-      editor = E.createEditor({
-        selector: '#editor-text-area',
-        config: editorConfig
-      })
-
-      toolbar = E.createToolbar({
-        editor,
-        selector: '#editor-toolbar',
-        config: toolbarConfig,
-      })
-    })
-
-    // destroy
-    document.getElementById('btn-destroy').addEventListener('click', () => {
-      editor.destroy()
-      editor = null
-    })
-  </script>
-</body>
-</html>
\ No newline at end of file
diff --git a/packages/editor/examples/parse-html.html b/packages/editor/examples/parse-html.html
deleted file mode 100644
index 117bab197..000000000
--- a/packages/editor/examples/parse-html.html
+++ /dev/null
@@ -1,181 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>回显 html</title>
-  <link href="https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
-  <link href="./css/editor.css" rel="stylesheet">
-  <link href="../dist/css/style.css" rel="stylesheet">
-</head>
-<body>
-  <p style="background-color: #ccc; padding: 5px 0;"><b>回显 html</b> - 使用 html 初始化编辑器内容</p>
-
-  <div style="margin: 10px 0;">
-    <button id="btn-v4-html">填入 V4 html 示例</button>
-    <button id="btn-v5-html">填入 V5 html 示例</button>
-    <span style="color: red;"><b>【注意】只识别从 wangEditor 生成的 html</b> ,不可以随意自定义 html 代码(html 格式太灵活了,不会全部兼容)</span>
-  </div>
-  <textarea id="text-html" style="width: 100%; height: 300px; font-size: 12px;" placeholder="输入 html 然后创建编辑器">&lt;p&gt;hello world&lt;/p&gt;</textarea>
-
-  <div style="margin: 10px 0;">
-    <button id="btn-create">创建编辑器</button>
-    <button id="btn-set-html">setHtml</button>
-    (如有 JS 报错,再次创建时要刷新页面)
-  </div>
-  <div>
-    <div id="editor-toolbar" class="editor-toolbar"></div>
-    <div id="editor-text-area" class="editor-text-area"></div>
-  </div>
-
-  <script src="../dist/index.js"></script>
-  <script>
-    const E = window.wangEditor
-    const textarea = document.getElementById('text-html')
-    let editor
-
-    // 创建编辑器
-    document.getElementById('btn-create').addEventListener('click', () => {
-      if (editor) editor.destroy()
-
-      const html = textarea.value
-      editor = E.createEditor({
-        selector: '#editor-text-area',
-        html,
-      })
-      E.createToolbar({
-        editor,
-        selector: '#editor-toolbar',
-      })
-    })
-
-      // setHtml
-      document.getElementById('btn-set-html').addEventListener('click', () => {
-        if (!editor) alert('editor 尚未创建')
-
-        const html = textarea.value
-        editor.setHtml(html)
-      })
-
-    // 填入 v4 html
-    document.getElementById('btn-v4-html').addEventListener('click', () => {
-      textarea.value = `<p>你好&nbsp;<font size="6">世界</font> <font face="黑体">黑体文字</font>!</p>
-<p>欢迎<font color="#eeece0">使用</font> <b>wangEditor</b> <span style="background-color: rgb(139, 170, 74);">富文本</span>编辑器</p>
-<h1 id="94fco">标题</h1>
-<blockquote><p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p></blockquote>
-<p>欢迎<i>使用</i> <b>wangEditor</b><u>富文本</u><strike>编辑器</strike>~</p>
-<p style="padding-left:2em;">缩进&nbsp;<a href="https://github.com/wangeditor-team" target="_blank">链接</a></p>
-<p data-we-empty-p="" style="line-height:3;">行高&nbsp;</p><p>
-  <img src="https://www.wangeditor.com/imgs/logo.png"/></p>
-<p style="text-align:center;">欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
-<ol>
-  <li>abc</li>
-  <li>def</li>
-</ol>
-<ul><li>123</li><li>123</li></ul>
-<p>
-  <img src="https://www.wangeditor.com/imgs/logo.png" alt="111" data-href="https%3A%2F%2Fgithub.com%2Fwangeditor-team" style="max-width:100%;" contenteditable="false"/>
-</p>
-<table border="0" width="100%" cellpadding="0" cellspacing="0">
-  <tbody>
-    <tr><th>是的</th><th>你好</th><th>世界</th></tr><tr><td>aaa</td><td></td><td></td></tr>
-    <tr><td>bb</td><td>33</td><td></td></tr><tr><td></td><td></td><td>55</td></tr>
-    <tr><td></td><td></td><td></td></tr>
-  </tbody>
-</table>
-<p>100</p>
-<p><video src="https://media.w3.org/2010/05/sintel/trailer.mp4" controls=""></video></p>
-<p>200</p>
-<p><iframe src="//player.bilibili.com/player.html?aid=250348909&amp;bvid=BV1Pv411w7Xr&amp;cid=401518678&amp;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe></p>
-<p>123123 🤣😎</p>
-<pre><code class="JavaScript"><xmp>const a = 100;
-function fn() {
-  return a;
-}
-


-
  • 吃饭
-
  • 睡觉11
-
  • 打豆豆
-


` - }) - - // 填入 v5 html - document.getElementById('btn-v5-html').addEventListener('click', () => { - textarea.value = `

标题1

-

标题3
-换行1
-换行2
-换行3

-


-
引用文字123
换行
-

hello 多样式 word~😊😬

-

- span包裹1  - span包裹2  - span包裹3 -

-
-

- 百度 -   - var -

-

行高文字 line-height

-

对齐方式 text-align

-

增加缩进 indent 1

-

增加缩进 indent 2

-

- google -

-

- 设置字号 -   - 设置字体 -

-

颜色 颜色 颜色

-
const a = 10;
-function fn() {
-  // 代码1
-}
-


-
const a = 10;
-function fn() {
-  // 代码2
-}
-
    -
  • 项目A
  • -
  • 项目B
  • -
  • 项目C word~
  • -
-
    -
  1. 项目1 百度
  2. -
  3. 项目2
  4. -
  5. 项目3
  6. -
-
- -
-
- -
-


-
- - - - -
a
a1
a2
bc
1
2
3
23
-


-
吃饭
-
睡觉
-
看电影
-
- 其他标签 -
-` - }) - - - diff --git a/packages/editor/examples/shadow-dom.html b/packages/editor/examples/shadow-dom.html deleted file mode 100644 index 9cd7c3640..000000000 --- a/packages/editor/examples/shadow-dom.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - -shadow dom - - - - -
-

- - - - -

- - -
- -
- - -

- 当前文字数量:; - 选中文字数量:; - 选中文字:"" -

- - -
-
- - - - - - - \ No newline at end of file diff --git a/packages/editor/examples/simple-mode.html b/packages/editor/examples/simple-mode.html deleted file mode 100644 index 44d8f6e22..000000000 --- a/packages/editor/examples/simple-mode.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - simple mode - - - - - - -

simple mode

-

- bold - default mode -

- - -
-
-
-
- -
- -
- - - - - - \ No newline at end of file diff --git a/packages/editor/examples/theme.html b/packages/editor/examples/theme.html deleted file mode 100644 index c5b30af07..000000000 --- a/packages/editor/examples/theme.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - Theme demo - - - - - - -

- Theme - -

- - -
-
-
-
- - - - - - \ No newline at end of file diff --git a/packages/editor/examples/todo.html b/packages/editor/examples/todo.html deleted file mode 100644 index 9fb0da698..000000000 --- a/packages/editor/examples/todo.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - todo demo - - - - - - - -

todo

- - -
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/packages/editor/examples/upload-image.html b/packages/editor/examples/upload-image.html deleted file mode 100644 index 9d32da206..000000000 --- a/packages/editor/examples/upload-image.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - upload image demo - - - - - - -

- Upload image -

-

- 本地下载、启动 server 服务端 - http://127.0.0.1:3000/api/upload-img -

- -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/packages/editor/examples/upload-video.html b/packages/editor/examples/upload-video.html deleted file mode 100644 index ff254e720..000000000 --- a/packages/editor/examples/upload-video.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - upload video demo - - - - - - -

- Upload video -

-

- 本地下载、启动 server 服务端 - http://127.0.0.1:3000/api/upload-video -

- -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/packages/editor/package.json b/packages/editor/package.json deleted file mode 100644 index fee734385..000000000 --- a/packages/editor/package.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "name": "@wangeditor/editor", - "version": "5.1.23", - "description": "Web rich text editor, Web 富文本编辑器", - "keywords": [ - "wangeditor", - "rich text", - "editor", - "富文本", - "编辑器" - ], - "author": "wangfupeng1988 ", - "contributors": [], - "homepage": "https://www.wangeditor.com/", - "license": "MIT", - "types": "dist/editor/src/index.d.ts", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "browser": { - "./dist/index.js": "./dist/index.js", - "./dist/index.esm.js": "./dist/index.esm.js" - }, - "directories": { - "lib": "dist", - "test": "__tests__" - }, - "files": [ - "dist" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.com/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wangeditor-team/wangEditor.git" - }, - "scripts": { - "test": "jest", - "test-c": "jest --coverage", - "example": "concurrently \"yarn dev-watch\" \"http-server -p 8881 -c-1\" ", - "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js", - "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", - "build": "cross-env NODE_ENV=production rollup -c rollup.config.js", - "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js", - "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js" - }, - "bugs": { - "url": "https://github.com/wangeditor-team/wangEditor/issues" - }, - "dependencies": { - "@uppy/core": "^2.1.1", - "@uppy/xhr-upload": "^2.0.3", - "@wangeditor/basic-modules": "^1.1.7", - "@wangeditor/code-highlight": "^1.0.3", - "@wangeditor/core": "^1.1.19", - "@wangeditor/list-module": "^1.0.5", - "@wangeditor/table-module": "^1.1.4", - "@wangeditor/upload-image-module": "^1.0.2", - "@wangeditor/video-module": "^1.1.4", - "dom7": "^3.0.0", - "is-hotkey": "^0.2.0", - "lodash.camelcase": "^4.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.foreach": "^4.5.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "lodash.toarray": "^4.4.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } -} diff --git a/packages/editor/rollup.config.js b/packages/editor/rollup.config.js deleted file mode 100644 index 91bf6e0db..000000000 --- a/packages/editor/rollup.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import { createRollupConfig, IS_PRD, IS_DEV } from '../../build/create-rollup-config' -import pkg from './package.json' - -const name = 'wangEditor' - -const configList = [] - -// umd -const umdConf = createRollupConfig({ - output: { - file: pkg.main, - format: 'umd', - name, - }, -}) -configList.push(umdConf) - -// esm -const esmConf = createRollupConfig({ - output: { - file: pkg.module, - format: 'esm', - name, - }, -}) -configList.push(esmConf) - -export default configList diff --git a/packages/editor/src/Boot.ts b/packages/editor/src/Boot.ts deleted file mode 100644 index d1d0da770..000000000 --- a/packages/editor/src/Boot.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @description Editor View class - * @author wangfupeng - */ - -import { - IDomEditor, - - // 配置 - IEditorConfig, - IToolbarConfig, - IModuleConf, - - // 注册菜单 - IRegisterMenuConf, - registerMenu, - - // 渲染 modal -> view - IRenderElemConf, - RenderStyleFnType, - registerStyleHandler, - registerRenderElemConf, - - // to html - IElemToHtmlConf, - styleToHtmlFnType, - registerStyleToHtmlHandler, - registerElemToHtmlConf, - - // parseHtml - PreParseHtmlFnType, - IPreParseHtmlConf, - registerPreParseHtmlConf, - ParseStyleHtmlFnType, - IParseElemHtmlConf, - registerParseElemHtmlConf, - registerParseStyleHtmlHandler, -} from '@wangeditor/core' -import registerModule from './register-builtin-modules/register' - -type PluginType = (editor: T) => T - -class Boot { - constructor() { - throw new Error('不能实例化\nCan not construct a instance') - } - - // editor 配置 - static editorConfig: Partial = {} - static setEditorConfig(newConfig: Partial = {}) { - this.editorConfig = { - ...this.editorConfig, - ...newConfig, - } - } - static simpleEditorConfig: Partial = {} - static setSimpleEditorConfig(newConfig: Partial = {}) { - this.simpleEditorConfig = { - ...this.simpleEditorConfig, - ...newConfig, - } - } - - //toolbar 配置 - static toolbarConfig: Partial = {} - static setToolbarConfig(newConfig: Partial = {}) { - this.toolbarConfig = { - ...this.toolbarConfig, - ...newConfig, - } - } - static simpleToolbarConfig: Partial = {} - static setSimpleToolbarConfig(newConfig: Partial = {}) { - this.simpleToolbarConfig = { - ...this.simpleToolbarConfig, - ...newConfig, - } - } - - // 注册插件 - static plugins: PluginType[] = [] - static registerPlugin(plugin: PluginType) { - this.plugins.push(plugin) - } - - // 注册 menu - // TODO 可在注册时传入配置,在开发文档中说明 - static registerMenu(menuConf: IRegisterMenuConf, customConfig?: { [key: string]: any }) { - registerMenu(menuConf, customConfig) - } - - // 注册 renderElem - static registerRenderElem(renderElemConf: IRenderElemConf) { - registerRenderElemConf(renderElemConf) - } - - // 注册 renderStyle - static registerRenderStyle(fn: RenderStyleFnType) { - registerStyleHandler(fn) - } - - // 注册 elemToHtml - static registerElemToHtml(elemToHtmlConf: IElemToHtmlConf) { - registerElemToHtmlConf(elemToHtmlConf) - } - - // 注册 styleToHtml - static registerStyleToHtml(fn: styleToHtmlFnType) { - registerStyleToHtmlHandler(fn) - } - - // 注册 preParseHtml - static registerPreParseHtml(preParseHtmlConf: IPreParseHtmlConf) { - registerPreParseHtmlConf(preParseHtmlConf) - } - - // 注册 parseElemHtml - static registerParseElemHtml(parseElemHtmlConf: IParseElemHtmlConf) { - registerParseElemHtmlConf(parseElemHtmlConf) - } - - // 注册 parseStyleHtml - static registerParseStyleHtml(fn: ParseStyleHtmlFnType) { - registerParseStyleHtmlHandler(fn) - } - - // 注册 module - static registerModule(module: Partial) { - registerModule(module) - } -} - -export default Boot diff --git a/packages/editor/src/assets/index.less b/packages/editor/src/assets/index.less deleted file mode 100644 index 2a0dd08b3..000000000 --- a/packages/editor/src/assets/index.less +++ /dev/null @@ -1,24 +0,0 @@ -// 集中定义 css vars ,否则会被重复定义多次 -:root, :host { - // textarea - css vars - --w-e-textarea-bg-color: #fff; - --w-e-textarea-color: #333; - --w-e-textarea-border-color: #ccc; - --w-e-textarea-slight-border-color: #e8e8e8; - --w-e-textarea-slight-color: #d4d4d4; - --w-e-textarea-slight-bg-color: #f5f2f0; - --w-e-textarea-selected-border-color: #B4D5FF; // 选中的元素,如选中了分割线 - --w-e-textarea-handler-bg-color: #4290f7; // 工具,如图片拖拽按钮 - - // toolbar - css vars - --w-e-toolbar-color: #595959; - --w-e-toolbar-bg-color: #fff; - --w-e-toolbar-active-color: #333; - --w-e-toolbar-active-bg-color: #f1f1f1; - --w-e-toolbar-disabled-color: #999; - --w-e-toolbar-border-color: #e8e8e8; - - // modal - css vars - --w-e-modal-button-bg-color: #fafafa; - --w-e-modal-button-border-color: #d9d9d9; -} diff --git a/packages/editor/src/constants/svg.ts b/packages/editor/src/constants/svg.ts deleted file mode 100644 index ecf42ebb8..000000000 --- a/packages/editor/src/constants/svg.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @description svg tag - * @author wangfupeng - */ - -/** - * 【注意】svg 字符串的长度 ,否则会导致代码体积过大 - * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293 - * 找不到再从 iconfont.com 搜索 - */ - -// 缩进 right -export const INDENT_RIGHT_SVG = - '' - -// 左对齐 -export const JUSTIFY_LEFT_SVG = - '' - -// 图片 -export const IMAGE_SVG = - '' - -// plus -export const MORE_SVG = - '' - -// 视频 -export const VIDEO_SVG = - '' diff --git a/packages/editor/src/create.ts b/packages/editor/src/create.ts deleted file mode 100644 index 7e79414c1..000000000 --- a/packages/editor/src/create.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @description create - * @author wangfupeng - */ - -import { Descendant } from 'slate' -import Boot from './Boot' -import { DOMElement } from './utils/dom' -import { - IEditorConfig, - IDomEditor, - IToolbarConfig, - coreCreateEditor, - coreCreateToolbar, - Toolbar, -} from '@wangeditor/core' - -export interface ICreateEditorOption { - selector: string | DOMElement - config: Partial - content?: Descendant[] - html?: string - mode: string -} - -export interface ICreateToolbarOption { - editor: IDomEditor | null - selector: string | DOMElement - config?: Partial - mode?: string -} - -/** - * 创建 editor 实例 - */ -export function createEditor(option: Partial = {}): IDomEditor { - const { selector = '', content = [], html, config = {}, mode = 'default' } = option - - let globalConfig = mode === 'simple' ? Boot.simpleEditorConfig : Boot.editorConfig - - // 单独处理 hoverbarKeys - const newHoverbarKeys = { - ...(globalConfig.hoverbarKeys || {}), - ...(config.hoverbarKeys || {}), - } - - const editor = coreCreateEditor({ - selector, - config: { - ...globalConfig, // 全局配置 - ...config, - hoverbarKeys: newHoverbarKeys, - }, - content, - html, - plugins: Boot.plugins, - }) - - return editor -} - -/** - * 创建 toolbar 实例 - */ -export function createToolbar(option: ICreateToolbarOption): Toolbar { - const { selector, editor, config = {}, mode = 'default' } = option - if (!selector) { - throw new Error(`Cannot find 'selector' when create toolbar`) - } - - let globalConfig = mode === 'simple' ? Boot.simpleToolbarConfig : Boot.toolbarConfig - - const toolbar = coreCreateToolbar(editor, { - selector, - config: { - ...globalConfig, // 全局配置 - ...config, - }, - }) - - return toolbar -} diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts deleted file mode 100644 index fe1761818..000000000 --- a/packages/editor/src/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @description editor entry - * @author wangfupeng - */ - -import './assets/index.less' -import '@wangeditor/core/dist/css/style.css' - -// 兼容性(要放在最开始就执行) -import './utils/browser-polyfill' -import './utils/node-polyfill' - -// 配置多语言 -import './locale/index' - -// 注册内置模块 -import './register-builtin-modules/index' -// 初始化默认配置 -import './init-default-config' - -// 全局注册 -import Boot from './Boot' -export { Boot } - -// 导出 core API 和接口(注意,此处按需导出,不可直接用 `*` ) -export { - DomEditor, - IDomEditor, - IEditorConfig, - IToolbarConfig, - Toolbar, - // 第三方模块 - 接口 - IModuleConf, - IButtonMenu, - ISelectMenu, - IDropPanelMenu, - IModalMenu, - // 第三方模块 - 多语言 - i18nChangeLanguage, - i18nAddResources, - i18nGetResources, - t, - // 第三方模块 - modal 中用到的 API - genModalTextareaElems, - genModalInputElems, - genModalButtonElems, - // 第三方模块 - 上传时用到 - createUploader, - IUploadConfig, -} from '@wangeditor/core' - -// 导出 slate API 和接口 (需重命名,加 `Slate` 前缀) -export { - Transforms as SlateTransforms, - Descendant as SlateDescendant, - Editor as SlateEditor, - Node as SlateNode, - Element as SlateElement, - Text as SlateText, - Path as SlatePath, - Range as SlateRange, - Location as SlateLocation, - Point as SlatePoint, -} from 'slate' - -// 导出 create 函数 -export { createEditor, createToolbar } from './create' - -export default {} diff --git a/packages/editor/src/init-default-config/config/hoverbar.ts b/packages/editor/src/init-default-config/config/hoverbar.ts deleted file mode 100644 index a5df8dc15..000000000 --- a/packages/editor/src/init-default-config/config/hoverbar.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @description hoverbar 配置 - * @author wangfupeng - */ - -const COMMON_HOVERBAR_KEYS = { - // key 即 element type - link: { - menuKeys: ['editLink', 'unLink', 'viewLink'], - }, - image: { - menuKeys: [ - 'imageWidth30', - 'imageWidth50', - 'imageWidth100', - 'editImage', - 'viewImageLink', - 'deleteImage', - ], - }, - pre: { - menuKeys: ['enter', 'codeBlock', 'codeSelectLang'], - }, - table: { - menuKeys: [ - 'enter', - 'tableHeader', - 'tableFullWidth', - 'insertTableRow', - 'deleteTableRow', - 'insertTableCol', - 'deleteTableCol', - 'deleteTable', - ], - }, - divider: { - menuKeys: ['enter'], - }, - video: { - menuKeys: ['enter', 'editVideoSize'], - }, -} - -export function genDefaultHoverbarKeys() { - return { - ...COMMON_HOVERBAR_KEYS, - - // 也可以自定义 match 来匹配元素,此时 key 就随意了 - text: { - menuKeys: [ - 'headerSelect', - 'insertLink', - 'bulletedList', - '|', - 'bold', - 'through', - 'color', - 'bgColor', - 'clearStyle', - ], - }, - // other hover bar ... - } -} - -export function genSimpleHoverbarKeys() { - return COMMON_HOVERBAR_KEYS -} diff --git a/packages/editor/src/init-default-config/config/index.ts b/packages/editor/src/init-default-config/config/index.ts deleted file mode 100644 index d3dd5a626..000000000 --- a/packages/editor/src/init-default-config/config/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @description 获取编辑器默认配置 - * @author wangfupeng - */ - -import { genDefaultToolbarKeys, genSimpleToolbarKeys } from './toolbar' -import { genDefaultHoverbarKeys, genSimpleHoverbarKeys } from './hoverbar' - -export function getDefaultEditorConfig() { - return { - hoverbarKeys: genDefaultHoverbarKeys(), - } -} - -export function getSimpleEditorConfig() { - return { - hoverbarKeys: genSimpleHoverbarKeys(), - } -} - -export function getDefaultToolbarConfig() { - return { - toolbarKeys: genDefaultToolbarKeys(), - } -} - -export function getSimpleToolbarConfig() { - return { - toolbarKeys: genSimpleToolbarKeys(), - } -} diff --git a/packages/editor/src/init-default-config/config/toolbar.ts b/packages/editor/src/init-default-config/config/toolbar.ts deleted file mode 100644 index 56e070d6a..000000000 --- a/packages/editor/src/init-default-config/config/toolbar.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * @description toolbar 配置 - * @author wangfupeng - */ - -import { t } from '@wangeditor/core' -import { - INDENT_RIGHT_SVG, - JUSTIFY_LEFT_SVG, - IMAGE_SVG, - MORE_SVG, - VIDEO_SVG, -} from '../../constants/svg' - -export function genDefaultToolbarKeys() { - return [ - 'headerSelect', - // 'header1', - // 'header2', - // 'header3', - 'blockquote', - '|', - 'bold', - 'underline', - 'italic', - { - key: 'group-more-style', // 以 group 开头 - title: t('editor.more'), - iconSvg: MORE_SVG, - menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'], - }, - 'color', - 'bgColor', - '|', - 'fontSize', - 'fontFamily', - 'lineHeight', - '|', - 'bulletedList', - 'numberedList', - 'todo', - { - key: 'group-justify', // 以 group 开头 - title: t('editor.justify'), - iconSvg: JUSTIFY_LEFT_SVG, - menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'], - }, - { - key: 'group-indent', // 以 group 开头 - title: t('editor.indent'), - iconSvg: INDENT_RIGHT_SVG, - menuKeys: ['indent', 'delIndent'], - }, - '|', - 'emotion', - 'insertLink', - // 'editLink', - // 'unLink', - // 'viewLink', - { - key: 'group-image', // 以 group 开头 - title: t('editor.image'), - iconSvg: IMAGE_SVG, - menuKeys: ['insertImage', 'uploadImage'], - }, - // 'deleteImage', - // 'editImage', - // 'viewImageLink', - { - key: 'group-video', // 以 group 开头 - title: t('editor.video'), - iconSvg: VIDEO_SVG, - menuKeys: ['insertVideo', 'uploadVideo'], - }, - // 'deleteVideo', - 'insertTable', - 'codeBlock', - // 'codeSelectLang', - 'divider', - // 'deleteTable', - '|', - 'undo', - 'redo', - '|', - 'fullScreen', - ] -} - -export function genSimpleToolbarKeys() { - return [ - 'blockquote', - 'header1', - 'header2', - 'header3', - '|', - 'bold', - 'underline', - 'italic', - 'through', - 'color', - 'bgColor', - 'clearStyle', - '|', - 'bulletedList', - 'numberedList', - 'todo', - 'justifyLeft', - 'justifyRight', - 'justifyCenter', - '|', - 'insertLink', - { - key: 'group-image', // 以 group 开头 - title: t('editor.image'), - iconSvg: IMAGE_SVG, - menuKeys: ['insertImage', 'uploadImage'], - }, - 'insertVideo', - 'insertTable', - 'codeBlock', - '|', - 'undo', - 'redo', - '|', - 'fullScreen', - ] -} diff --git a/packages/editor/src/init-default-config/index.ts b/packages/editor/src/init-default-config/index.ts deleted file mode 100644 index 5d18431cf..000000000 --- a/packages/editor/src/init-default-config/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @description set default config - * @author wangfupeng - */ - -import Boot from '../Boot' -import { - getDefaultEditorConfig, - getDefaultToolbarConfig, - getSimpleEditorConfig, - getSimpleToolbarConfig, -} from './config' - -import { wangEditorCodeHighLightDecorate } from '@wangeditor/code-highlight' - -const defaultEditorConfig = getDefaultEditorConfig() -Boot.setEditorConfig({ - ...defaultEditorConfig, - decorate: wangEditorCodeHighLightDecorate, // 代码高亮 -}) - -const simpleEditorConfig = getSimpleEditorConfig() -Boot.setSimpleEditorConfig({ - ...simpleEditorConfig, - decorate: wangEditorCodeHighLightDecorate, // 代码高亮 -}) - -const defaultToolbarConfig = getDefaultToolbarConfig() -Boot.setToolbarConfig(defaultToolbarConfig) - -const simpleToolbarConfig = getSimpleToolbarConfig() -Boot.setSimpleToolbarConfig(simpleToolbarConfig) diff --git a/packages/editor/src/locale/en.ts b/packages/editor/src/locale/en.ts deleted file mode 100644 index 9ef955532..000000000 --- a/packages/editor/src/locale/en.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @description i18n en - * @author wangfupeng - */ - -export default { - editor: { - more: 'More', - justify: 'Justify', - indent: 'Indent', - image: 'Image', - video: 'Video', - }, -} diff --git a/packages/editor/src/locale/index.ts b/packages/editor/src/locale/index.ts deleted file mode 100644 index 22f6ec6cb..000000000 --- a/packages/editor/src/locale/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n entry - * @author wangfupeng - */ - -import { i18nAddResources } from '@wangeditor/core' -import enResources from './en' -import zhResources from './zh-CN' - -i18nAddResources('en', enResources) -i18nAddResources('zh-CN', zhResources) diff --git a/packages/editor/src/locale/zh-CN.ts b/packages/editor/src/locale/zh-CN.ts deleted file mode 100644 index 470331a87..000000000 --- a/packages/editor/src/locale/zh-CN.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @description i18n zh-CN - * @author wangfupeng - */ - -export default { - editor: { - more: '更多', - justify: '对齐', - indent: '缩进', - image: '图片', - video: '视频', - }, -} diff --git a/packages/editor/src/register-builtin-modules/index.ts b/packages/editor/src/register-builtin-modules/index.ts deleted file mode 100644 index 774619146..000000000 --- a/packages/editor/src/register-builtin-modules/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @description register builtin modules - * @author wangfupeng - */ - -// basic-modules -import '@wangeditor/basic-modules/dist/css/style.css' -import basicModules from '@wangeditor/basic-modules' - -import '@wangeditor/list-module/dist/css/style.css' -import wangEditorListModule from '@wangeditor/list-module' - -// table-module -import '@wangeditor/table-module/dist/css/style.css' -import wangEditorTableModule from '@wangeditor/table-module' - -// video-module -import '@wangeditor/video-module/dist/css/style.css' -import wangEditorVideoModule from '@wangeditor/video-module' - -// upload-image-module -import '@wangeditor/upload-image-module/dist/css/style.css' -import wangEditorUploadImageModule from '@wangeditor/upload-image-module' - -// code-highlight -import '@wangeditor/code-highlight/dist/css/style.css' -import { wangEditorCodeHighlightModule } from '@wangeditor/code-highlight' - -import registerModule from './register' - -basicModules.forEach(module => registerModule(module)) -registerModule(wangEditorListModule) -registerModule(wangEditorTableModule) -registerModule(wangEditorVideoModule) -registerModule(wangEditorUploadImageModule) -registerModule(wangEditorCodeHighlightModule) diff --git a/packages/editor/src/register-builtin-modules/register.ts b/packages/editor/src/register-builtin-modules/register.ts deleted file mode 100644 index 59773c396..000000000 --- a/packages/editor/src/register-builtin-modules/register.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @description 注册 module - * @author wangfupeng - */ - -import Boot from '../Boot' -import { IModuleConf } from '@wangeditor/core' - -function registerModule(module: Partial) { - const { - menus, - renderElems, - renderStyle, - elemsToHtml, - styleToHtml, - preParseHtml, - parseElemsHtml, - parseStyleHtml, - editorPlugin, - } = module - - if (menus) { - menus.forEach(menu => Boot.registerMenu(menu)) - } - if (renderElems) { - renderElems.forEach(renderElemConf => Boot.registerRenderElem(renderElemConf)) - } - if (renderStyle) { - Boot.registerRenderStyle(renderStyle) - } - if (elemsToHtml) { - elemsToHtml.forEach(elemToHtmlConf => Boot.registerElemToHtml(elemToHtmlConf)) - } - if (styleToHtml) { - Boot.registerStyleToHtml(styleToHtml) - } - if (preParseHtml) { - preParseHtml.forEach(conf => Boot.registerPreParseHtml(conf)) - } - if (parseElemsHtml) { - parseElemsHtml.forEach(parseElemHtmlConf => Boot.registerParseElemHtml(parseElemHtmlConf)) - } - if (parseStyleHtml) { - Boot.registerParseStyleHtml(parseStyleHtml) - } - if (editorPlugin) { - Boot.registerPlugin(editorPlugin) - } -} - -export default registerModule diff --git a/packages/editor/src/utils/browser-polyfill.ts b/packages/editor/src/utils/browser-polyfill.ts deleted file mode 100644 index 4aa4f8310..000000000 --- a/packages/editor/src/utils/browser-polyfill.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @description browser polyfill - * @author wangfupeng - */ - -// @ts-nocheck - -// 必须是浏览器环境 -if (typeof global === 'undefined') { - // 检查 IE 浏览器 - if ('ActiveXObject' in window) { - let info = '抱歉,wangEditor V5+ 版本开始,不在支持 IE 浏览器' - info += '\n Sorry, wangEditor V5+ versions do not support IE browser.' - console.error(info) - } - - globalThisPolyfill() - AggregateErrorPolyfill() -} else if (global && global.navigator?.userAgent.match('QQBrowser')) { - // 兼容 QQ 浏览器 AggregateError 报错 - globalThisPolyfill() - AggregateErrorPolyfill() -} - -function globalThisPolyfill() { - // 部分浏览器不支持 globalThis - if (typeof globalThis === 'undefined') { - // @ts-ignore - window.globalThis = window - } -} - -function AggregateErrorPolyfill() { - if (typeof AggregateError === 'undefined') { - window.AggregateError = function (errors, msg) { - const err = new Error(msg) - err.errors = errors - return err - } - } -} diff --git a/packages/editor/src/utils/dom.ts b/packages/editor/src/utils/dom.ts deleted file mode 100644 index d2539cce3..000000000 --- a/packages/editor/src/utils/dom.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @description dom utils - * @author wangfupeng - */ - -import DOMElement = globalThis.Element - -export { DOMElement } diff --git a/packages/editor/src/utils/node-polyfill.ts b/packages/editor/src/utils/node-polyfill.ts deleted file mode 100644 index affc164cf..000000000 --- a/packages/editor/src/utils/node-polyfill.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @description node polyfill - * @author wangfupeng - */ - -// @ts-nocheck - -// 必须是 node 环境 -if (typeof global === 'object') { - // 用于 nodejs ,避免报错 - const globalProperty = Object.getOwnPropertyDescriptor(global, 'window') - - // global.window 为空则直接写入 - // 部分框架下已经定义了global.window且是不可写属性 - if (!global.window || globalProperty.set) { - global.window = global - global.requestAnimationFrame = () => {} - global.navigator = { - userAgent: '', - } - global.location = { - hostname: '0.0.0.0', - port: 0, - protocol: 'http:', - } - global.btoa = () => {} - global.crypto = { - getRandomValues: function (buffer: any) { - return nodeCrypto.randomFillSync(buffer) - }, - } - } - - if (global.document != null) { - // SSR 环境下可能会报错 (issue 4409) - if (global.document.getElementsByTagName == null) { - global.document.getElementsByTagName = () => [] - } - } -} diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json deleted file mode 100644 index 87e3a8293..000000000 --- a/packages/editor/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": [ - "./src/**/*", - "../custom-types.d.ts" - ] -} \ No newline at end of file diff --git a/packages/list-module/CHANGELOG.md b/packages/list-module/CHANGELOG.md deleted file mode 100644 index 900de0724..000000000 --- a/packages/list-module/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.0.5](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/list-module@1.0.4...@wangeditor/list-module@1.0.5) (2022-09-27) - - -### Bug Fixes - -* list-item - 遇到 style 是 toHtml 出错 ([9854308](https://github.com/wangeditor-team/wangEditor/commit/98543083a1cb09207aceb2a4d8f3c1ce020b106d)) - - - - - -## [1.0.4](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/list-module@1.0.3...@wangeditor/list-module@1.0.4) (2022-09-27) - -**Note:** Version bump only for package @wangeditor/list-module diff --git a/packages/list-module/README.md b/packages/list-module/README.md deleted file mode 100644 index 20f049f27..000000000 --- a/packages/list-module/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# wangEditor list-module - -List module built in [wangEditor](https://www.wangeditor.com/) by default. diff --git a/packages/list-module/__tests__/elem-to-html.test.ts b/packages/list-module/__tests__/elem-to-html.test.ts deleted file mode 100644 index b6326d8e6..000000000 --- a/packages/list-module/__tests__/elem-to-html.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @description list toHtml test - * @author wangfupeng - */ - -import createEditor from '../../../tests/utils/create-editor' -import { ELEM_TO_EDITOR } from '../src/utils/maps' -import listItemToHtmlConf from '../src/module/elem-to-html' - -describe('module elem-to-html', () => { - const childrenHtml = 'hello' - - const orderedElem1 = { type: 'list-item', ordered: true, children: [{ text: '' }] } - const orderedElem2 = { type: 'list-item', ordered: true, children: [{ text: '' }] } - const unOrderedItem1 = { type: 'list-item', children: [{ text: '' }] } - const unOrderedItem2 = { type: 'list-item', children: [{ text: '' }] } - const unOrderedItem21 = { type: 'list-item', level: 1, children: [{ text: '' }] } - - const editor = createEditor({ - content: [orderedElem1, orderedElem2, unOrderedItem1, unOrderedItem2, unOrderedItem21], - }) - - // elem 绑定 editor - ELEM_TO_EDITOR.set(orderedElem1, editor) - ELEM_TO_EDITOR.set(orderedElem2, editor) - ELEM_TO_EDITOR.set(unOrderedItem1, editor) - ELEM_TO_EDITOR.set(unOrderedItem2, editor) - ELEM_TO_EDITOR.set(unOrderedItem21, editor) - - test(`toHtml conf type`, () => { - expect(listItemToHtmlConf.type).toBe('list-item') - }) - - test(`ordered item toHtml`, () => { - const { elemToHtml } = listItemToHtmlConf - - // first item - const firstHtml = elemToHtml(orderedElem1, childrenHtml) - expect(firstHtml).toEqual({ - html: '
  • hello
  • ', - prefix: '
      ', // 第一个 item ,前面会有
        - suffix: '', - }) - - // last item - const lastHtml = elemToHtml(orderedElem2, childrenHtml) - expect(lastHtml).toEqual({ - html: '
      1. hello
      2. ', - prefix: '', - suffix: '
      ', // 最后一个 item ,后面会有
    - }) - }) - - test(`unOrdered item toHtml`, () => { - const { elemToHtml } = listItemToHtmlConf - - // first item - const firstHtml = elemToHtml(unOrderedItem1, childrenHtml) - expect(firstHtml).toEqual({ - html: '
  • hello
  • ', - prefix: '
      ', // 第一个 item ,前面会有
        - suffix: '', - }) - - // second item - const secondHtml = elemToHtml(unOrderedItem2, childrenHtml) - expect(secondHtml).toEqual({ - html: '
      • hello
      • ', // 第二个 item ,不应该有
          - prefix: '', - suffix: '', - }) - - // last item - leveled - const lastHtml = elemToHtml(unOrderedItem21, childrenHtml) - expect(lastHtml).toEqual({ - html: '
        • hello
        • ', // 最后一个 item ( leveled ) ,包裹
            - prefix: '
              ', - suffix: '
          ', - }) - }) -}) diff --git a/packages/list-module/__tests__/menu/bulleted-list-menu.test.ts b/packages/list-module/__tests__/menu/bulleted-list-menu.test.ts deleted file mode 100644 index 1abb3cd91..000000000 --- a/packages/list-module/__tests__/menu/bulleted-list-menu.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @description bulletedList menu test - * @author wangfupeng - */ - -import BulletedListMenu from '../../src/module/menu/BulletedListMenu' -import createEditor from '../../../../tests/utils/create-editor' - -describe('list BulletedListMenu', () => { - const menu = new BulletedListMenu() - - it('getValue', () => { - const editor = createEditor() - expect(menu.getValue(editor)).toBe('') - }) - - it('isActive', () => { - const editor = createEditor({ - content: [ - { type: 'paragraph', children: [{ text: 'hello' }] }, - { type: 'list-item', children: [{ text: 'a' }] }, - ], - }) - - editor.deselect() - expect(menu.isActive(editor)).toBeFalsy() - - editor.select({ path: [0, 0], offset: 0 }) // 选中 p - expect(menu.isActive(editor)).toBeFalsy() - - editor.select({ path: [1, 0], offset: 0 }) // 选中 li - expect(menu.isActive(editor)).toBeTruthy() - }) - - it('isDisabled', () => { - const editor = createEditor({ - content: [ - { type: 'paragraph', children: [{ text: 'hello' }] }, - { type: 'list-item', children: [{ text: 'a' }] }, - { - type: 'table', - width: 'auto', - children: [ - { - type: 'table-row', - children: [{ type: 'table-cell', children: [{ text: '' }], isHeader: true }], - }, - ], - }, - { - type: 'pre', - children: [{ type: 'code', language: '', children: [{ text: 'a' }] }], - }, - ], - }) - - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select({ path: [0, 0], offset: 0 }) // 选中 p - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.select({ path: [1, 0], offset: 0 }) // 选中 li - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.select({ path: [2, 0, 0, 0], offset: 0 }) // 选中 table 单元格 - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select({ path: [3, 0, 0], offset: 0 }) // 选中 code - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('exec', () => { - const pElem = { type: 'paragraph', children: [{ text: 'hello' }] } - const editor = createEditor({ - content: [pElem], - }) - editor.select({ path: [0, 0], offset: 0 }) // 选中 p - - menu.exec(editor, '') // p 转 li - expect(editor.children).toEqual([ - { - type: 'list-item', - ordered: false, - children: [{ text: 'hello' }], - }, - ]) - - menu.exec(editor, '') // li 转 p - expect(editor.children).toEqual([pElem]) - }) -}) diff --git a/packages/list-module/__tests__/menu/numbered-list-menu.test.ts b/packages/list-module/__tests__/menu/numbered-list-menu.test.ts deleted file mode 100644 index ada56b747..000000000 --- a/packages/list-module/__tests__/menu/numbered-list-menu.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @description list NumberedListMenu test - * @author wangfupeng - */ - -import NumberedListMenu from '../../src/module/menu/NumberedListMenu' -import createEditor from '../../../../tests/utils/create-editor' - -describe('list NumberedListMenu', () => { - const menu = new NumberedListMenu() - - it('getValue', () => { - const editor = createEditor() - expect(menu.getValue(editor)).toBe('') - }) - - it('isActive', () => { - const editor = createEditor({ - content: [ - { type: 'paragraph', children: [{ text: 'hello' }] }, - { type: 'list-item', ordered: true, children: [{ text: 'a' }] }, - ], - }) - - editor.deselect() - expect(menu.isActive(editor)).toBeFalsy() - - editor.select({ path: [0, 0], offset: 0 }) // 选中 p - expect(menu.isActive(editor)).toBeFalsy() - - editor.select({ path: [1, 0], offset: 0 }) // 选中 li - expect(menu.isActive(editor)).toBeTruthy() - }) - - it('isDisabled', () => { - const editor = createEditor({ - content: [ - { type: 'paragraph', children: [{ text: 'hello' }] }, - { type: 'list-item', ordered: true, children: [{ text: 'a' }] }, - { - type: 'table', - width: 'auto', - children: [ - { - type: 'table-row', - children: [{ type: 'table-cell', children: [{ text: '' }], isHeader: true }], - }, - ], - }, - { - type: 'pre', - children: [{ type: 'code', language: '', children: [{ text: 'a' }] }], - }, - ], - }) - - editor.deselect() - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select({ path: [0, 0], offset: 0 }) // 选中 p - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.select({ path: [1, 0], offset: 0 }) // 选中 li - expect(menu.isDisabled(editor)).toBeFalsy() - - editor.select({ path: [2, 0, 0, 0], offset: 0 }) // 选中 table 单元格 - expect(menu.isDisabled(editor)).toBeTruthy() - - editor.select({ path: [3, 0, 0], offset: 0 }) // 选中 code - expect(menu.isDisabled(editor)).toBeTruthy() - }) - - it('exec', () => { - const pElem = { type: 'paragraph', children: [{ text: 'hello' }] } - const editor = createEditor({ - content: [pElem], - }) - editor.select({ path: [0, 0], offset: 0 }) // 选中 p - - menu.exec(editor, '') // p 转 li - expect(editor.children).toEqual([ - { - type: 'list-item', - ordered: true, - children: [{ text: 'hello' }], - }, - ]) - - menu.exec(editor, '') // li 转 p - expect(editor.children).toEqual([pElem]) - }) -}) diff --git a/packages/list-module/__tests__/parse-html.test.ts b/packages/list-module/__tests__/parse-html.test.ts deleted file mode 100644 index cd9da4a54..000000000 --- a/packages/list-module/__tests__/parse-html.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../tests/utils/create-editor' -import { parseItemHtmlConf, parseListHtmlConf } from '../src/module/parse-elem-html' - -describe('list - parse html', () => { - const editor = createEditor() - - it('parse unOrdered list item', () => { - const $ul = $('
            ') - const $li = $('
          • ') - $ul.append($li) - const children = [{ text: 'hello' }] - - const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) - expect(elem).toEqual({ - type: 'list-item', - ordered: false, - level: 0, - children, - }) - }) - - it('parse ordered list item', () => { - const $ol = $('
              ') - const $li = $('
            1. ') - $ol.append($li) - const children = [{ text: 'hello' }] - - const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) - expect(elem).toEqual({ - type: 'list-item', - ordered: true, - level: 0, - children, - }) - }) - - it('parse leveled list item', () => { - const $ul = $('
                ') - const $ol = $('
                  ') - const $li = $('
                1. ') - $ul.append($ol) - $ol.append($li) - const children = [{ text: 'hello' }] - - const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) - expect(elem).toEqual({ - type: 'list-item', - ordered: true, - level: 1, - children, - }) - }) - - it('parse list', () => { - const $ol = $('
                    ') - const children = [ - { - type: 'list-item', - ordered: true, - children: [{ text: 'a' }], - }, - { - type: 'list-item', - ordered: true, - children: [{ text: 'b' }], - }, - // 嵌套列表 - [ - { - type: 'list-item', - level: 1, - children: [{ text: 'x' }], - }, - { - type: 'list-item', - level: 1, - children: [{ text: 'y' }], - }, - ], - ] - // @ts-ignore - const listElems = parseListHtmlConf.parseElemHtml($ol[0], children, editor) - expect(listElems.length).toBe(4) // parse list 时,会把输出的结果(数组)flatten ,把嵌套的平铺开 - }) -}) diff --git a/packages/list-module/__tests__/plugin.test.ts b/packages/list-module/__tests__/plugin.test.ts deleted file mode 100644 index 9ee006d98..000000000 --- a/packages/list-module/__tests__/plugin.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @description list plugin test - * @author wangfupeng - */ - -import withList from '../src/module/plugin' -import createEditor from '../../../tests/utils/create-editor' - -describe('list plugin test', () => { - it('insert tab - increase level', () => { - const listItem = { type: 'list-item', children: [{ text: 'hello' }] } - let editor = createEditor({ - content: [listItem], - }) - editor = withList(editor) // 使用插件 - editor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 - - editor.handleTab() // tab - - const children = editor.children - expect(children).toEqual([ - { - ...listItem, - level: 1, // 增加 level - }, - ]) - }) - - it('insert delete - decrease level', () => { - const listItem = { type: 'list-item', children: [{ text: 'hello' }], level: 2 } - let editor = createEditor({ - content: [listItem], - }) - editor = withList(editor) // 使用插件 - editor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 - - editor.deleteBackward('character') // delete - expect(editor.children).toEqual([ - { - ...listItem, - level: 1, // 减少 level - }, - ]) - - editor.deleteBackward('character') // delete - expect(editor.children).toEqual([ - { - ...listItem, - level: 0, // 减少 level - }, - ]) - }) - - it('兼容之前的 JSON 格式', () => { - const listItem = { type: 'list-item', children: [{ text: 'hello' }] } - let editor = createEditor({ - // 之前的 JSON 格式 - content: [ - { - type: 'bulleted-list', - children: [listItem], - }, - ], - }) - editor = withList(editor) // 使用插件 - - expect(editor.children).toEqual([listItem]) - }) -}) diff --git a/packages/list-module/__tests__/render-elem.test.ts b/packages/list-module/__tests__/render-elem.test.ts deleted file mode 100644 index ead1fbed0..000000000 --- a/packages/list-module/__tests__/render-elem.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @description list render elem test - * @author wangfupeng - */ - -import createEditor from '../../../tests/utils/create-editor' -import renderListItemConf from '../src/module/render-elem' - -describe('list module - render elem', () => { - const unOrderedItem = { type: 'list-item', children: [{ text: '' }] } - const orderedItem = { type: 'list-item', ordered: true, children: [{ text: '' }] } - const leveledItem = { type: 'list-item', level: 3, children: [{ text: '' }] } - const editor = createEditor({ - content: [unOrderedItem, orderedItem, leveledItem], - }) - - it('render conf type', () => { - expect(renderListItemConf.type).toBe('list-item') - }) - - it('render ordered list item elem', () => { - const vnode: any = renderListItemConf.renderElem(orderedItem, null, editor) - expect(vnode.sel).toBe('div') // render-elem 使用
                    模拟
                  1. - - const prefixVnode = vnode.children[0] || {} - expect(prefixVnode.text).toBe('1.') // ordered list-item 有序号 - }) - - it('render unOrdered list item elem', () => { - const vnode: any = renderListItemConf.renderElem(unOrderedItem, null, editor) - expect(vnode.sel).toBe('div') // render-elem 使用
                    模拟
                  2. - - const prefixVnode = vnode.children[0] || {} - expect(prefixVnode.text).toBe('•') // unOrdered list-item 点号 - }) - - it('render leveled list item elem', () => { - const vnode: any = renderListItemConf.renderElem(leveledItem, null, editor) - const style = vnode.data.style - expect(style).toEqual({ margin: '5px 0 5px 60px' }) // margin-left 60px - }) -}) diff --git a/packages/list-module/package.json b/packages/list-module/package.json deleted file mode 100644 index 3a45af61e..000000000 --- a/packages/list-module/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@wangeditor/list-module", - "version": "1.0.5", - "description": "wangEditor list module", - "author": "wangfupeng1988 ", - "contributors": [], - "homepage": "https://github.com/wangeditor-team/wangEditor#readme", - "license": "MIT", - "types": "dist/list-module/src/index.d.ts", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "browser": { - "./dist/index.js": "./dist/index.js", - "./dist/index.esm.js": "./dist/index.esm.js" - }, - "directories": { - "lib": "dist", - "test": "__tests__" - }, - "files": [ - "dist" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.com/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wangeditor-team/wangEditor.git" - }, - "scripts": { - "test": "jest", - "test-c": "jest --coverage", - "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js", - "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", - "build": "cross-env NODE_ENV=production rollup -c rollup.config.js", - "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js", - "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js" - }, - "bugs": { - "url": "https://github.com/wangeditor-team/wangEditor/issues" - }, - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } -} diff --git a/packages/list-module/rollup.config.js b/packages/list-module/rollup.config.js deleted file mode 100644 index 28f6aa6e9..000000000 --- a/packages/list-module/rollup.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' -import pkg from './package.json' - -const name = 'WangEditorListModule' - -const configList = [] - -// esm - 开发环境不需要 CDN 引入,只需要 npm 引入,所以优先输出 esm -const esmConf = createRollupConfig({ - output: { - file: pkg.module, - format: 'esm', - name, - }, -}) -configList.push(esmConf) - -// umd -const umdConf = createRollupConfig({ - output: { - file: pkg.main, - format: 'umd', - name, - }, -}) -configList.push(umdConf) - -export default configList diff --git a/packages/list-module/src/constants/svg.ts b/packages/list-module/src/constants/svg.ts deleted file mode 100644 index 1ee141bfa..000000000 --- a/packages/list-module/src/constants/svg.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @description icon svg - * @author wangfupeng - */ - -/** - * 【注意】svg 字符串的长度 ,否则会导致代码体积过大 - * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293 - * 找不到再从 iconfont.com 搜索 - */ - -// 无序列表 -export const BULLETED_LIST_SVG = - '' - -// 有序列表 -export const NUMBERED_LIST_SVG = - '' diff --git a/packages/list-module/src/index.ts b/packages/list-module/src/index.ts deleted file mode 100644 index a32d617ab..000000000 --- a/packages/list-module/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @description list module - * @author wangfupeng - */ - -import './assets/index.less' - -// 配置多语言 -import './locale/index' - -// 导出 module -import wangEditorListModule from './module/index' -export default wangEditorListModule diff --git a/packages/list-module/src/locale/en.ts b/packages/list-module/src/locale/en.ts deleted file mode 100644 index 86f1027f0..000000000 --- a/packages/list-module/src/locale/en.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n en - * @author wangfupeng - */ - -export default { - listModule: { - unOrderedList: 'Unordered list', - orderedList: 'Ordered list', - }, -} diff --git a/packages/list-module/src/locale/index.ts b/packages/list-module/src/locale/index.ts deleted file mode 100644 index 22f6ec6cb..000000000 --- a/packages/list-module/src/locale/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n entry - * @author wangfupeng - */ - -import { i18nAddResources } from '@wangeditor/core' -import enResources from './en' -import zhResources from './zh-CN' - -i18nAddResources('en', enResources) -i18nAddResources('zh-CN', zhResources) diff --git a/packages/list-module/src/locale/zh-CN.ts b/packages/list-module/src/locale/zh-CN.ts deleted file mode 100644 index c779d2359..000000000 --- a/packages/list-module/src/locale/zh-CN.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n zh-CN - * @author wangfupeng - */ - -export default { - listModule: { - unOrderedList: '无序列表', - orderedList: '有序列表', - }, -} diff --git a/packages/list-module/src/module/custom-types.ts b/packages/list-module/src/module/custom-types.ts deleted file mode 100644 index 3c72fe47c..000000000 --- a/packages/list-module/src/module/custom-types.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @description list element - * @author wangfupeng - */ - -import { Text } from 'slate' - -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts - -export type ListItemElement = { - type: 'list-item' - ordered: boolean // 有序/无序 - level: number // 层级:0 1 2 ... - children: Text[] -} diff --git a/packages/list-module/src/module/elem-to-html.ts b/packages/list-module/src/module/elem-to-html.ts deleted file mode 100644 index dd143efb1..000000000 --- a/packages/list-module/src/module/elem-to-html.ts +++ /dev/null @@ -1,164 +0,0 @@ -/** - * @description to html - * @author wangfupeng - */ - -import { Element, Path, Editor } from 'slate' -import { DomEditor } from '@wangeditor/core' -import { ListItemElement } from './custom-types' -import { ELEM_TO_EDITOR } from '../utils/maps' - -/** - * 当前 list-item 前面需要拼接几个
                        - * @param elem elem - */ -function getStartContainerTagNumber(elem: Element): number { - const editor = ELEM_TO_EDITOR.get(elem) - if (editor == null) return 0 - - const { type, ordered = false, level = 0 } = elem as ListItemElement - - const path = DomEditor.findPath(editor, elem) - if (path[0] === 0) { - // list-item 是第一个元素,再往前没有了。需要拼接
                            - return level + 1 - } - - // 获取上一个 elem - const prevPath = Path.previous(path) - const prevEntry = Editor.node(editor, prevPath) - if (!prevEntry) return 0 - const [prevElem] = prevEntry - - const prevType = DomEditor.getNodeType(prevElem) - if (prevType !== type) { - // 上一个 elem 不是 list-item ,需要拼接
                                - return level + 1 - } - - // 上一个 elem 是 list-item - const { ordered: prevOrdered = false, level: prevLevel = 0 } = prevElem as ListItemElement - if (prevLevel < level) { - // 上一个 level 小于当前 level ,需要拼接
                                    - return level - prevLevel - } - if (prevLevel > level) { - // 上一个 level 大于当前 level ,不需要拼接
                                        - return 0 - } - if (prevLevel === level) { - // 上一个 level 等于当前 level - if (prevOrdered === ordered) { - // ordered 一致,则不需要拼接
                                            - return 0 - } else { - /// ordered 不一致,则需要拼接
                                                - return 1 - } - } - - // 其他情况 - return 0 -} - -/** - * 当前 list-item 后面面需要拼接几个
                                          - * @param elem elem - */ -function getEndContainerTagNumber(elem: Element): number { - const editor = ELEM_TO_EDITOR.get(elem) - if (editor == null) return 0 - - const { type, ordered = false, level = 0 } = elem as ListItemElement - - const path = DomEditor.findPath(editor, elem) - if (path[0] === editor.children.length - 1) { - // list-item 是最后一个元素,再往后没有了。需要拼接
                                      - return level + 1 - } - - // 获取下一个 elem - const nextPath = Path.next(path) - const nextEntry = Editor.node(editor, nextPath) - if (!nextEntry) return 0 - const [nextElem] = nextEntry - - const nextType = DomEditor.getNodeType(nextElem) - if (nextType !== type) { - // 下一个 elem 不是 list-item ,需要拼接
                                          - return level + 1 - } - - // 下一个 elem 是 list-item - const { ordered: nextOrdered = false, level: nextLevel = 0 } = nextElem as ListItemElement - if (nextLevel < level) { - // 下一个 level 小于当前 level ,需要拼接
                                  - return level - nextLevel - } - if (nextLevel > level) { - // 下一个 level 大于当前 level ,不需要拼接
                              - return 0 - } - if (nextLevel === level) { - // 下一个 level 等于当前 level - if (nextOrdered === ordered) { - // ordered 一致,则不需要拼接
                          - return 0 - } else { - /// ordered 不一致,则需要拼接
                      - return 1 - } - } - - // 其他情况 - return 0 -} - -// ol ul 栈 -const CONTAINER_TAG_STACK: Array = [] - -function elemToHtml( - elem: Element, - childrenHtml: string -): { - html: string - prefix?: string - suffix?: string -} { - let startContainerStr = '' - let endContainerStr = '' - - const { ordered = false } = elem as ListItemElement - const containerTag = ordered ? 'ol' : 'ul' - - // 前面需要拼接几个
                          - const startContainerTagNumber = getStartContainerTagNumber(elem) - if (startContainerTagNumber > 0) { - for (let i = 0; i < startContainerTagNumber; i++) { - startContainerStr += `<${containerTag}>` // 记录 start container tag ,如 `
                            ` - CONTAINER_TAG_STACK.push(containerTag) // tag 压栈 - } - } - - // 后面需要拼接几个
                  - const endContainerTagNumber = getEndContainerTagNumber(elem) - if (endContainerTagNumber > 0) { - for (let i = 0; i < endContainerTagNumber; i++) { - const tag = CONTAINER_TAG_STACK.pop() // tag 从栈中获取 - endContainerStr += `` // 记录 end container tag ,如 `
                ` - } - } - - return { - html: `
              • ${childrenHtml}
              • `, - prefix: startContainerStr, - suffix: endContainerStr, - } -} - -const listItemToHtmlConf = { - type: 'list-item', - elemToHtml: elemToHtml, -} - -export default listItemToHtmlConf diff --git a/packages/list-module/src/module/index.ts b/packages/list-module/src/module/index.ts deleted file mode 100644 index f9faadc91..000000000 --- a/packages/list-module/src/module/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @description list module entry - * @author wangfupeng - */ - -import { IModuleConf } from '@wangeditor/core' -import renderListItemConf from './render-elem' -import withList from './plugin' -import { bulletedListMenuConf, numberedListMenuConf } from './menu/index' -import listItemToHtmlConf from './elem-to-html' -import { parseItemHtmlConf, parseListHtmlConf } from './parse-elem-html' - -const list: Partial = { - renderElems: [renderListItemConf], - editorPlugin: withList, - menus: [bulletedListMenuConf, numberedListMenuConf], - elemsToHtml: [listItemToHtmlConf], - parseElemsHtml: [parseListHtmlConf, parseItemHtmlConf], -} - -export default list diff --git a/packages/list-module/src/module/menu/BaseMenu.ts b/packages/list-module/src/module/menu/BaseMenu.ts deleted file mode 100644 index 9e7c337fe..000000000 --- a/packages/list-module/src/module/menu/BaseMenu.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @description base menu - * @author wangfupeng - */ - -import { Editor, Node, Transforms, Element } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor/core' -import { ListItemElement } from '../custom-types' - -abstract class BaseMenu implements IButtonMenu { - readonly type = 'list-item' - abstract readonly ordered: boolean - abstract readonly title: string - abstract readonly iconSvg: string - readonly tag = 'button' - - private getListNode(editor: IDomEditor): Node | null { - const { type } = this - return DomEditor.getSelectedNodeByType(editor, type) - } - - getValue(editor: IDomEditor): string | boolean { - return '' - } - - isActive(editor: IDomEditor): boolean { - const node = this.getListNode(editor) - if (node == null) return false - const { ordered = false } = node as ListItemElement - return ordered === this.ordered - } - - isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true - - const selectedElems = DomEditor.getSelectedElems(editor) - const notMatch = selectedElems.some((elem: Element) => { - if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem)) return true - - const { type } = elem as Element - if (['pre', 'code', 'table'].includes(type)) return true - }) - if (notMatch) return true - - return false - } - - exec(editor: IDomEditor, value: string | boolean): void { - const active = this.isActive(editor) - if (active) { - // 如果当前 active ,则转换为 p 标签 - Transforms.setNodes(editor, { - type: 'paragraph', - ordered: undefined, - level: undefined, - }) - } else { - // 否则,转换为 list-item - Transforms.setNodes(editor, { - type: 'list-item', - ordered: this.ordered, // 有序/无序 - indent: undefined, - }) - } - } -} - -export default BaseMenu diff --git a/packages/list-module/src/module/menu/BulletedListMenu.ts b/packages/list-module/src/module/menu/BulletedListMenu.ts deleted file mode 100644 index 74bb037fe..000000000 --- a/packages/list-module/src/module/menu/BulletedListMenu.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @description bulleted list menu - * @author wangfupeng - */ - -import { t } from '@wangeditor/core' -import BaseMenu from './BaseMenu' -import { BULLETED_LIST_SVG } from '../../constants/svg' - -class BulletedListMenu extends BaseMenu { - readonly ordered = false - readonly title = t('listModule.unOrderedList') - readonly iconSvg = BULLETED_LIST_SVG -} - -export default BulletedListMenu diff --git a/packages/list-module/src/module/menu/NumberedListMenu.ts b/packages/list-module/src/module/menu/NumberedListMenu.ts deleted file mode 100644 index c74a46ed4..000000000 --- a/packages/list-module/src/module/menu/NumberedListMenu.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @description numbered list menu - * @author wangfupeng - */ - -import { t } from '@wangeditor/core' -import BaseMenu from './BaseMenu' -import { NUMBERED_LIST_SVG } from '../../constants/svg' - -class NumberedListMenu extends BaseMenu { - readonly ordered = true - readonly title = t('listModule.orderedList') - readonly iconSvg = NUMBERED_LIST_SVG -} - -export default NumberedListMenu diff --git a/packages/list-module/src/module/menu/index.ts b/packages/list-module/src/module/menu/index.ts deleted file mode 100644 index efbd802da..000000000 --- a/packages/list-module/src/module/menu/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @description menu entry - * @author wangfupeng - */ - -import BulletedListMenu from './BulletedListMenu' -import NumberedListMenu from './NumberedListMenu' - -export const bulletedListMenuConf = { - key: 'bulletedList', - factory() { - return new BulletedListMenu() - }, -} - -export const numberedListMenuConf = { - key: 'numberedList', - factory() { - return new NumberedListMenu() - }, -} diff --git a/packages/list-module/src/module/parse-elem-html.ts b/packages/list-module/src/module/parse-elem-html.ts deleted file mode 100644 index e591e5f5b..000000000 --- a/packages/list-module/src/module/parse-elem-html.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @description parse elem html - * @author wangfupeng - */ - -import { Dom7Array } from 'dom7' -import { Descendant, Text } from 'slate' -import $, { DOMElement, getTagName } from '../utils/dom' -import { IDomEditor } from '@wangeditor/core' -import { ListItemElement } from './custom-types' - -/** - * 获取 ordered - * @param $elem list $elem - */ -function getOrdered($elem: Dom7Array): boolean { - const $list = $elem.parent() - const listTagName = getTagName($list) - if (listTagName === 'ol') return true - return false -} - -/** - * 获取 level - * @param $elem list $elem - */ -function getLevel($elem: Dom7Array): number { - let level = 0 - - let $cur: Dom7Array = $elem.parent() - let tagName: string = getTagName($cur) - - while (tagName === 'ul' || tagName === 'ol') { - $cur = $cur.parent() - tagName = getTagName($cur) - level++ - } - - return level - 1 -} - -function parseItemHtml( - elem: DOMElement, - children: Descendant[], - editor: IDomEditor -): ListItemElement { - const $elem = $(elem) - - children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true - return false - }) - - // 无 children ,则用纯文本 - if (children.length === 0) { - children = [{ text: $elem.text().replace(/\s+/gm, ' ') }] - } - - const ordered = getOrdered($elem) - const level = getLevel($elem) - - return { - type: 'list-item', - ordered, - level, - // @ts-ignore - children, - } -} - -export const parseItemHtmlConf = { - selector: 'li:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性 - parseElemHtml: parseItemHtml, -} - -function parseListHtml( - elem: DOMElement, - children: Descendant[], - editor: IDomEditor -): ListItemElement[] { - // @ts-ignore flatten 因为可能有 ul/ol 嵌套,重要!!! - return children.flat(Infinity) -} - -export const parseListHtmlConf = { - selector: 'ul:not([data-w-e-type]),ol:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性 - parseElemHtml: parseListHtml, -} diff --git a/packages/list-module/src/module/plugin.ts b/packages/list-module/src/module/plugin.ts deleted file mode 100644 index 255b85edb..000000000 --- a/packages/list-module/src/module/plugin.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @description editor 插件,重写 editor API - * @author wangfupeng - */ - -import { Editor, Transforms, Range } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor/core' -import { ListItemElement } from './custom-types' - -/** - * 获取选中的 top elems - * @param editor editor - */ -function getTopSelectedElemsBySelection(editor: IDomEditor) { - return Editor.nodes(editor, { - at: editor.selection || undefined, - match: n => DomEditor.findPath(editor, n).length === 1, // 只匹配顶级元素 - }) -} - -function withList(editor: T): T { - const { deleteBackward, handleTab, normalizeNode } = editor - const newEditor = editor - - // 重写 deleteBackward - 降低 level 或者转换为 p 元素 - newEditor.deleteBackward = unit => { - const { selection } = newEditor - if (selection == null) { - deleteBackward(unit) - return - } - - if (Range.isExpanded(selection)) { - deleteBackward(unit) - return - } - - const listItemElem = DomEditor.getSelectedNodeByType(newEditor, 'list-item') - if (listItemElem == null) { - // 未匹配到 list-item - deleteBackward(unit) - return - } - - if (selection.focus.offset === 0) { - // 选中了当前 list-item 文本的开头,此时按删除键,应该降低 level 或转换为 p 元素 - const { level = 0 } = listItemElem as ListItemElement - if (level > 0) { - // 降低 level - Transforms.setNodes(newEditor, { level: level - 1 }) - } else { - // 转换为 p 元素 - Transforms.setNodes(newEditor, { - type: 'paragraph', - ordered: undefined, - level: undefined, - }) - } - return - } - - // 其他情况 - deleteBackward(unit) - } - - // 重写 tab - 当选中 list-item 文本开头时,增加 level - newEditor.handleTab = () => { - const { selection } = newEditor - if (selection == null) { - handleTab() - return - } - - // 选区是合并的,判断单个 list-item 即可 - if (Range.isCollapsed(selection)) { - const listItemElem = DomEditor.getSelectedNodeByType(newEditor, 'list-item') - if (listItemElem == null) { - // 未匹配到 list-item - handleTab() - return - } - - if (selection.focus.offset === 0) { - // 选中了当前 list-item 文本的开头,此时按 tab 应该增加 level - const { level = 0 } = listItemElem as ListItemElement - Transforms.setNodes(newEditor, { level: level + 1 }) - return - } - } - - // 选区是展开的,要判断多个 list-item - if (Range.isExpanded(selection)) { - let listItemNum = 0 // 选中的 list-item 有几个 - let hasOtherElem = false // 是否有其他元素 - - for (const entry of getTopSelectedElemsBySelection(newEditor)) { - const [elem] = entry - const type = DomEditor.getNodeType(elem) - if (type === 'list-item') listItemNum++ - else hasOtherElem = true - } - - if (hasOtherElem || listItemNum <= 1) { - // 选中了其他元素,或者只选中一个 list-item ,则执行默认行为 - handleTab() - return - } - - // 未选中其他元素,且选中多个 list-item ,则增加 level - for (const entry of getTopSelectedElemsBySelection(newEditor)) { - const [elem, path] = entry - const { level = 0 } = elem as ListItemElement - Transforms.setNodes(newEditor, { level: level + 1 }, { at: path }) - } - return - } - - // 其他情况 - handleTab() - } - - // 兼容之前的 JSON 格式 `numbered-list` 和 `bulleted-list` (之前的 list 没有嵌套功能) - newEditor.normalizeNode = ([node, path]) => { - const type = DomEditor.getNodeType(node) - - if (type === 'bulleted-list' || type === 'numbered-list') { - Transforms.unwrapNodes(newEditor, { at: path }) - } - - // 执行默认行为 - return normalizeNode([node, path]) - } - - return newEditor -} - -export default withList diff --git a/packages/list-module/src/module/render-elem.tsx b/packages/list-module/src/module/render-elem.tsx deleted file mode 100644 index 7b7dde715..000000000 --- a/packages/list-module/src/module/render-elem.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @description render list elem - * @author wangfupeng - */ - -import { Element as SlateElement, Path, Editor, Text } from 'slate' -import { jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor/core' -import { ListItemElement } from './custom-types' -import { ELEM_TO_EDITOR } from '../utils/maps' - -/** - * 无序列表:根据 level 获取的前置符号 - * @param level 层级 - */ -function genPreSymbol(level = 0): string { - let s = '' - switch (level) { - case 0: - s = '•' // 第一层级 - break - case 1: - s = '◦' // 第一层级 - break - case 2: - s = '▪' // 第三层级 - break - default: - s = '▪' // 其他层级 - } - return s -} - -/** - * 有序列表:获取前缀 number - * @param editor editor - * @param elem listItem elem - */ -function getOrderedItemNumber(editor: IDomEditor, elem: SlateElement): number { - const { type, level = 0, ordered = false } = elem as ListItemElement - if (!ordered) { - return -1 // 不是有序列表 - } - - let num = 1 // 默认值 1 - let curElem = elem - let curPath = DomEditor.findPath(editor, curElem) - - // 第一个元素,直接返回 1 - if (curPath[0] === 0) return 1 - - while (curPath[0] > 0) { - const prevPath = Path.previous(curPath) - const prevEntry = Editor.node(editor, prevPath) - if (prevEntry == null) break - const prevElem = prevEntry[0] as ListItemElement // 上一个节点 - const { level: prevLevel = 0, type: prevType, ordered: prevOrdered } = prevElem - - // type 不一致,退出循环,不再累加 num - if (prevType !== type) break - // prevLevel 更小,退出循环,不再累加 num - if (prevLevel < level) break - - if (prevLevel === level) { - // level 一样,如果 ordered 不一样,则退出循环,不再累加 num - if (prevOrdered !== ordered) break - // level 一样,order 一样,则累加 num - else num++ - } - - // prevLevel 更大,不累加 num ,继续向前 - curElem = prevElem - curPath = prevPath - } - - return num -} - -/** - * 获取第一个 text-node 的颜色 - * @param elem elem - */ -function getListItemColor(elem: SlateElement): string { - const children = elem.children || [] - const length = children.length - if (length === 0) return '' - - let firstTextNode - - for (let i = 0; i < length; i++) { - if (firstTextNode) break // 已找到第一个 text-node ,则退出 - const child = children[i] - if (Text.isText(child)) firstTextNode = child - } - - if (firstTextNode == null) return '' - return firstTextNode['color'] || '' -} - -function renderListElem( - elemNode: SlateElement, - children: VNode[] | null, - editor: IDomEditor -): VNode { - ELEM_TO_EDITOR.set(elemNode, editor) // 记录 elem 和 editor 关系,elem-to-html 时要用 - - const { level = 0, ordered = false } = elemNode as ListItemElement - - // 根据 level 增加 margin-left - const listStyle = { margin: `5px 0 5px ${level * 20}px` } - - // list-item 前缀 - let prefix = '' - if (ordered) { - // 有序列表:获取前缀 number - const orderedNumber = getOrderedItemNumber(editor, elemNode) - prefix = `${orderedNumber}.` - } else { - // 无序列表:根据层级,使用不同的前缀符号 - prefix = genPreSymbol(level) - } - - // 获取前缀颜色 - const prefixColor = getListItemColor(elemNode) - - const vnode = ( -
                - - {prefix} - - {children} -
                - ) - return vnode -} - -const renderListItemConf = { - type: 'list-item', - renderElem: renderListElem, -} - -export default renderListItemConf diff --git a/packages/list-module/src/utils/dom.ts b/packages/list-module/src/utils/dom.ts deleted file mode 100644 index c07bb007c..000000000 --- a/packages/list-module/src/utils/dom.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @description DOM 操作 - * @author wangfupeng - */ - -import $, { append, on, focus, attr, val, html, parent, hasClass, Dom7Array, empty } from 'dom7' - -if (append) $.fn.append = append -// if (on) $.fn.on = on -// if (focus) $.fn.focus = focus -if (attr) $.fn.attr = attr -// if (val) $.fn.val = val -// if (html) $.fn.html = html -if (parent) $.fn.parent = parent -// if (hasClass) $.fn.hasClass = hasClass -// if (empty) $.fn.empty = empty - -export default $ - -// COMPAT: This is required to prevent TypeScript aliases from doing some very -// weird things for Slate's types with the same name as globals. (2019/11/27) -// https://github.com/microsoft/TypeScript/issues/35002 -import DOMNode = globalThis.Node -import DOMComment = globalThis.Comment -import DOMElement = globalThis.Element -import DOMText = globalThis.Text -import DOMRange = globalThis.Range -import DOMSelection = globalThis.Selection -import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } - -/** - * 获取 tagName lower-case - * @param $elem $elem - */ -export function getTagName($elem: Dom7Array): string { - if ($elem.length) return $elem[0].tagName.toLowerCase() - return '' -} diff --git a/packages/list-module/src/utils/maps.ts b/packages/list-module/src/utils/maps.ts deleted file mode 100644 index 3401e85c3..000000000 --- a/packages/list-module/src/utils/maps.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @description maps - * @author wangfupeng - */ - -import { Element as SlateElement } from 'slate' -import { IDomEditor } from '@wangeditor/core' - -export const ELEM_TO_EDITOR = new WeakMap() diff --git a/packages/list-module/tsconfig.json b/packages/list-module/tsconfig.json deleted file mode 100644 index 5b6e8ad7b..000000000 --- a/packages/list-module/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": {}, - "include": [ - "./src/**/*", - "../custom-types.d.ts" - ] -} \ No newline at end of file diff --git a/packages/table-module/CHANGELOG.md b/packages/table-module/CHANGELOG.md deleted file mode 100644 index 68955ecc1..000000000 --- a/packages/table-module/CHANGELOG.md +++ /dev/null @@ -1,95 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.1.4](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/table-module@1.1.3...@wangeditor/table-module@1.1.4) (2022-09-27) - -**Note:** Version bump only for package @wangeditor/table-module - - - - - -## [1.1.3](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/table-module@1.1.2...@wangeditor/table-module@1.1.3) (2022-09-15) - - -### Bug Fixes - -* 插入表格会删掉去掉 issue 4711 ([d4fac4e](https://github.com/wangeditor-team/wangEditor/commit/d4fac4efd06480457a95c2b06e7472cf6204de58)) - - - - - -## [1.1.2](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/table-module@1.1.1...@wangeditor/table-module@1.1.2) (2022-09-14) - -**Note:** Version bump only for package @wangeditor/table-module - - - - - -## [1.1.1](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/table-module@1.1.0...@wangeditor/table-module@1.1.1) (2022-07-11) - - -### Bug Fixes - -* disabled 时,点击 table 会弹出菜单栏 ([9aa4b80](https://github.com/wangeditor-team/wangEditor/commit/9aa4b80a8c3cd29ca57dd62d69f5811868998f5c)) - - - - - -# [1.1.0](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/table-module@1.0.1...@wangeditor/table-module@1.1.0) (2022-05-25) - - -### Bug Fixes - -* 从表格后面删除,删除最后一个单元格 ([b327fcd](https://github.com/wangeditor-team/wangEditor/commit/b327fcd4669b1b1fad0e8b38b7d88db04c300e37)) - - -### Features - -* enter menu ([988fc31](https://github.com/wangeditor-team/wangEditor/commit/988fc31f31de3d37dffbf54abb784cceb8e6118d)) -* 表格拖拽列宽 ([46ea2c0](https://github.com/wangeditor-team/wangEditor/commit/46ea2c0f831b03ebca5fddfd59d682fed0b3476e)) - - - - - -## 1.0.1 (2022-04-18) - - -### Bug Fixes - -* 部分菜单 disabled ([87f1233](https://github.com/wangeditor-team/wangEditor/commit/87f12332a087072406c1988dc5cef2eae8335375)) -* 单元格内包含复杂样式内容时按tab未跳转到下一个单元格 ([db5e6f2](https://github.com/wangeditor-team/wangEditor/commit/db5e6f20c2c081d193fa80077f91d121be98c2a0)) -* 更新各包之间依赖版本 ([75c552c](https://github.com/wangeditor-team/wangEditor/commit/75c552cc8ed54765bebb86a7ec5329a7fc79e85f)) -* 两个表格不能紧挨着 ([5955b61](https://github.com/wangeditor-team/wangEditor/commit/5955b614cf92f65c9ebea47e6719047f3c0d27ea)) -* 修复 pnpm 安装 @wangeditor/editor 出现警告的问题 ([4087fbe](https://github.com/wangeditor-team/wangEditor/commit/4087fbee01c76bdd55e747a5e86c5e4a8d6a8353)) -* 移除了每个包下的 publishConfig directory 配置 ([16559f0](https://github.com/wangeditor-team/wangEditor/commit/16559f052545c111318be760e64291a521bdcc65)) -* 优化 custom-types.d.ts 中类型声明,修复测试文件 ts 报错 ([3a6c455](https://github.com/wangeditor-team/wangEditor/commit/3a6c4553245bc734dae1e17d605af389971782a2)) -* 优化表格 ([f240ca7](https://github.com/wangeditor-team/wangEditor/commit/f240ca71e31ccdea947233a767e3371434af0b6f)) -* parse html - 有些 elem children 需要过滤 ([63cbb80](https://github.com/wangeditor-team/wangEditor/commit/63cbb804c8c7a778a4ee1f4ba8717a11b4b6b5a3)) -* rename es module filename ([1821d4e](https://github.com/wangeditor-team/wangEditor/commit/1821d4eef49e64efcb41b848849ca7a5e6472044)) -* table - 粘贴合并单元格的表格 ([56ecb63](https://github.com/wangeditor-team/wangEditor/commit/56ecb6392510d433e092653f0f08183361778a3d)) -* table - disabled ([2b8717c](https://github.com/wangeditor-team/wangEditor/commit/2b8717c9a1c6853a3311fa6a667df6e0e75b61ee)) -* table - elemToHtml ([e36e609](https://github.com/wangeditor-team/wangEditor/commit/e36e6092ef721723169afc8bf0560a47ac9f4dfc)) -* table 不能是第一个元素 ([9407b79](https://github.com/wangeditor-team/wangEditor/commit/9407b79604163fece99dd96552487d21afd085e7)) -* table insertDOMElem ([6c89177](https://github.com/wangeditor-team/wangEditor/commit/6c89177878461fd59f128aa44ac175b2a49c3bd6)) -* table insertDOMElem ([3a42c37](https://github.com/wangeditor-team/wangEditor/commit/3a42c37c3bc38343e3a0b245d2bfb2abed0bd720)) -* table-cell 全选 ([1ef4872](https://github.com/wangeditor-team/wangEditor/commit/1ef48729e6d99e7414bc89bc4ef0d66c172fc566)) -* table内图片拖拽消失问题 ([a700a51](https://github.com/wangeditor-team/wangEditor/commit/a700a512fa7149da304f3d7c0ffaad8548a3def9)) - - -### Features - -* basic text paste ([f0a5b98](https://github.com/wangeditor-team/wangEditor/commit/f0a5b980c95fa1e2fc59a898c6e0d0723c276c28)) -* i18n ([c11b244](https://github.com/wangeditor-team/wangEditor/commit/c11b2440f91b99d40bca18b675c66a22b6e160c9)) -* parse html ([2a5eace](https://github.com/wangeditor-team/wangEditor/commit/2a5eace00f33cded50b68e8164748ec2480213fd)) -* table module ([a397116](https://github.com/wangeditor-team/wangEditor/commit/a397116de73e088232d9c41828f30f8d56a22dd4)) -* table module - header + fullWidth ([9a8a0e0](https://github.com/wangeditor-team/wangEditor/commit/9a8a0e093af944ee7deab674f47c2ec7baae0e63)) -* table内按tab光标换到下一个单元格 ([02421ad](https://github.com/wangeditor-team/wangEditor/commit/02421ad7603d20ce8e0d627a0f046c8992ba4934)) -* toHtml 机制 ([1c4d872](https://github.com/wangeditor-team/wangEditor/commit/1c4d8729f84aaab6a448f23064b34a20596305e9)) -* upload video ([ac8e6f8](https://github.com/wangeditor-team/wangEditor/commit/ac8e6f8b5258e593714676a6f6be359ba525833c)) diff --git a/packages/table-module/README.md b/packages/table-module/README.md deleted file mode 100644 index 80b8b8e8f..000000000 --- a/packages/table-module/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# wangEditor table-module - -Table module built in [wangEditor](https://www.wangeditor.com/) by default. diff --git a/packages/table-module/__tests__/elem-to-html.test.ts b/packages/table-module/__tests__/elem-to-html.test.ts deleted file mode 100644 index e7d6964bc..000000000 --- a/packages/table-module/__tests__/elem-to-html.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @description table menu test - * @author luochao - */ - -import { - tableCellToHtmlConf, - tableToHtmlConf, - tableRowToHtmlConf, -} from '../src/module/elem-to-html' -import * as core from '@wangeditor/core' -import { Ancestor } from 'slate' - -describe('TableModule module', () => { - describe('module elem-to-html', () => { - test('tableCellToHtmlConf should return object that include "type" and "elemToHtml" property', () => { - expect(tableCellToHtmlConf.type).toBe('table-cell') - expect(typeof tableCellToHtmlConf.elemToHtml).toBe('function') - }) - - test('tableCellToHtmlConf elemToHtml should throw Error if tableCell do not have parent', () => { - const element = { - type: 'table-cell', - children: [], - } - - try { - tableCellToHtmlConf.elemToHtml(element, '123') - } catch (err) { - expect(err.message).toBe( - `Cannot get table row node by cell node ${JSON.stringify(element)}` - ) - } - }) - - test('tableCellToHtmlConf elemToHtml should throw Error if tableRow do not have parent', () => { - const element = { - type: 'table-cell', - children: [], - } - jest - .spyOn(core.DomEditor, 'getParentNode') - .mockReturnValue({ type: 'table-row', children: [{ text: '' }] } as any) - try { - tableCellToHtmlConf.elemToHtml(element, '123') - } catch (err) { - expect(err.message).toBe(`Cannot get table node by cell node ${JSON.stringify(element)}`) - } - }) - - test('tableCellToHtmlConf elemToHtml should return html element td string', () => { - const element = { - type: 'table-cell', - children: [], - } - jest - .spyOn(core.DomEditor, 'getParentNode') - .mockReturnValueOnce({ type: 'table-row', children: [{ text: '' }] } as any) - .mockReturnValueOnce({ type: 'table', children: [{ text: '' }] } as Ancestor) - - const res = tableCellToHtmlConf.elemToHtml(element, '123') - expect(res).toBe('123') - }) - - test('tableRowToHtmlConf should return object that include "type" and "elemToHtml" property', () => { - expect(tableRowToHtmlConf.type).toBe('table-row') - expect(typeof tableRowToHtmlConf.elemToHtml).toBe('function') - }) - - test('tableRowToHtmlConf elemToHtml should return html table row string', () => { - const element = { - type: 'table-row', - children: [], - } - const res = tableRowToHtmlConf.elemToHtml(element, '123') - expect(res).toBe('123') - }) - - test('tableToHtmlConf should return object that include "type" and "elemToHtml" property', () => { - expect(tableToHtmlConf.type).toBe('table') - expect(typeof tableToHtmlConf.elemToHtml).toBe('function') - }) - - test('tableToHtmlConf should return html table string', () => { - const element = { - type: 'table', - children: [], - } - const res = tableToHtmlConf.elemToHtml(element, '123') - expect(res).toBe('
                123
                ') - }) - - test('tableToHtmlConf should return html table string with full width style if element is set fullWith value true', () => { - const element = { - type: 'table', - width: '100%', - children: [], - } - const res = tableToHtmlConf.elemToHtml(element, '123') - expect(res).toBe('
                123
                ') - }) - }) -}) diff --git a/packages/table-module/__tests__/menu/delete-col.test.ts b/packages/table-module/__tests__/menu/delete-col.test.ts deleted file mode 100644 index 61d07aa6b..000000000 --- a/packages/table-module/__tests__/menu/delete-col.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import DeleteCol from '../../src/module/menu/DeleteCol' -import createEditor from '../../../../tests/utils/create-editor' -import { DEL_COL_SVG } from '../../src/constants/svg' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Delete Col Menu', () => { - test('it should create DeleteCol object', () => { - const deleteColMenu = new DeleteCol() - expect(typeof deleteColMenu).toBe('object') - expect(deleteColMenu.tag).toBe('button') - expect(deleteColMenu.iconSvg).toBe(DEL_COL_SVG) - expect(deleteColMenu.title).toBe(locale.tableModule.deleteCol) - }) - - test('it should get empty string if invoke getValue method', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - expect(deleteColMenu.getValue(editor)).toBe('') - }) - - test('it should get falsy value if invoke isActive method', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - expect(deleteColMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - editor.selection = null - expect(deleteColMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor selection is collapsed', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) - - expect(deleteColMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is not table cell', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(deleteColMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is table cell', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - expect(deleteColMenu.isDisabled(editor)).toBeFalsy() - }) - - test('exec should return directly if menu is disabled', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - setEditorSelection(editor, null) - - expect(deleteColMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should invoke removeNodes method to remove whole table if menu is not disabled and table col length less than 1', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - - jest.spyOn(deleteColMenu, 'isDisabled').mockImplementation(() => false) - jest.spyOn(core.DomEditor, 'getParentNode').mockImplementation(() => ({ - type: 'table-col', - children: [], - })) - - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - [0, 1], - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - const removeNodesFn = jest.fn() - jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) - - deleteColMenu.exec(editor, '') - expect(removeNodesFn).toBeCalled() - }) - - test('exec should invoke removeNodes method to remove all table cells if menu is not disabled and table col length greater than 1', () => { - const deleteColMenu = new DeleteCol() - const editor = createEditor() - - jest.spyOn(deleteColMenu, 'isDisabled').mockImplementation(() => false) - jest.spyOn(core.DomEditor, 'getParentNode').mockImplementation(() => ({ - type: 'table-row', - children: [ - { - type: 'table-col', - children: [{ type: 'table-cell', children: [] }], - }, - { - type: 'table-col', - children: [{ type: 'table-cell', children: [] }], - }, - ], - })) - - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - [0, 1], - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - jest.spyOn(core.DomEditor, 'findPath').mockImplementation(() => [0, 1] as slate.Path) - const removeNodesFn = jest.fn() - jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) - - deleteColMenu.exec(editor, '') - expect(removeNodesFn).toBeCalledTimes(2) - }) -}) diff --git a/packages/table-module/__tests__/menu/delete-row.test.ts b/packages/table-module/__tests__/menu/delete-row.test.ts deleted file mode 100644 index 1e0499218..000000000 --- a/packages/table-module/__tests__/menu/delete-row.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import DeleteRow from '../../src/module/menu/DeleteRow' -import createEditor from '../../../../tests/utils/create-editor' -import { DEL_ROW_SVG } from '../../src/constants/svg' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Delete Row Menu', () => { - test('it should create DeleteRow object', () => { - const deleteRowMenu = new DeleteRow() - expect(typeof deleteRowMenu).toBe('object') - expect(deleteRowMenu.tag).toBe('button') - expect(deleteRowMenu.iconSvg).toBe(DEL_ROW_SVG) - expect(deleteRowMenu.title).toBe(locale.tableModule.deleteRow) - }) - - test('it should get empty string if invoke getValue method', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - expect(deleteRowMenu.getValue(editor)).toBe('') - }) - - test('it should get falsy value if invoke isActive method', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - expect(deleteRowMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - editor.selection = null - expect(deleteRowMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor selection is collapsed', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) - - expect(deleteRowMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is not table cell', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(deleteRowMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is table cell', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - expect(deleteRowMenu.isDisabled(editor)).toBeFalsy() - }) - - test('exec should return directly if menu is disabled', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - setEditorSelection(editor, null) - - expect(deleteRowMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should invoke removeNodes method to remove whole table if menu is not disabled and table row length less than 1', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - - jest.spyOn(deleteRowMenu, 'isDisabled').mockImplementation(() => false) - jest.spyOn(core.DomEditor, 'getParentNode').mockImplementation(() => ({ - type: 'table', - children: [ - { - type: 'table-row', - children: [], - }, - ], - })) - - const path = [0, 1] - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - path, - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - const removeNodesFn = jest.fn() - jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) - - deleteRowMenu.exec(editor, '') - expect(removeNodesFn).toBeCalled() - }) - - test('exec should invoke removeNodes method to remove current row if menu is not disabled and table row length greater than 1', () => { - const deleteRowMenu = new DeleteRow() - const editor = createEditor() - - jest.spyOn(deleteRowMenu, 'isDisabled').mockImplementation(() => false) - jest.spyOn(core.DomEditor, 'getParentNode').mockImplementation(() => ({ - type: 'table', - children: [ - { - type: 'table-row', - children: [], - }, - { - type: 'table-row', - children: [], - }, - ], - })) - - const path = [0, 1] - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - path, - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - const removeNodesFn = jest.fn() - jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) - - deleteRowMenu.exec(editor, '') - expect(removeNodesFn).toBeCalledWith(editor, { at: path }) - }) -}) diff --git a/packages/table-module/__tests__/menu/delete-table.test.ts b/packages/table-module/__tests__/menu/delete-table.test.ts deleted file mode 100644 index 0e5a91cca..000000000 --- a/packages/table-module/__tests__/menu/delete-table.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import DeleteTable from '../../src/module/menu/DeleteTable' -import createEditor from '../../../../tests/utils/create-editor' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Delete Table Menu', () => { - test('it should create DeleteTable object', () => { - const deleteTableMenu = new DeleteTable() - expect(typeof deleteTableMenu).toBe('object') - expect(deleteTableMenu.tag).toBe('button') - expect(deleteTableMenu.title).toBe(locale.tableModule.deleteTable) - }) - - test('it should get empty string if invoke getValue method', () => { - const deleteTableMenu = new DeleteTable() - const editor = createEditor() - expect(deleteTableMenu.getValue(editor)).toBe('') - }) - - test('it should get falsy value if invoke isActive method', () => { - const deleteTableMenu = new DeleteTable() - const editor = createEditor() - expect(deleteTableMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const deleteTableMenu = new DeleteTable() - const editor = createEditor() - editor.selection = null - expect(deleteTableMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is not table cell', () => { - const deleteTableMenu = new DeleteTable() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(deleteTableMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is table cell', () => { - const deleteTableMenu = new DeleteTable() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - expect(deleteTableMenu.isDisabled(editor)).toBeFalsy() - }) - - test('exec should return directly if menu is disabled', () => { - const deleteTableMenu = new DeleteTable() - const editor = createEditor() - setEditorSelection(editor, null) - - expect(deleteTableMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should invoke removeNodes method to remove whole table if menu is not disabled', () => { - const deleteTableMenu = new DeleteTable() - const editor = createEditor() - - jest.spyOn(deleteTableMenu, 'isDisabled').mockReturnValue(false) - const fn = jest.fn() - - jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(fn) - - deleteTableMenu.exec(editor, '') - expect(fn).toBeCalled() - }) -}) diff --git a/packages/table-module/__tests__/menu/full-width.test.ts b/packages/table-module/__tests__/menu/full-width.test.ts deleted file mode 100644 index 9e70e8fa0..000000000 --- a/packages/table-module/__tests__/menu/full-width.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import FullWidth from '../../src/module/menu/FullWidth' -import createEditor from '../../../../tests/utils/create-editor' -import { FULL_WIDTH_SVG } from '../../src/constants/svg' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Full Width Menu', () => { - test('it should create FullWidth object', () => { - const fullWidthMenu = new FullWidth() - expect(typeof fullWidthMenu).toBe('object') - expect(fullWidthMenu.tag).toBe('button') - expect(fullWidthMenu.iconSvg).toBe(FULL_WIDTH_SVG) - expect(fullWidthMenu.title).toBe(locale.tableModule.widthAuto) - }) - - test('getValue should get falsy value if editor selected node is not table', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - expect(fullWidthMenu.getValue(editor)).toBeFalsy() - }) - - test(`getValue should get truthy value if editor selected table's width is 100%`, () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - - jest - .spyOn(core.DomEditor, 'getSelectedNodeByType') - .mockImplementation(() => ({ width: '100%' } as any)) - expect(fullWidthMenu.getValue(editor)).toBeTruthy() - }) - - test('isActive should get falsy value if editor selected node is not table', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(fullWidthMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - editor.selection = null - expect(fullWidthMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor selection is collapsed', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) - - expect(fullWidthMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is not table cell', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(fullWidthMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is table cell', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - expect(fullWidthMenu.isDisabled(editor)).toBeFalsy() - }) - - test('exec should return directly if menu is disabled', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - setEditorSelection(editor, null) - - expect(fullWidthMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should invoke setNodes with props if menu is not disabled', () => { - const fullWidthMenu = new FullWidth() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - const fn = jest.fn() - jest.spyOn(slate.Transforms, 'setNodes').mockImplementation(fn) - - fullWidthMenu.exec(editor, true) - - expect(fn).toBeCalled() - }) -}) diff --git a/packages/table-module/__tests__/menu/insert-col.test.ts b/packages/table-module/__tests__/menu/insert-col.test.ts deleted file mode 100644 index 7ad93d278..000000000 --- a/packages/table-module/__tests__/menu/insert-col.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import InsertCol from '../../src/module/menu/InsertCol' -import createEditor from '../../../../tests/utils/create-editor' -import { ADD_COL_SVG } from '../../src/constants/svg' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Insert Col Menu', () => { - test('it should create InsertCol object', () => { - const insertColMenu = new InsertCol() - expect(typeof insertColMenu).toBe('object') - expect(insertColMenu.tag).toBe('button') - expect(insertColMenu.iconSvg).toBe(ADD_COL_SVG) - expect(insertColMenu.title).toBe(locale.tableModule.insertCol) - }) - - test('it should get empty string if invoke getValue method', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - expect(insertColMenu.getValue(editor)).toBe('') - }) - - test('it should get falsy value if invoke isActive method', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - expect(insertColMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - editor.selection = null - expect(insertColMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor selection is collapsed', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) - - expect(insertColMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is not table cell', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(insertColMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is table cell', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - expect(insertColMenu.isDisabled(editor)).toBeFalsy() - }) - - test('exec should return directly if menu is disabled', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - setEditorSelection(editor, null) - - expect(insertColMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should return directly if current selected node parent is null', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - - jest.spyOn(insertColMenu, 'isDisabled').mockReturnValue(false) - - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - [0, 1], - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - jest.spyOn(core.DomEditor, 'getParentNode').mockReturnValue(null) - - expect(insertColMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should return directly if current selected table row parent is null', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - - jest.spyOn(insertColMenu, 'isDisabled').mockReturnValue(false) - - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - [0, 1], - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - jest - .spyOn(core.DomEditor, 'getParentNode') - .mockReturnValue({} as any) - .mockReturnValue(null) - - expect(insertColMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should return directly if current selected table row parent is null', () => { - const insertColMenu = new InsertCol() - const editor = createEditor() - - jest.spyOn(insertColMenu, 'isDisabled').mockReturnValue(false) - - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - [0, 1], - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - jest - .spyOn(core.DomEditor, 'getParentNode') - .mockReturnValue({} as any) - .mockReturnValue({ - type: 'table', - children: [ - { - type: 'table-row', - children: [ - { - type: 'table-cell', - children: [], - }, - { - type: 'table-cell', - children: [], - }, - ], - }, - { - type: 'table-row', - children: [ - { - type: 'table-cell', - children: [], - }, - { - type: 'table-cell', - children: [], - }, - ], - }, - ], - } as any) - - jest.spyOn(core.DomEditor, 'findPath').mockReturnValue([0, 1]) - const insertNodesFn = jest.fn() - jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(insertNodesFn) - - insertColMenu.exec(editor, '') - - expect(insertNodesFn).toBeCalledWith( - editor, - { type: 'table-cell', children: [{ text: '' }] }, - { at: [0, 1] } - ) - }) -}) diff --git a/packages/table-module/__tests__/menu/insert-row.test.ts b/packages/table-module/__tests__/menu/insert-row.test.ts deleted file mode 100644 index 89abf0191..000000000 --- a/packages/table-module/__tests__/menu/insert-row.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import InsertRow from '../../src/module/menu/InsertRow' -import createEditor from '../../../../tests/utils/create-editor' -import { ADD_ROW_SVG } from '../../src/constants/svg' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Insert Row Menu', () => { - test('it should create InsertRow object', () => { - const insertRowMenu = new InsertRow() - expect(typeof insertRowMenu).toBe('object') - expect(insertRowMenu.tag).toBe('button') - expect(insertRowMenu.iconSvg).toBe(ADD_ROW_SVG) - expect(insertRowMenu.title).toBe(locale.tableModule.insertRow) - }) - - test('it should get empty string if invoke getValue method', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - expect(insertRowMenu.getValue(editor)).toBe('') - }) - - test('it should get falsy value if invoke isActive method', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - expect(insertRowMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - editor.selection = null - expect(insertRowMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor selection is collapsed', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) - - expect(insertRowMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is not table cell', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(insertRowMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is table cell', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - expect(insertRowMenu.isDisabled(editor)).toBeFalsy() - }) - - test('exec should return directly if menu is disabled', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - setEditorSelection(editor, null) - - expect(insertRowMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should invoke insertNodes method to remove whole table if menu is not disabled', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - - jest.spyOn(insertRowMenu, 'isDisabled').mockReturnValue(false) - jest.spyOn(core.DomEditor, 'getParentNode').mockImplementation(() => ({ - type: 'table-row', - children: [ - { - type: 'table-cell', - children: [], - }, - { - type: 'table-cell', - children: [], - }, - ], - })) - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - [0, 1], - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - const insertNodesFn = jest.fn() - jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(insertNodesFn) - - insertRowMenu.exec(editor, '') - expect(insertNodesFn).toBeCalled() - }) - - test('exec should return directly if current selected row that does not has children', () => { - const insertRowMenu = new InsertRow() - const editor = createEditor() - - jest.spyOn(insertRowMenu, 'isDisabled').mockReturnValue(false) - jest.spyOn(core.DomEditor, 'getParentNode').mockImplementation(() => ({ - type: 'table-row', - children: [], - })) - const fn = function* a() { - yield [ - { - type: 'table-cell', - children: [], - } as slate.Element, - [0, 1], - ] as slate.NodeEntry - } - jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) - const insertNodesFn = jest.fn() - jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(insertNodesFn) - - expect(insertRowMenu.exec(editor, '')).toBeUndefined() - expect(insertNodesFn).not.toBeCalled() - }) -}) diff --git a/packages/table-module/__tests__/menu/insert-table.test.ts b/packages/table-module/__tests__/menu/insert-table.test.ts deleted file mode 100644 index 804ec0823..000000000 --- a/packages/table-module/__tests__/menu/insert-table.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import InsertTable from '../../src/module/menu/InsertTable' -import createEditor from '../../../../tests/utils/create-editor' -import { TABLE_SVG } from '../../src/constants/svg' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' -import $, { DOMElement } from '../../src/utils/dom' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Insert Table Menu', () => { - test('it should create InsertTable object', () => { - const insertTableMenu = new InsertTable() - expect(typeof insertTableMenu).toBe('object') - expect(insertTableMenu.tag).toBe('button') - expect(insertTableMenu.iconSvg).toBe(TABLE_SVG) - expect(insertTableMenu.title).toBe(locale.tableModule.insertTable) - }) - - test('it should get empty string if invoke getValue method', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - expect(insertTableMenu.getValue(editor)).toBe('') - }) - - test('it should get falsy value if invoke isActive method', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - expect(insertTableMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - editor.selection = null - expect(insertTableMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor selection is collapsed', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) - - expect(insertTableMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is contains pre node', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest - .spyOn(core.DomEditor, 'getSelectedElems') - .mockImplementation(() => [{ type: 'pre', children: [] }]) - - expect(insertTableMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is contains table node', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest - .spyOn(core.DomEditor, 'getSelectedElems') - .mockImplementation(() => [{ type: 'table', children: [] }]) - - expect(insertTableMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is contains void node', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest - .spyOn(core.DomEditor, 'getSelectedElems') - .mockImplementation(() => [{ type: 'image', children: [] }]) - - jest.spyOn(editor, 'isVoid').mockImplementation(() => true) - - expect(insertTableMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is valid', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest - .spyOn(core.DomEditor, 'getSelectedElems') - .mockImplementation(() => [{ type: 'paragraph', children: [] }]) - - expect(insertTableMenu.isDisabled(editor)).toBeFalsy() - }) - - test('getPanelContentElem should return table panel dom', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - - expect(insertTableMenu.getPanelContentElem(editor) instanceof DOMElement).toBeTruthy() - expect(insertTableMenu.getPanelContentElem(editor).className).toBe('w-e-panel-content-table') - }) - - test('it should invoke insertNodes method if click panel td node', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - - const tablePanel = insertTableMenu.getPanelContentElem(editor) - const tdEl = $(tablePanel).find('td')[0] - - const fn = jest.fn() - jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(fn) - - tdEl.dispatchEvent( - new MouseEvent('click', { - view: window, - bubbles: true, - cancelable: true, - }) - ) - - expect(fn).toBeCalled() - }) - - test('it should add active class if mouse enter panel td node', () => { - const insertTableMenu = new InsertTable() - const editor = createEditor() - - const tablePanel = insertTableMenu.getPanelContentElem(editor) - const tdEl = $(tablePanel).find('td')[0] - - expect(tdEl.className).toBe('') - - tdEl.dispatchEvent( - new MouseEvent('mouseenter', { - view: window, - bubbles: true, - cancelable: true, - }) - ) - - expect(tdEl.className).toBe('active') - }) -}) diff --git a/packages/table-module/__tests__/menu/table-header.test.ts b/packages/table-module/__tests__/menu/table-header.test.ts deleted file mode 100644 index 661b6a2f8..000000000 --- a/packages/table-module/__tests__/menu/table-header.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import TableHeader from '../../src/module/menu/TableHeader' -import createEditor from '../../../../tests/utils/create-editor' -import { TABLE_HEADER_SVG } from '../../src/constants/svg' -import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor/core' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} -describe('Table Module Table Header Menu', () => { - test('it should create TableHeader object', () => { - const tableHeaderMenu = new TableHeader() - expect(typeof tableHeaderMenu).toBe('object') - expect(tableHeaderMenu.tag).toBe('button') - expect(tableHeaderMenu.iconSvg).toBe(TABLE_HEADER_SVG) - expect(tableHeaderMenu.title).toBe(locale.tableModule.header) - }) - - test('getValue should get falsy value if editor selected node is not table', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(tableHeaderMenu.getValue(editor)).toBeFalsy() - }) - - test('isActive should get falsy value if editor selected node is not table', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(tableHeaderMenu.isActive(editor)).toBeFalsy() - }) - - test('isDisabled should get truthy value if editor selection is null', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - editor.selection = null - expect(tableHeaderMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor selection is collapsed', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) - - expect(tableHeaderMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get truthy value if editor current selected node is not table cell', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(tableHeaderMenu.isDisabled(editor)).toBeTruthy() - }) - - test('isDisabled should get falsy value if editor current selected node is table cell', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) - - expect(tableHeaderMenu.isDisabled(editor)).toBeFalsy() - }) - - test('exec should return directly if menu is disabled', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - setEditorSelection(editor, null) - - expect(tableHeaderMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should return directly if current selected node is not table', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - - jest.spyOn(tableHeaderMenu, 'isDisabled').mockReturnValue(false) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) - - expect(tableHeaderMenu.exec(editor, '')).toBeUndefined() - }) - - test('exec should invoke setNodes to set table header if current selected node table', () => { - const tableHeaderMenu = new TableHeader() - const editor = createEditor() - - jest.spyOn(tableHeaderMenu, 'isDisabled').mockReturnValue(false) - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({ - type: 'table', - children: [ - { - type: 'table-row', - children: [ - { type: 'table-cell', children: [] }, - { type: 'table-cell', children: [] }, - ], - }, - { - type: 'table-row', - children: [ - { type: 'table-cell', children: [] }, - { type: 'table-cell', children: [] }, - ], - }, - ], - })) - - const fn = jest.fn() - jest.spyOn(slate.Transforms, 'setNodes').mockImplementation(fn) - jest.spyOn(core.DomEditor, 'findPath').mockImplementation(() => [0, 1]) - - tableHeaderMenu.exec(editor, '') - - expect(fn).toBeCalledTimes(2) - }) -}) diff --git a/packages/table-module/__tests__/parse-html.test.ts b/packages/table-module/__tests__/parse-html.test.ts deleted file mode 100644 index 838bbb8e7..000000000 --- a/packages/table-module/__tests__/parse-html.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../tests/utils/create-editor' -import { preParseTableHtmlConf } from '../src/module/pre-parse-html' -import { - parseCellHtmlConf, - parseRowHtmlConf, - parseTableHtmlConf, -} from '../src/module/parse-elem-html' - -describe('table - pre parse html', () => { - it('pre parse', () => { - const $table = $('
                hello
                ') - - // match selector - expect($table[0].matches(preParseTableHtmlConf.selector)).toBeTruthy() - - // pre parse - const res = preParseTableHtmlConf.preParseHtml($table[0]) - expect(res.outerHTML).toBe('
                hello
                ') - }) - - it('it should return fake element if pass fake table element', () => { - const fakeTable = $('
                hello
                ') - - // pre parse - const res = preParseTableHtmlConf.preParseHtml(fakeTable[0]) - expect(res.outerHTML).toBe('
                hello
                ') - }) - - it('it should return directly if pass table element without body', () => { - const table = $('
                hello
                ') - - // pre parse - const res = preParseTableHtmlConf.preParseHtml(table[0]) - expect(res.outerHTML).toBe('
                hello
                ') - }) -}) - -describe('table - parse html', () => { - const editor = createEditor() - - it('table cell', () => { - const $cell1 = $('hello world') - expect($cell1[0].matches(parseCellHtmlConf.selector)).toBeTruthy() - expect(parseCellHtmlConf.parseElemHtml($cell1[0], [], editor)).toEqual({ - type: 'table-cell', - isHeader: false, - colSpan: 1, - rowSpan: 1, - width: 'auto', - children: [{ text: 'hello world' }], - }) - - const $cell2 = $('') - const children = [{ text: 'hello ' }, { text: 'world', bold: true }] - expect($cell2[0].matches(parseCellHtmlConf.selector)).toBeTruthy() - expect(parseCellHtmlConf.parseElemHtml($cell2[0], children, editor)).toEqual({ - type: 'table-cell', - isHeader: true, - colSpan: 1, - rowSpan: 1, - width: 'auto', - children, - }) - }) - - it('table row', () => { - const $tr = $('') - const children = [{ type: 'table-cell', children: [{ text: 'hello world' }] }] - - expect($tr[0].matches(parseRowHtmlConf.selector)).toBeTruthy() - - expect(parseRowHtmlConf.parseElemHtml($tr[0], children, editor)).toEqual({ - type: 'table-row', - children, - }) - }) - - it('table', () => { - const $table = $('
                ') - const children = [ - { - type: 'table-row', - children: [{ type: 'table-cell', children: [{ text: 'hello world' }] }], - }, - ] - - expect($table[0].matches(parseTableHtmlConf.selector)).toBeTruthy() - - expect(parseTableHtmlConf.parseElemHtml($table[0], children, editor)).toEqual({ - type: 'table', - width: '100%', - children, - }) - }) -}) diff --git a/packages/table-module/__tests__/plugin.test.ts b/packages/table-module/__tests__/plugin.test.ts deleted file mode 100644 index db7c30f1f..000000000 --- a/packages/table-module/__tests__/plugin.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @description table menu test - * @author luochao - */ - -import createEditor from '../../../tests/utils/create-editor' -import withTable from '../src/module/plugin' -import * as core from '@wangeditor/core' -import * as slate from 'slate' - -describe('TableModule module', () => { - describe('module plugin', () => { - test('use withTable plugin when break line not split node', () => { - const editor = createEditor() - const newEditor = withTable(editor) - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockReturnValue({ - type: 'table', - children: [{ text: '' }], - } as slate.Element) - - const mockFn = jest.fn() - newEditor.insertText = mockFn - - newEditor.insertBreak() - - expect(mockFn).toBeCalledWith('\n') - }) - - test('use withTable plugin when insertData should insertText to cell', () => { - const editor = createEditor() - const newEditor = withTable(editor) - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockReturnValue({ - type: 'table', - children: [{ text: '' }], - } as slate.Element) - - const mockFn = jest.fn() - slate.Editor.insertText = mockFn - - newEditor.insertData({ getData: () => 'test' } as unknown as DataTransfer) - - expect(mockFn).toBeCalled() - }) - - test('use withTable plugin when insertData should invoke original insertData if selection not in table node', () => { - const editor = createEditor() - const mockInsertDataFn = jest.fn() - editor.insertData = mockInsertDataFn - - const newEditor = withTable(editor) - - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockReturnValue(null) - - newEditor.insertData({} as DataTransfer) - - expect(mockInsertDataFn).toBeCalled() - }) - }) -}) diff --git a/packages/table-module/__tests__/render-elem.test.ts b/packages/table-module/__tests__/render-elem.test.ts deleted file mode 100644 index c2f53d0a6..000000000 --- a/packages/table-module/__tests__/render-elem.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import createEditor from '../../../tests/utils/create-editor' -import { renderTableConf, renderTableCellConf, renderTableRowConf } from '../src/module/render-elem' - -describe('table module - render elem', () => { - const editor = createEditor() - - it('render table td elem', () => { - expect(renderTableCellConf.type).toBe('table-cell') - - const elem = { type: 'table-cell', children: [] } - const vnode = renderTableCellConf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('td') - }) - - // // isHeader 必须在第一行才能生效,该 case 运行报错,暂注释 - wangfupeng 2022.05.20 - // it('render table th elem', () => { - // const cell = { type: 'table-cell', children: [], isHeader: true } - // const row = { type: 'table-row', children: [cell] } - // const table = { type: 'table', children: [row] } - // editor.insertNode(table) - // const vnode = renderTableCellConf.renderElem(cell, null, editor) - // expect(vnode.sel).toBe('th') - // }) - - it('render table row elem', () => { - expect(renderTableRowConf.type).toBe('table-row') - - const elem = { type: 'table-row', children: [] } - const vnode = renderTableRowConf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('tr') - }) - - it('render table elem', () => { - expect(renderTableConf.type).toBe('table') - - const elem = { type: 'table', children: [] } - const containerVnode = renderTableConf.renderElem(elem, null, editor) as any - expect(containerVnode.sel).toBe('div') - const tableVnode = containerVnode.children[0] as any - expect(tableVnode.sel).toBe('table') - }) - - it('render table elem with full with', () => { - const elem = { type: 'table', children: [], width: '100%' } - const containerVnode = renderTableConf.renderElem(elem, null, editor) as any - const tableVnode = containerVnode.children[0] as any - expect(tableVnode.data.width).toBe('100%') - }) -}) diff --git a/packages/table-module/package.json b/packages/table-module/package.json deleted file mode 100644 index a84e11db4..000000000 --- a/packages/table-module/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@wangeditor/table-module", - "version": "1.1.4", - "description": "wangEditor table module", - "author": "wangfupeng1988 ", - "contributors": [], - "homepage": "https://github.com/wangeditor-team/wangEditor#readme", - "license": "MIT", - "types": "dist/table-module/src/index.d.ts", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "browser": { - "./dist/index.js": "./dist/index.js", - "./dist/index.esm.js": "./dist/index.esm.js" - }, - "directories": { - "lib": "dist", - "test": "__tests__" - }, - "files": [ - "dist" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.com/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wangeditor-team/wangEditor.git" - }, - "scripts": { - "test": "jest", - "test-c": "jest --coverage", - "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js", - "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", - "build": "cross-env NODE_ENV=production rollup -c rollup.config.js", - "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js", - "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js" - }, - "bugs": { - "url": "https://github.com/wangeditor-team/wangEditor/issues" - }, - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } -} diff --git a/packages/table-module/rollup.config.js b/packages/table-module/rollup.config.js deleted file mode 100644 index d51fa5cf8..000000000 --- a/packages/table-module/rollup.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' -import pkg from './package.json' - -const name = 'WangEditorTableModule' - -const configList = [] - -// esm -const esmConf = createRollupConfig({ - output: { - file: pkg.module, - format: 'esm', - name, - }, -}) -configList.push(esmConf) - -// umd -const umdConf = createRollupConfig({ - output: { - file: pkg.main, - format: 'umd', - name, - }, -}) -configList.push(umdConf) - -export default configList diff --git a/packages/table-module/src/assets/index.less b/packages/table-module/src/assets/index.less deleted file mode 100644 index 38690d8b9..000000000 --- a/packages/table-module/src/assets/index.less +++ /dev/null @@ -1,50 +0,0 @@ -@import "../../../vars.less"; - -.w-e-text-container [data-slate-editor] { - .table-container { - width: 100%; - overflow-x: auto; - border: 1px dashed var(--w-e-textarea-border-color); - padding: 10px; - border-radius: 5px; - margin-top: 10px; - } - - table { - border-collapse: collapse; - - td,th { - border: 1px solid @textarea-border-color; - padding: 3px 5px; - min-width: 30px; - text-align: left; - line-height: 1.5; - } - th { - background-color: @textarea-slight-bg-color; - text-align: center; - font-weight: bold; - } - } -} - -// --------------------------------- 分割线 --------------------------------- - -.w-e-panel-content-table { - background-color: @toolbar-bg-color; - - table { - border-collapse: collapse; - } - - td { - border: 1px solid @toolbar-border-color; - padding: 3px 5px; - width: 20px; - height: 15px; - cursor: pointer; - } - td.active { - background-color: @toolbar-active-bg-color; - } -} diff --git a/packages/table-module/src/constants/svg.ts b/packages/table-module/src/constants/svg.ts deleted file mode 100644 index 7a9e2191a..000000000 --- a/packages/table-module/src/constants/svg.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @description icon svg - * @author wangfupeng - */ - -/** - * 【注意】svg 字符串的长度 ,否则会导致代码体积过大 - * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293 - * 找不到再从 iconfont.com 搜索 - */ - -// 表格 -export const TABLE_SVG = - '' - -// 垃圾桶(删除) -export const TRASH_SVG = - '' - -// 表格 添加行 -export const ADD_ROW_SVG = - '' - -// 表格 删除行 -export const DEL_ROW_SVG = - '' - -// 表格 添加列 -export const ADD_COL_SVG = - '' - -// 表格 删除列 -export const DEL_COL_SVG = - '' - -// 表头 -export const TABLE_HEADER_SVG = - '' - -// 宽度 -export const FULL_WIDTH_SVG = - '' diff --git a/packages/table-module/src/index.ts b/packages/table-module/src/index.ts deleted file mode 100644 index 1fec86c6c..000000000 --- a/packages/table-module/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @description table entry - * @author wangfupeng - */ - -import './assets/index.less' - -// 配置多语言 -import './locale/index' - -import wangEditorTableModule from './module/index' -export default wangEditorTableModule diff --git a/packages/table-module/src/locale/en.ts b/packages/table-module/src/locale/en.ts deleted file mode 100644 index bfa7a06e6..000000000 --- a/packages/table-module/src/locale/en.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @description i18n en - * @author wangfupeng - */ - -export default { - tableModule: { - deleteCol: 'Delete column', - deleteRow: 'Delete row', - deleteTable: 'Delete table', - widthAuto: 'Width auto', - insertCol: 'Insert column', - insertRow: 'Insert row', - insertTable: 'Insert table', - header: 'Header', - }, -} diff --git a/packages/table-module/src/locale/index.ts b/packages/table-module/src/locale/index.ts deleted file mode 100644 index 22f6ec6cb..000000000 --- a/packages/table-module/src/locale/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n entry - * @author wangfupeng - */ - -import { i18nAddResources } from '@wangeditor/core' -import enResources from './en' -import zhResources from './zh-CN' - -i18nAddResources('en', enResources) -i18nAddResources('zh-CN', zhResources) diff --git a/packages/table-module/src/locale/zh-CN.ts b/packages/table-module/src/locale/zh-CN.ts deleted file mode 100644 index c88ddfa8b..000000000 --- a/packages/table-module/src/locale/zh-CN.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @description i18n zh-CN - * @author wangfupeng - */ - -export default { - tableModule: { - deleteCol: '删除列', - deleteRow: '删除行', - deleteTable: '删除表格', - widthAuto: '宽度自适应', - insertCol: '插入列', - insertRow: '插入行', - insertTable: '插入表格', - header: '表头', - }, -} diff --git a/packages/table-module/src/module/custom-types.ts b/packages/table-module/src/module/custom-types.ts deleted file mode 100644 index 33e73ff91..000000000 --- a/packages/table-module/src/module/custom-types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @description 自定义 element - * @author wangfupeng - */ - -import { Text } from 'slate' - -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts - -export type TableCellElement = { - type: 'table-cell' - isHeader?: boolean // td/th 只作用于第一行 - colSpan?: number - rowSpan?: number - width?: string // 只作用于第一行(尚未考虑单元格合并!) - children: Text[] -} - -export type TableRowElement = { - type: 'table-row' - children: TableCellElement[] -} - -export type TableElement = { - type: 'table' - width: string - children: TableRowElement[] -} diff --git a/packages/table-module/src/module/elem-to-html.ts b/packages/table-module/src/module/elem-to-html.ts deleted file mode 100644 index 8a1b69199..000000000 --- a/packages/table-module/src/module/elem-to-html.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @description to html - * @author wangfupeng - */ - -import { Element } from 'slate' -import { TableCellElement, TableRowElement, TableElement } from './custom-types' - -function tableToHtml(elemNode: Element, childrenHtml: string): string { - const { width = 'auto' } = elemNode as TableElement - - return `${childrenHtml}
                ` -} - -function tableRowToHtml(elem: Element, childrenHtml: string): string { - return `${childrenHtml}` -} - -function tableCellToHtml(cellNode: Element, childrenHtml: string): string { - const { - colSpan = 1, - rowSpan = 1, - isHeader = false, - width = 'auto', - } = cellNode as TableCellElement - const tag = isHeader ? 'th' : 'td' - return `<${tag} colSpan="${colSpan}" rowSpan="${rowSpan}" width="${width}">${childrenHtml}` -} - -export const tableToHtmlConf = { - type: 'table', - elemToHtml: tableToHtml, -} - -export const tableRowToHtmlConf = { - type: 'table-row', - elemToHtml: tableRowToHtml, -} - -export const tableCellToHtmlConf = { - type: 'table-cell', - elemToHtml: tableCellToHtml, -} diff --git a/packages/table-module/src/module/helpers.ts b/packages/table-module/src/module/helpers.ts deleted file mode 100644 index d14e4993d..000000000 --- a/packages/table-module/src/module/helpers.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @description table menu helpers - * @author wangfupeng - */ - -import { DomEditor, IDomEditor } from '@wangeditor/core' -import { TableElement, TableCellElement } from './custom-types' - -/** - * 获取第一行所有 cells - * @param tableNode table node - */ -export function getFirstRowCells(tableNode: TableElement): TableCellElement[] { - const rows = tableNode.children || [] // 所有行 - if (rows.length === 0) return [] - const firstRow = rows[0] || {} // 第一行 - const cells = firstRow.children || [] // 第一行所有 cell - return cells -} - -/** - * 表格是否带有表头? - * @param tableNode table node - */ -export function isTableWithHeader(tableNode: TableElement): boolean { - const firstRowCells = getFirstRowCells(tableNode) - return firstRowCells.every(cell => !!cell.isHeader) -} - -/** - * 单元格是否在第一行 - * @param editor editor - * @param cellNode cell node - */ -export function isCellInFirstRow(editor: IDomEditor, cellNode: TableCellElement): boolean { - const rowNode = DomEditor.getParentNode(editor, cellNode) - if (rowNode == null) return false - const tableNode = DomEditor.getParentNode(editor, rowNode) - if (tableNode == null) return false - - const firstRowCells = getFirstRowCells(tableNode as TableElement) - return firstRowCells.some(c => c === cellNode) -} diff --git a/packages/table-module/src/module/index.ts b/packages/table-module/src/module/index.ts deleted file mode 100644 index 4434d231b..000000000 --- a/packages/table-module/src/module/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @description table module - * @author wangfupeng - */ - -import { IModuleConf } from '@wangeditor/core' -import withTable from './plugin' -import { renderTableConf, renderTableRowConf, renderTableCellConf } from './render-elem/index' -import { tableToHtmlConf, tableRowToHtmlConf, tableCellToHtmlConf } from './elem-to-html' -import { preParseTableHtmlConf } from './pre-parse-html' -import { parseCellHtmlConf, parseRowHtmlConf, parseTableHtmlConf } from './parse-elem-html' -import { - insertTableMenuConf, - deleteTableMenuConf, - insertTableRowConf, - deleteTableRowConf, - insertTableColConf, - deleteTableColConf, - tableHeaderMenuConf, - tableFullWidthMenuConf, -} from './menu/index' - -const table: Partial = { - renderElems: [renderTableConf, renderTableRowConf, renderTableCellConf], - elemsToHtml: [tableToHtmlConf, tableRowToHtmlConf, tableCellToHtmlConf], - preParseHtml: [preParseTableHtmlConf], - parseElemsHtml: [parseCellHtmlConf, parseRowHtmlConf, parseTableHtmlConf], - menus: [ - insertTableMenuConf, - deleteTableMenuConf, - insertTableRowConf, - deleteTableRowConf, - insertTableColConf, - deleteTableColConf, - tableHeaderMenuConf, - tableFullWidthMenuConf, - ], - editorPlugin: withTable, -} - -export default table diff --git a/packages/table-module/src/module/menu/DeleteCol.ts b/packages/table-module/src/module/menu/DeleteCol.ts deleted file mode 100644 index dac211b2f..000000000 --- a/packages/table-module/src/module/menu/DeleteCol.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @description del col menu - * @author wangfupeng - */ - -import isEqual from 'lodash.isequal' -import { Editor, Element, Transforms, Range, Node } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { DEL_COL_SVG } from '../../constants/svg' - -class DeleteCol implements IButtonMenu { - readonly title = t('tableModule.deleteCol') - readonly iconSvg = DEL_COL_SVG - readonly tag = 'button' - - getValue(editor: IDomEditor): string | boolean { - // 无需获取 val - return '' - } - - isActive(editor: IDomEditor): boolean { - // 无需 active - return false - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true - - const cellNode = DomEditor.getSelectedNodeByType(editor, 'table-cell') - if (cellNode == null) { - // 选区未处于 table cell node ,则禁用 - return true - } - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - const [cellEntry] = Editor.nodes(editor, { - match: n => DomEditor.checkNodeType(n, 'table-cell'), - universal: true, - }) - const [selectedCellNode, selectedCellPath] = cellEntry - - // 如果只有一列,则删除整个表格 - const rowNode = DomEditor.getParentNode(editor, selectedCellNode) - const colLength = rowNode?.children.length || 0 - if (!rowNode || colLength <= 1) { - Transforms.removeNodes(editor, { mode: 'highest' }) // 删除整个表格 - return - } - - // ------------------------- 不只有 1 列,则继续 ------------------------- - - const tableNode = DomEditor.getParentNode(editor, rowNode) - if (tableNode == null) return - - // 遍历所有 rows ,挨个删除 cell - const rows = tableNode.children || [] - rows.forEach(row => { - if (!Element.isElement(row)) return - - const cells = row.children || [] - // 遍历一个 row 的所有 cells - cells.forEach((cell: Node) => { - const path = DomEditor.findPath(editor, cell) - if ( - path.length === selectedCellPath.length && - isEqual(path.slice(-1), selectedCellPath.slice(-1)) // 俩数组,最后一位相同 - ) { - // 如果当前 td 的 path 和选中 td 的 path ,最后一位相同,说明是同一列 - // 删除当前的 cell - Transforms.removeNodes(editor, { at: path }) - } - }) - }) - } -} - -export default DeleteCol diff --git a/packages/table-module/src/module/menu/DeleteRow.ts b/packages/table-module/src/module/menu/DeleteRow.ts deleted file mode 100644 index 4000dac10..000000000 --- a/packages/table-module/src/module/menu/DeleteRow.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @description del row menu - * @author wangfupeng - */ - -import { Editor, Transforms, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { DEL_ROW_SVG } from '../../constants/svg' - -class DeleteRow implements IButtonMenu { - readonly title = t('tableModule.deleteRow') - readonly iconSvg = DEL_ROW_SVG - readonly tag = 'button' - - getValue(editor: IDomEditor): string | boolean { - // 无需获取 val - return '' - } - - isActive(editor: IDomEditor): boolean { - // 无需 active - return false - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true - - const rowNode = DomEditor.getSelectedNodeByType(editor, 'table-row') - if (rowNode == null) { - // 选区未处于 table row node ,则禁用 - return true - } - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - const [rowEntry] = Editor.nodes(editor, { - match: n => DomEditor.checkNodeType(n, 'table-row'), - universal: true, - }) - const [rowNode, rowPath] = rowEntry - - const tableNode = DomEditor.getParentNode(editor, rowNode) - const rowsLength = tableNode?.children.length || 0 - if (rowsLength <= 1) { - // row 只有一行,则删掉整个表格 - Transforms.removeNodes(editor, { mode: 'highest' }) - return - } - - // row > 1 行,则删掉这一行 - Transforms.removeNodes(editor, { at: rowPath }) - } -} - -export default DeleteRow diff --git a/packages/table-module/src/module/menu/DeleteTable.ts b/packages/table-module/src/module/menu/DeleteTable.ts deleted file mode 100644 index 4073c55ab..000000000 --- a/packages/table-module/src/module/menu/DeleteTable.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @description del table menu - * @author wangfupeng - */ - -import { Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { TRASH_SVG } from '../../constants/svg' - -class DeleteTable implements IButtonMenu { - readonly title = t('tableModule.deleteTable') - readonly iconSvg = TRASH_SVG - readonly tag = 'button' - - getValue(editor: IDomEditor): string | boolean { - // 无需获取 val - return '' - } - - isActive(editor: IDomEditor): boolean { - // 无需 active - return false - } - - isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true - - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') - if (tableNode == null) { - // 选区未处于 table node ,则禁用 - return true - } - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - // 删除表格 - Transforms.removeNodes(editor, { mode: 'highest' }) - } -} - -export default DeleteTable diff --git a/packages/table-module/src/module/menu/FullWidth.ts b/packages/table-module/src/module/menu/FullWidth.ts deleted file mode 100644 index ea71e43b3..000000000 --- a/packages/table-module/src/module/menu/FullWidth.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @description table full width menu - * @author wangfupeng - */ - -import { Transforms, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { FULL_WIDTH_SVG } from '../../constants/svg' -import { TableElement } from '../custom-types' - -class TableFullWidth implements IButtonMenu { - readonly title = t('tableModule.widthAuto') - readonly iconSvg = FULL_WIDTH_SVG - readonly tag = 'button' - - // 是否已设置 宽度自适应 - getValue(editor: IDomEditor): string | boolean { - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') - if (tableNode == null) return false - return (tableNode as TableElement).width === '100%' - } - - isActive(editor: IDomEditor): boolean { - return !!this.getValue(editor) - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true - - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') - if (tableNode == null) { - // 选区未处于 table node ,则禁用 - return true - } - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - const props: Partial = { - width: value ? 'auto' : '100%', // 切换 'auto' 和 '100%' - } - Transforms.setNodes(editor, props, { mode: 'highest' }) - } -} - -export default TableFullWidth diff --git a/packages/table-module/src/module/menu/InsertCol.ts b/packages/table-module/src/module/menu/InsertCol.ts deleted file mode 100644 index e22d77ade..000000000 --- a/packages/table-module/src/module/menu/InsertCol.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @description insert col menu - * @author wangfupeng - */ - -import isEqual from 'lodash.isequal' -import { Editor, Element, Transforms, Range, Node } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { ADD_COL_SVG } from '../../constants/svg' -import { TableCellElement, TableElement } from '../custom-types' -import { isTableWithHeader } from '../helpers' - -class InsertCol implements IButtonMenu { - readonly title = t('tableModule.insertCol') - readonly iconSvg = ADD_COL_SVG - readonly tag = 'button' - - getValue(editor: IDomEditor): string | boolean { - // 无需获取 val - return '' - } - - isActive(editor: IDomEditor): boolean { - // 无需 active - return false - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true - - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') - if (tableNode == null) { - // 选区未处于 table cell node ,则禁用 - return true - } - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - const [cellEntry] = Editor.nodes(editor, { - match: n => DomEditor.checkNodeType(n, 'table-cell'), - universal: true, - }) - const [selectedCellNode, selectedCellPath] = cellEntry - - const rowNode = DomEditor.getParentNode(editor, selectedCellNode) - if (rowNode == null) return - const tableNode = DomEditor.getParentNode(editor, rowNode) as TableElement - if (tableNode == null) return - - // 遍历所有 rows ,挨个添加 cell - const rows = tableNode.children || [] - rows.forEach((row, rowIndex) => { - if (!Element.isElement(row)) return - - const cells = row.children || [] - // 遍历一个 row 的所有 cells - cells.forEach((cell: Node) => { - const path = DomEditor.findPath(editor, cell) - if ( - path.length === selectedCellPath.length && - isEqual(path.slice(-1), selectedCellPath.slice(-1)) // 俩数组,最后一位相同 - ) { - // 如果当前 td 的 path 和选中 td 的 path ,最后一位相同,说明是同一列 - // 则在其后插入一个 cell - const newCell: TableCellElement = { type: 'table-cell', children: [{ text: '' }] } - if (rowIndex === 0 && isTableWithHeader(tableNode)) { - newCell.isHeader = true - } - Transforms.insertNodes(editor, newCell, { at: path }) - } - }) - }) - } -} - -export default InsertCol diff --git a/packages/table-module/src/module/menu/InsertRow.ts b/packages/table-module/src/module/menu/InsertRow.ts deleted file mode 100644 index 4c34a3c29..000000000 --- a/packages/table-module/src/module/menu/InsertRow.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @description insert row menu - * @author wangfupeng - */ - -import { Editor, Transforms, Range, Path } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { ADD_ROW_SVG } from '../../constants/svg' -import { TableRowElement, TableCellElement } from '../custom-types' - -class InsertRow implements IButtonMenu { - readonly title = t('tableModule.insertRow') - readonly iconSvg = ADD_ROW_SVG - readonly tag = 'button' - - getValue(editor: IDomEditor): string | boolean { - // 无需获取 val - return '' - } - - isActive(editor: IDomEditor): boolean { - // 无需 active - return false - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true - - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') - if (tableNode == null) { - // 选区未处于 table cell node ,则禁用 - return true - } - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - const [cellEntry] = Editor.nodes(editor, { - match: n => DomEditor.checkNodeType(n, 'table-cell'), - universal: true, - }) - const [cellNode, cellPath] = cellEntry - - // 获取 cell length ,即多少列 - const rowNode = DomEditor.getParentNode(editor, cellNode) - const cellsLength = rowNode?.children.length || 0 - if (cellsLength === 0) return - - // 拼接新的 row - const newRow: TableRowElement = { type: 'table-row', children: [] } - for (let i = 0; i < cellsLength; i++) { - const cell: TableCellElement = { - type: 'table-cell', - children: [{ text: '' }], - } - newRow.children.push(cell) - } - - // 插入 row - const rowPath = Path.parent(cellPath) // 获取 tr 的 path - const newRowPath = Path.next(rowPath) - Transforms.insertNodes(editor, newRow, { at: newRowPath }) - } -} - -export default InsertRow diff --git a/packages/table-module/src/module/menu/InsertTable.ts b/packages/table-module/src/module/menu/InsertTable.ts deleted file mode 100644 index 69acca17d..000000000 --- a/packages/table-module/src/module/menu/InsertTable.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @description insert table menu - * @author wangfupeng - */ - -import { Editor, Transforms, Range, Node } from 'slate' -import { IDropPanelMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import $, { Dom7Array, DOMElement } from '../../utils/dom' -import { genRandomStr } from '../../utils/util' -import { TABLE_SVG } from '../../constants/svg' -import { TableElement, TableCellElement, TableRowElement } from '../custom-types' - -function genTableNode(rowNum: number, colNum: number): TableElement { - // 拼接 rows - const rows: TableRowElement[] = [] - for (let i = 0; i < rowNum; i++) { - // 拼接 cells - const cells: TableCellElement[] = [] - for (let j = 0; j < colNum; j++) { - const cellNode: TableCellElement = { - type: 'table-cell', - children: [{ text: '' }], - } - if (i === 0) { - cellNode.isHeader = true // 第一行默认是 th - } - cells.push(cellNode) - } - - // 生成 row - rows.push({ - type: 'table-row', - children: cells, - }) - } - - return { - type: 'table', - width: 'auto', - children: rows, - } -} - -/** - * 生成唯一的 DOM ID - */ -function genDomID(): string { - return genRandomStr('w-e-insert-table') -} - -class InsertTable implements IDropPanelMenu { - title = t('tableModule.insertTable') - iconSvg = TABLE_SVG - tag = 'button' - showDropPanel = true // 点击 button 时显示 dropPanel - private $content: Dom7Array | null = null - - getValue(editor: IDomEditor): string | boolean { - // 插入菜单,不需要 value - return '' - } - - isActive(editor: IDomEditor): boolean { - // 任何时候,都不用激活 menu - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - // 点击菜单时,弹出 modal 之前,不需要执行其他代码 - // 此处空着即可 - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用 - - const selectedElems = DomEditor.getSelectedElems(editor) - const hasVoidOrPreOrTable = selectedElems.some(elem => { - const type = DomEditor.getNodeType(elem) - if (type === 'pre') return true - if (type === 'table') return true - if (type === 'list-item') return true - if (editor.isVoid(elem)) return true - return false - }) - if (hasVoidOrPreOrTable) return true // 匹配到,禁用 - - return false - } - - /** - * 获取 panel 内容 - * @param editor editor - */ - getPanelContentElem(editor: IDomEditor): DOMElement { - // 已有,直接返回 - if (this.$content) return this.$content[0] - - // 初始化 - const $content = $('
                ') - const $info = $('0 × 0') // 显示行列数量 - - // 渲染 10 * 10 table ,以快速创建表格 - const $table = $('
                ') - for (let i = 0; i < 10; i++) { - const $tr = $('') - for (let j = 0; j < 10; j++) { - const $td = $('') - $td.attr('data-x', j.toString()) - $td.attr('data-y', i.toString()) - $tr.append($td) - - // 绑定 mouseenter - $td.on('mouseenter', (e: Event) => { - const { target } = e - if (target == null) return - const $focusTd = $(target) - const { x: focusX, y: focusY } = $focusTd.dataset() - - // 显示行列数量 - $info[0].innerHTML = `${focusX + 1} × ${focusY + 1}` - - // 修改 table td 样式 - $table.children().each(tr => { - $(tr) - .children() - .each(td => { - const $td = $(td) - const { x, y } = $td.dataset() - if (x <= focusX && y <= focusY) { - $td.addClass('active') - } else { - $td.removeClass('active') - } - }) - }) - }) - - // 绑定 click - $td.on('click', (e: Event) => { - e.preventDefault() - const { target } = e - if (target == null) return - const $td = $(target) - const { x, y } = $td.dataset() - this.insertTable(editor, y + 1, x + 1) - }) - } - $table.append($tr) - } - $content.append($table) - $content.append($info) - - // 记录,并返回 - this.$content = $content - return $content[0] - } - - private insertTable(editor: IDomEditor, rowNumStr: string, colNumStr: string) { - const rowNum = parseInt(rowNumStr, 10) - const colNum = parseInt(colNumStr, 10) - if (!rowNum || !colNum) return - if (rowNum <= 0 || colNum <= 0) return - - // 如果当前是空 p ,则删除该 p - if (DomEditor.isSelectedEmptyParagraph(editor)) { - Transforms.removeNodes(editor, { mode: 'highest' }) - } - - // 插入表格 - const tableNode = genTableNode(rowNum, colNum) - Transforms.insertNodes(editor, tableNode, { mode: 'highest' }) - } -} - -export default InsertTable diff --git a/packages/table-module/src/module/menu/TableHeader.ts b/packages/table-module/src/module/menu/TableHeader.ts deleted file mode 100644 index 5808f91ce..000000000 --- a/packages/table-module/src/module/menu/TableHeader.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @description table header menu - * @author wangfupeng - */ - -import { Transforms, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor/core' -import { TABLE_HEADER_SVG } from '../../constants/svg' -import { TableElement } from '../custom-types' -import { getFirstRowCells, isTableWithHeader } from '../helpers' - -class TableHeader implements IButtonMenu { - readonly title = t('tableModule.header') - readonly iconSvg = TABLE_HEADER_SVG - readonly tag = 'button' - - // 是否已设置表头 - getValue(editor: IDomEditor): string | boolean { - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') as TableElement - if (tableNode == null) return false - - return isTableWithHeader(tableNode) - } - - isActive(editor: IDomEditor): boolean { - return !!this.getValue(editor) - } - - isDisabled(editor: IDomEditor): boolean { - const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true - - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') - if (tableNode == null) { - // 选区未处于 table node ,则禁用 - return true - } - return false - } - - exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return - - // 已经设置了表头,则取消。未设置表头,则设置 - const newValue = value ? false : true - - // 获取第一行所有 cell - const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') as TableElement - if (tableNode == null) return - const firstRowCells = getFirstRowCells(tableNode) - - // 设置 isHeader 属性 - firstRowCells.forEach(cell => - Transforms.setNodes( - editor, - { isHeader: newValue }, - { - at: DomEditor.findPath(editor, cell), - } - ) - ) - } -} - -export default TableHeader diff --git a/packages/table-module/src/module/menu/index.ts b/packages/table-module/src/module/menu/index.ts deleted file mode 100644 index 63a71236e..000000000 --- a/packages/table-module/src/module/menu/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @description table menu - * @author wangfupeng - */ - -import InsertTable from './InsertTable' -import DeleteTable from './DeleteTable' -import InsertRow from './InsertRow' -import DeleteRow from './DeleteRow' -import InsertCol from './InsertCol' -import DeleteCol from './DeleteCol' -import TableHander from './TableHeader' -import FullWidth from './FullWidth' - -export const insertTableMenuConf = { - key: 'insertTable', - factory() { - return new InsertTable() - }, -} - -export const deleteTableMenuConf = { - key: 'deleteTable', - factory() { - return new DeleteTable() - }, -} - -export const insertTableRowConf = { - key: 'insertTableRow', - factory() { - return new InsertRow() - }, -} - -export const deleteTableRowConf = { - key: 'deleteTableRow', - factory() { - return new DeleteRow() - }, -} - -export const insertTableColConf = { - key: 'insertTableCol', - factory() { - return new InsertCol() - }, -} - -export const deleteTableColConf = { - key: 'deleteTableCol', - factory() { - return new DeleteCol() - }, -} - -export const tableHeaderMenuConf = { - key: 'tableHeader', - factory() { - return new TableHander() - }, -} - -export const tableFullWidthMenuConf = { - key: 'tableFullWidth', - factory() { - return new FullWidth() - }, -} diff --git a/packages/table-module/src/module/parse-elem-html.ts b/packages/table-module/src/module/parse-elem-html.ts deleted file mode 100644 index 8e4011905..000000000 --- a/packages/table-module/src/module/parse-elem-html.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @description parse html - * @author wangfupeng - */ - -import { Descendant, Text } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor/core' -import { TableCellElement, TableRowElement, TableElement } from './custom-types' -import $, { getTagName, getStyleValue, DOMElement } from '../utils/dom' - -function parseCellHtml( - elem: DOMElement, - children: Descendant[], - editor: IDomEditor -): TableCellElement { - const $elem = $(elem) - - children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true - return false - }) - - // 无 children ,则用纯文本 - if (children.length === 0) { - children = [{ text: $elem.text().replace(/\s+/gm, ' ') }] - } - - const colSpan = parseInt($elem.attr('colSpan') || '1') - const rowSpan = parseInt($elem.attr('rowSpan') || '1') - const width = $elem.attr('width') || 'auto' - - return { - type: 'table-cell', - isHeader: getTagName($elem) === 'th', - colSpan, - rowSpan, - width, - // @ts-ignore - children, - } -} - -export const parseCellHtmlConf = { - selector: 'td:not([data-w-e-type]),th:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性 - parseElemHtml: parseCellHtml, -} - -function parseRowHtml( - elem: DOMElement, - children: Descendant[], - editor: IDomEditor -): TableRowElement { - return { - type: 'table-row', - // @ts-ignore - children: children.filter(child => DomEditor.getNodeType(child) === 'table-cell'), - } -} - -export const parseRowHtmlConf = { - selector: 'tr:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性 - parseElemHtml: parseRowHtml, -} - -function parseTableHtml( - elem: DOMElement, - children: Descendant[], - editor: IDomEditor -): TableElement { - const $elem = $(elem) - - // 计算宽度 - let width = 'auto' - if (getStyleValue($elem, 'width') === '100%') width = '100%' - if ($elem.attr('width') === '100%') width = '100%' // 兼容 v4 格式 - - return { - type: 'table', - width, - // @ts-ignore - children: children.filter(child => DomEditor.getNodeType(child) === 'table-row'), - } -} - -export const parseTableHtmlConf = { - selector: 'table:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性 - parseElemHtml: parseTableHtml, -} diff --git a/packages/table-module/src/module/plugin.ts b/packages/table-module/src/module/plugin.ts deleted file mode 100644 index ec5d4b92a..000000000 --- a/packages/table-module/src/module/plugin.ts +++ /dev/null @@ -1,230 +0,0 @@ -/** - * @description editor 插件,重写 editor API - * @author wangfupeng - */ - -import { - Editor, - Transforms, - Location, - Point, - Element as SlateElement, - Descendant, - NodeEntry, - Node, - BaseText, - Path, -} from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor/core' - -// table cell 内部的删除处理 -function deleteHandler(newEditor: IDomEditor): boolean { - const { selection } = newEditor - if (selection == null) return false - - const [cellNodeEntry] = Editor.nodes(newEditor, { - match: n => DomEditor.checkNodeType(n, 'table-cell'), - }) - if (cellNodeEntry) { - const [, cellPath] = cellNodeEntry - const start = Editor.start(newEditor, cellPath) - - if (Point.equals(selection.anchor, start)) { - return true // 阻止删除 cell - } - } - - return false -} - -/** - * 判断该 location 有没有命中 table - * @param editor editor - * @param location location - */ -function isTableLocation(editor: IDomEditor, location: Location): boolean { - const tables = Editor.nodes(editor, { - at: location, - match: n => { - const type = DomEditor.getNodeType(n) - return type === 'table' - }, - }) - let hasTable = false - for (const table of tables) { - hasTable = true // 找到了 table - } - return hasTable -} - -function withTable(editor: T): T { - const { - insertBreak, - deleteBackward, - deleteForward, - normalizeNode, - insertData, - handleTab, - selectAll, - } = editor - const newEditor = editor - - // 重写 insertBreak - cell 内换行,只换行文本,不拆分 node - newEditor.insertBreak = () => { - const selectedNode = DomEditor.getSelectedNodeByType(newEditor, 'table') - if (selectedNode != null) { - // 选中了 table ,则在 cell 内换行 - newEditor.insertText('\n') - return - } - - // 未选中 table ,默认的换行 - insertBreak() - } - - // 重写 delete - cell 内删除,只删除文字,不删除 node - newEditor.deleteBackward = unit => { - const res = deleteHandler(newEditor) - if (res) return // 命中 table cell ,自己处理删除 - - // 防止从 table 后面的 p 删除时,删除最后一个 cell - issues/4221 - const { selection } = newEditor - if (selection) { - const before = Editor.before(newEditor, selection) // 前一个 location - if (before) { - const isTableOnBeforeLocation = isTableLocation(newEditor, before) // before 是否是 table - const isTableOnCurSelection = isTableLocation(newEditor, selection) // 当前是否是 table - if (isTableOnBeforeLocation && !isTableOnCurSelection) { - return // 如果当前不是 table ,前面是 table ,则不执行删除。否则会删除 table 最后一个 cell - } - } - } - - // 执行默认的删除 - deleteBackward(unit) - } - - // 重写 handleTab 在table内按tab时跳到下一个单元格 - newEditor.handleTab = () => { - const selectedNode = DomEditor.getSelectedNodeByType(newEditor, 'table') - if (selectedNode) { - const above = Editor.above(editor) as NodeEntry - - // 常规情况下选中文字外层 table-cell 进行跳转 - if (DomEditor.checkNodeType(above[0], 'table-cell')) { - Transforms.select(editor, above[1]) - } - - let next = Editor.next(editor) - if (next) { - if (next[0] && (next[0] as BaseText).text) { - // 多个单元格同时选中按 tab 导致错位修复 - next = (Editor.above(editor, { at: next[1] }) as NodeEntry) ?? next - } - Transforms.select(editor, next[1]) - } else { - const topLevelNodes = newEditor.children || [] - const topLevelNodesLength = topLevelNodes.length - // 在最后一个单元格按tab时table末尾如果没有p则插入p后光标切到p上 - if (DomEditor.checkNodeType(topLevelNodes[topLevelNodesLength - 1], 'table')) { - const p = DomEditor.genEmptyParagraph() - Transforms.insertNodes(newEditor, p, { at: [topLevelNodesLength] }) - // 在表格末尾插入p后再次执行使光标切到p上 - newEditor.handleTab() - } - } - return - } - - handleTab() - } - - newEditor.deleteForward = unit => { - const res = deleteHandler(newEditor) - if (res) return // 命中 table cell ,自己处理删除 - - // 执行默认的删除 - deleteForward(unit) - } - - // 重新 normalize - newEditor.normalizeNode = ([node, path]) => { - const type = DomEditor.getNodeType(node) - if (type !== 'table') { - // 未命中 table ,执行默认的 normalizeNode - return normalizeNode([node, path]) - } - - // -------------- table 是 editor 最后一个节点,需要后面插入 p -------------- - const isLast = DomEditor.isLastNode(newEditor, node) - if (isLast) { - const p = DomEditor.genEmptyParagraph() - Transforms.insertNodes(newEditor, p, { at: [path[0] + 1] }) - } - } - - // 重写 insertData - 粘贴文本 - newEditor.insertData = (data: DataTransfer) => { - const tableNode = DomEditor.getSelectedNodeByType(newEditor, 'table') - if (tableNode == null) { - insertData(data) // 执行默认的 insertData - return - } - - // 获取文本,并插入到 cell - const text = data.getData('text/plain') - - // 单图或图文 插入 - if (text === '\n' || /]+>/.test(data.getData('text/html'))) { - insertData(data) - return - } - - Editor.insertText(newEditor, text) - } - - // 重写 table-cell 中的全选 - newEditor.selectAll = () => { - const selection = newEditor.selection - if (selection == null) { - selectAll() - return - } - - const cell = DomEditor.getSelectedNodeByType(newEditor, 'table-cell') - if (cell == null) { - selectAll() - return - } - - const { anchor, focus } = selection - if (!Path.equals(anchor.path.slice(0, 3), focus.path.slice(0, 3))) { - // 选中了多个 cell ,忽略 - selectAll() - return - } - - const text = Node.string(cell) - const textLength = text.length - if (textLength === 0) { - selectAll() - return - } - - const path = DomEditor.findPath(newEditor, cell) - const start = Editor.start(newEditor, path) - const end = Editor.end(newEditor, path) - const newSelection = { - anchor: start, - focus: end, - } - newEditor.select(newSelection) // 选中 table-cell 内部的全部文字 - } - - // 可继续修改其他 newEditor API ... - - // 返回 editor ,重要! - return newEditor -} - -export default withTable diff --git a/packages/table-module/src/module/pre-parse-html.ts b/packages/table-module/src/module/pre-parse-html.ts deleted file mode 100644 index 162976e8e..000000000 --- a/packages/table-module/src/module/pre-parse-html.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @description pre parse html - * @author wangfupeng - */ - -import $, { getTagName, DOMElement } from '../utils/dom' - -/** - * pre-prase table ,去掉 - * @param table table elem - */ -function preParse(tableElem: DOMElement): DOMElement { - const $table = $(tableElem) - const tagName = getTagName($table) - if (tagName !== 'table') return tableElem - - // 没有 则直接返回 - const $tbody = $table.find('tbody') - if ($tbody.length === 0) return tableElem - - // 去掉 ,把 移动到 下面 - const $tr = $table.find('tr') - $table.append($tr) - $tbody.remove() - - return $table[0] -} - -export const preParseTableHtmlConf = { - selector: 'table', - preParseHtml: preParse, -} diff --git a/packages/table-module/src/module/render-elem/index.ts b/packages/table-module/src/module/render-elem/index.ts deleted file mode 100644 index 98f89c924..000000000 --- a/packages/table-module/src/module/render-elem/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @description render elem - * @author wangfupeng - */ - -import renderTable from './render-table' -import renderTableRow from './render-row' -import renderTableCell from './render-cell' - -export const renderTableConf = { - type: 'table', - renderElem: renderTable, -} - -export const renderTableRowConf = { - type: 'table-row', - renderElem: renderTableRow, -} - -export const renderTableCellConf = { - type: 'table-cell', - renderElem: renderTableCell, -} diff --git a/packages/table-module/src/module/render-elem/render-cell.tsx b/packages/table-module/src/module/render-elem/render-cell.tsx deleted file mode 100644 index dc1a09661..000000000 --- a/packages/table-module/src/module/render-elem/render-cell.tsx +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @description render cell - * @author wangfupeng - */ - -import throttle from 'lodash.throttle' -import { Element as SlateElement, Transforms, Location } from 'slate' -import { jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor/core' -import { TableCellElement } from '../custom-types' -import { isCellInFirstRow } from '../helpers' -import $ from '../../utils/dom' - -// 拖拽列宽相关信息 -let isMouseDownForResize = false -let clientXWhenMouseDown = 0 -let cellWidthWhenMouseDown = 0 -let cellPathWhenMouseDown: Location | null = null -let editorWhenMouseDown: IDomEditor | null = null -const $body = $('body') - -function onMouseDown(event: Event) { - const elem = event.target as HTMLElement - if (elem.tagName !== 'TH' && elem.tagName !== 'TD') return - - if (elem.style.cursor !== 'col-resize') return - elem.style.cursor = 'auto' - - event.preventDefault() - - // 记录必要信息 - isMouseDownForResize = true - const { clientX } = event as MouseEvent - clientXWhenMouseDown = clientX - const { width } = elem.getBoundingClientRect() - cellWidthWhenMouseDown = width - - // 绑定事件 - $body.on('mousemove', onMouseMove) - $body.on('mouseup', onMouseUp) -} -$body.on('mousedown', onMouseDown) // 绑定事件 - -function onMouseUp(event: Event) { - isMouseDownForResize = false - editorWhenMouseDown = null - cellPathWhenMouseDown = null - - // 解绑事件 - $body.off('mousemove', onMouseMove) - $body.off('mouseup', onMouseUp) -} - -const onMouseMove = throttle(function (event: Event) { - if (!isMouseDownForResize) return - if (editorWhenMouseDown == null || cellPathWhenMouseDown == null) return - event.preventDefault() - - const { clientX } = event as MouseEvent - let newWith = cellWidthWhenMouseDown + (clientX - clientXWhenMouseDown) // 计算新宽度 - newWith = Math.floor(newWith * 100) / 100 // 保留小数点后两位 - if (newWith < 30) newWith = 30 // 最小宽度 - - // 这是宽度 - Transforms.setNodes( - editorWhenMouseDown, - { width: newWith.toString() }, - { - at: cellPathWhenMouseDown, - } - ) -}, 100) - -function renderTableCell( - cellNode: SlateElement, - children: VNode[] | null, - editor: IDomEditor -): VNode { - const isFirstRow = isCellInFirstRow(editor, cellNode as TableCellElement) - const { colSpan = 1, rowSpan = 1, isHeader = false } = cellNode as TableCellElement - - // ------------------ 不是第一行,直接渲染 - ) - } - - // ------------------ 是第一行:1. 判断 th ;2. 拖拽列宽 ------------------ - const Tag = isHeader ? 'th' : 'td' - - const vnode = ( - left + width - 5 && clientX < left + width // X 轴,是否接近 cell 右侧? - const matchY = clientY > top && clientY < top + height // Y 轴,是否在 cell 之内 - // X Y 轴都接近,则修改鼠标样式 - if (matchX && matchY) { - elem.style.cursor = 'col-resize' - editorWhenMouseDown = editor - cellPathWhenMouseDown = DomEditor.findPath(editor, cellNode) - } else { - if (!isMouseDownForResize) { - elem.style.cursor = 'auto' - editorWhenMouseDown = null - cellPathWhenMouseDown = null - } - } - }, 100), - }} - > - {children} - - ) - return vnode -} - -export default renderTableCell diff --git a/packages/table-module/src/module/render-elem/render-row.tsx b/packages/table-module/src/module/render-elem/render-row.tsx deleted file mode 100644 index fb5b81f59..000000000 --- a/packages/table-module/src/module/render-elem/render-row.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @description render row - * @author wangfupeng - */ - -import { Element as SlateElement } from 'slate' -import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor/core' - -function renderTableRow( - elemNode: SlateElement, - children: VNode[] | null, - editor: IDomEditor -): VNode { - const vnode = {children} - return vnode -} - -export default renderTableRow diff --git a/packages/table-module/src/module/render-elem/render-table.tsx b/packages/table-module/src/module/render-elem/render-table.tsx deleted file mode 100644 index 3777075e1..000000000 --- a/packages/table-module/src/module/render-elem/render-table.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @description render table - * @author wangfupeng - */ - -import { Editor, Element as SlateElement, Range, Point, Path } from 'slate' -import { jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor/core' -import { TableElement } from '../custom-types' -import { getFirstRowCells } from '../helpers' - -/** - * 计算 table 是否可编辑。如果选区跨域 table 和外部内容,删除,会导致 table 结构打乱。所以,有时要让 table 不可编辑 - * @param editor editor - * @param tableElem table elem - */ -function getContentEditable(editor: IDomEditor, tableElem: SlateElement): boolean { - if (editor.isDisabled()) return false - - const { selection } = editor - if (selection == null) return true - if (Range.isCollapsed(selection)) return true - - const { anchor, focus } = selection - const tablePath = DomEditor.findPath(editor, tableElem) - - const tableStart = Editor.start(editor, tablePath) - const tableEnd = Editor.end(editor, tablePath) - const isAnchorInTable = - Point.compare(anchor, tableEnd) <= 0 && Point.compare(anchor, tableStart) >= 0 - const isFocusInTable = - Point.compare(focus, tableEnd) <= 0 && Point.compare(focus, tableStart) >= 0 - - // 选区在 table 内部,且选中了同一个单元格。表格可以编辑 - if (isAnchorInTable && isFocusInTable) { - if (Path.equals(anchor.path.slice(0, 3), focus.path.slice(0, 3))) { - return true - } - } - - return false -} - -function renderTable(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode { - // 是否可编辑 - const editable = getContentEditable(editor, elemNode) - - // 宽度 - const { width = 'auto' } = elemNode as TableElement - - // 是否选中 - const selected = DomEditor.isNodeSelected(editor, elemNode) - - // 第一行的 cells ,以计算列宽 - const firstRowCells = getFirstRowCells(elemNode as TableElement) - - const vnode = ( -
                { - // @ts-ignore 阻止光标定位到 table 后面 - if (e.target.tagName === 'DIV') e.preventDefault() - - if (editor.isDisabled()) return - - // 是否需要定位到 table 内部 - const tablePath = DomEditor.findPath(editor, elemNode) - const tableStart = Editor.start(editor, tablePath) - const { selection } = editor - if (selection == null) { - editor.select(tableStart) // 选中 table 内部 - return - } - const { path } = selection.anchor - if (path[0] === tablePath[0]) return // 当前选区,就在 table 内部 - - editor.select(tableStart) // 选中 table 内部 - }, - }} - > -
                ------------------ - if (!isFirstRow) { - return ( - - {children} -
                - - {firstRowCells.map(cell => { - const { width = 'auto' } = cell - return - })} - - {children} -
                -
              - ) - return vnode -} - -export default renderTable diff --git a/packages/table-module/src/utils/dom.ts b/packages/table-module/src/utils/dom.ts deleted file mode 100644 index 14760a509..000000000 --- a/packages/table-module/src/utils/dom.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @description DOM 操作 - * @author wangfupeng - */ - -import $, { - append, - on, - focus, - attr, - val, - html, - dataset, - addClass, - removeClass, - children, - each, - find, - Dom7Array, -} from 'dom7' -export { Dom7Array } from 'dom7' - -if (append) $.fn.append = append -if (on) $.fn.on = on -if (focus) $.fn.focus = focus -if (attr) $.fn.attr = attr -if (val) $.fn.val = val -if (html) $.fn.html = html -if (dataset) $.fn.dataset = dataset -if (addClass) $.fn.addClass = addClass -if (removeClass) $.fn.removeClass = removeClass -if (children) $.fn.children = children -if (each) $.fn.each = each -if (find) $.fn.find = find - -export default $ - -/** - * 获取 tagName lower-case - * @param $elem $elem - */ -export function getTagName($elem: Dom7Array): string { - if ($elem.length) return $elem[0].tagName.toLowerCase() - return '' -} - -/** - * 获取 $elem 某一个 style 值 - * @param $elem $elem - * @param styleKey style key - */ -export function getStyleValue($elem: Dom7Array, styleKey: string): string { - let res = '' - - const styleStr = $elem.attr('style') || '' // 如 'line-height: 2.5; color: red;' - const styleArr = styleStr.split(';') // 如 ['line-height: 2.5', ' color: red', ''] - const length = styleArr.length - for (let i = 0; i < length; i++) { - const styleItemStr = styleArr[i] // 如 'line-height: 2.5' - if (styleItemStr) { - const arr = styleItemStr.split(':') // ['line-height', ' 2.5'] - if (arr[0].trim() === styleKey) { - res = arr[1].trim() - } - } - } - - return res -} - -// COMPAT: This is required to prevent TypeScript aliases from doing some very -// weird things for Slate's types with the same name as globals. (2019/11/27) -// https://github.com/microsoft/TypeScript/issues/35002 -import DOMNode = globalThis.Node -import DOMComment = globalThis.Comment -import DOMElement = globalThis.Element -import DOMText = globalThis.Text -import DOMRange = globalThis.Range -import DOMSelection = globalThis.Selection -import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } diff --git a/packages/table-module/src/utils/util.ts b/packages/table-module/src/utils/util.ts deleted file mode 100644 index 84ef78add..000000000 --- a/packages/table-module/src/utils/util.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @description 工具函数 - * @author wangfupeng - */ - -import { nanoid } from 'nanoid' - -/** - * 获取随机数字符串 - * @param prefix 前缀 - * @returns 随机数字符串 - */ -export function genRandomStr(prefix: string = 'r'): string { - return `${prefix}-${nanoid()}` -} diff --git a/packages/table-module/tsconfig.json b/packages/table-module/tsconfig.json deleted file mode 100644 index 9bef938c9..000000000 --- a/packages/table-module/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": {}, - "extends": "../../tsconfig.json", - "include": [ - "./src/**/*", - "../custom-types.d.ts" - ] -} \ No newline at end of file diff --git a/packages/upload-image-module/CHANGELOG.md b/packages/upload-image-module/CHANGELOG.md deleted file mode 100644 index 6aad75d6d..000000000 --- a/packages/upload-image-module/CHANGELOG.md +++ /dev/null @@ -1,42 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.0.2](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/upload-image-module@1.0.1...@wangeditor/upload-image-module@1.0.2) (2022-09-15) - - -### Bug Fixes - -* customInsert 不触发 onSuccess ([d6f4a1b](https://github.com/wangeditor-team/wangEditor/commit/d6f4a1b1494864b116a1310cce2d9e8632c92c6f)) - - - - - -## 1.0.1 (2022-04-18) - - -### Bug Fixes - -* 多图片上传 ([53fe915](https://github.com/wangeditor-team/wangEditor/commit/53fe915aa7d40f05e1e9446c7f26606c46832ff3)) -* 更新各包之间依赖版本 ([75c552c](https://github.com/wangeditor-team/wangEditor/commit/75c552cc8ed54765bebb86a7ec5329a7fc79e85f)) -* 上传图片 - base64 仍触发上传 + 超出 maxSize 的报错提醒 ([a1d469a](https://github.com/wangeditor-team/wangEditor/commit/a1d469accb7f87f8ea0282a1699d002aaaa4e79a)) -* 图片上传,提示 ([3754012](https://github.com/wangeditor-team/wangEditor/commit/37540129dff1212c5ebfd4ca3f4d4e8def735e73)) -* 修复 pnpm 安装 @wangeditor/editor 出现警告的问题 ([4087fbe](https://github.com/wangeditor-team/wangEditor/commit/4087fbee01c76bdd55e747a5e86c5e4a8d6a8353)) -* 移除了每个包下的 publishConfig directory 配置 ([16559f0](https://github.com/wangeditor-team/wangEditor/commit/16559f052545c111318be760e64291a521bdcc65)) -* 粘贴 excel ([5382a6e](https://github.com/wangeditor-team/wangEditor/commit/5382a6edab2d362c7be143b62e7dd21bea8a15ab)) -* npm install error - basic-modules 相关 ([b85a0dc](https://github.com/wangeditor-team/wangEditor/commit/b85a0dcfaa15d69424d86a20255d6b9e8b28494f)) -* rename es module filename ([1821d4e](https://github.com/wangeditor-team/wangEditor/commit/1821d4eef49e64efcb41b848849ca7a5e6472044)) - - -### Features - -* 上传图片 metaWithUrl ([2485157](https://github.com/wangeditor-team/wangEditor/commit/24851576a1dcc07b1a8931d17a147c3640222e85)) -* 增加 enable disable API(删除 setConfig setMenuConfig API) ([984fc50](https://github.com/wangeditor-team/wangEditor/commit/984fc50520061fc34ea08f4136bdeb93dee46564)) -* editor.showProgressBar ([51761d4](https://github.com/wangeditor-team/wangEditor/commit/51761d466ab3ef7c99e872954d4724ab51d8e28c)) -* i18n ([c11b244](https://github.com/wangeditor-team/wangEditor/commit/c11b2440f91b99d40bca18b675c66a22b6e160c9)) -* image menu - width 50% 100% ([f9b4c68](https://github.com/wangeditor-team/wangEditor/commit/f9b4c68dff3232b50491b07949c20eb4c18baa6b)) -* image menu config ([bb18774](https://github.com/wangeditor-team/wangEditor/commit/bb187740e9703b4a76cde4f5e4d32ac714aa793a)) -* upload image ([0a0564b](https://github.com/wangeditor-team/wangEditor/commit/0a0564bf14edd4dea6eb958e653272a9a216cec1)) -* upload video ([ac8e6f8](https://github.com/wangeditor-team/wangEditor/commit/ac8e6f8b5258e593714676a6f6be359ba525833c)) diff --git a/packages/upload-image-module/README.md b/packages/upload-image-module/README.md deleted file mode 100644 index c78e6443c..000000000 --- a/packages/upload-image-module/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# wangEditor upload-image-module - -Upload image module built in [wangEditor](https://www.wangeditor.com/) by default. diff --git a/packages/upload-image-module/__tests__/config.test.ts b/packages/upload-image-module/__tests__/config.test.ts deleted file mode 100644 index b9c22636a..000000000 --- a/packages/upload-image-module/__tests__/config.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { genUploadImageConfig } from '../src/module/menu/config' - -describe('Upload image default config', () => { - test('Upload image invoke genUploadImageConfig should generate default config', () => { - expect(typeof genUploadImageConfig()).toBe('object') - }) - - test('The option server is "" in default config', () => { - expect(genUploadImageConfig().server).toBe('') - }) - - test('The option fieldName is "wangeditor-uploaded-image" in default config', () => { - expect(genUploadImageConfig().fieldName).toBe('wangeditor-uploaded-image') - }) - - test('The option maxFileSize is "2M" in default config', () => { - expect(genUploadImageConfig().maxFileSize).toBe(2 * 1024 * 1024) - }) - - test('The option maxNumberOfFiles is "100" in default config', () => { - expect(genUploadImageConfig().maxNumberOfFiles).toBe(100) - }) - - test('The option allowedFileTypes is "[image/*"]" in default config', () => { - expect(genUploadImageConfig().allowedFileTypes).toEqual(['image/*']) - }) - - test('The option metaWithUrl is "false" in default config', () => { - expect(genUploadImageConfig().metaWithUrl).toBe(false) - }) - - test('The option withCredentials is "false" in default config', () => { - expect(genUploadImageConfig().withCredentials).toBe(false) - }) - - test('The option timeout is "10s" in default config', () => { - expect(genUploadImageConfig().timeout).toBe(10 * 1000) - }) - - test('The option base64LimitSize is "0" in default config', () => { - expect(genUploadImageConfig().base64LimitSize).toBe(0) - }) -}) diff --git a/packages/upload-image-module/__tests__/plugin.test.ts b/packages/upload-image-module/__tests__/plugin.test.ts deleted file mode 100644 index 47f76b1d2..000000000 --- a/packages/upload-image-module/__tests__/plugin.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { IDomEditor } from '@wangeditor/core' -import * as basicModule from '@wangeditor/basic-modules' -import createEditor from '../../../tests/utils/create-editor' -import withUploadImage from '../src/module/plugin' -import * as uploadImage from '../src/module/upload-images' - -let editor: IDomEditor - -describe('withUploadImage plugin', () => { - beforeEach(() => { - editor = createEditor() - - // mock isInsertImageMenuDisabled - jest.spyOn(basicModule, 'isInsertImageMenuDisabled').mockImplementation(() => false) - }) - - test('withUploadImage plugin should invoke insertData directly for insert transfer data if isInsertImageMenuDisabled return truthy value', () => { - jest.spyOn(basicModule, 'isInsertImageMenuDisabled').mockImplementation(() => true) - - const fn = jest.fn() - editor.insertData = fn - - const newEditor = withUploadImage(editor) - newEditor.insertData(new DataTransfer()) - - expect(fn).toBeCalled() - }) - - test('withUploadImage plugin should invoke insertData with text data if transfer data contains plain text ', () => { - const fn = jest.fn() - editor.insertData = fn - - const newEditor = withUploadImage(editor) - jest.spyOn(DataTransfer.prototype, 'getData').mockImplementation(() => 'plain text') - const transfer = new DataTransfer() - newEditor.insertData(transfer) - - expect(transfer.getData('text/plain')).toBe('plain text') - expect(fn).toBeCalledWith(transfer) - - // 不影响后面的测试,需要重置 - jest.spyOn(DataTransfer.prototype, 'getData').mockImplementation(() => '') - }) - - test('withUploadImage plugin should invoke insertData with transfer data if transfer data contains empty files', () => { - const fn = jest.fn() - editor.insertData = fn - - const newEditor = withUploadImage(editor) - jest.spyOn(DataTransfer.prototype, 'files', 'get').mockReturnValue([] as any) - newEditor.insertData(new DataTransfer()) - - expect(fn).toBeCalled() - }) - - test('withUploadImage plugin should invoke uploadImage method with image files if transfer data contains file which mime type is image', () => { - const fn = jest.fn() - jest.spyOn(uploadImage, 'default').mockImplementation(fn) - - const newEditor = withUploadImage(editor) - jest - .spyOn(DataTransfer.prototype, 'files', 'get') - .mockReturnValue([{ type: 'image/png', size: 10 }] as any) - - newEditor.insertData(new DataTransfer()) - - expect(fn).toBeCalled() - }) - - test('withUploadImage plugin should invoke insertData method with transfer data if transfer data contains file which mime type is not image', () => { - const fn = jest.fn() - editor.insertData = fn - - const newEditor = withUploadImage(editor) - jest - .spyOn(DataTransfer.prototype, 'files', 'get') - .mockReturnValue([{ type: 'text/html', size: 10 }] as any) - - const transfer = new DataTransfer() - newEditor.insertData(transfer) - - expect(fn).toBeCalledWith(transfer) - }) -}) diff --git a/packages/upload-image-module/__tests__/upload-files.test.ts b/packages/upload-image-module/__tests__/upload-files.test.ts deleted file mode 100644 index fbc308c46..000000000 --- a/packages/upload-image-module/__tests__/upload-files.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import uploadImages from '../src/module/upload-images' -import createEditor from '../../../tests/utils/create-editor' -import * as core from '@wangeditor/core' - -function mockFile(filename: string) { - const file = new File(['123'], filename) - return file -} - -describe('Upload image menu upload files util', () => { - test('uploadImages should do nothing if give null value to fileList argument', async () => { - const editor = createEditor() - const res = await uploadImages(editor, null) - expect(res).toBeUndefined() - }) - - test('uploadImages should invoke customUpload if give customUpload to config', async () => { - const fn = jest.fn() - const editor = createEditor({ - config: { - MENU_CONF: { - uploadImage: { - customUpload: fn, - }, - }, - }, - }) - - await uploadImages(editor, [mockFile('test.jpg')] as unknown as FileList) - - expect(fn).toBeCalled() - }) - - test('uploadImages should insert image with base64 string if file size less than base64LimitSize config', async () => { - const fn = jest.fn() - const editor = createEditor({ - config: { - MENU_CONF: { - uploadImage: { - customUpload: fn, - base64LimitSize: 10, - }, - }, - }, - }) - - const mockReadAsDataURL = jest.spyOn(FileReader.prototype, 'readAsDataURL') - - await uploadImages(editor, [mockFile('test.jpg')] as unknown as FileList) - - expect(mockReadAsDataURL).toBeCalled() - }) - - test('uploadImages should invoke core createUploader if not give customUpload to config', async () => { - const fn = jest.fn().mockImplementation( - () => - // 这里需要返回一个 duck 类型的 uppy 对象,防止后面代码执行报错 - ({ - addFile: jest.fn(), - upload: jest.fn(), - } as any) - ) - const editor = createEditor() - - jest.spyOn(core, 'createUploader').mockImplementation(fn) - - await uploadImages(editor, [mockFile('test.jpg')] as unknown as FileList) - - expect(fn).toBeCalled() - }) -}) diff --git a/packages/upload-image-module/__tests__/upload-image-menu.test.ts b/packages/upload-image-module/__tests__/upload-image-menu.test.ts deleted file mode 100644 index b2368aaf4..000000000 --- a/packages/upload-image-module/__tests__/upload-image-menu.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { IDomEditor } from '../../../packages/editor/src' -import UploadImageMenu from '../src/module/menu/UploadImageMenu' -import createEditor from '../../../tests/utils/create-editor' - -let editor: IDomEditor -let menu: UploadImageMenu - -describe('Upload image menu', () => { - beforeEach(() => { - editor = createEditor() - menu = new UploadImageMenu() - }) - - test('UploadImageMenu instance title is "上传图片" for zhCn locale config', () => { - expect(menu.title).toBe('上传图片') - }) - - test('UploadImageMenu invoke getValue return ""', () => { - expect(menu.getValue(editor)).toBe('') - }) - - test('UploadImageMenu invoke isActive always return false', () => { - expect(menu.isActive(editor)).toBe(false) - }) - - test('UploadImageMenu invoke exec should exec customBrowseAndUpload if config has customBrowseAndUpload option', () => { - const jestFn = jest.fn() - const editor = createEditor({ - config: { - MENU_CONF: { - uploadImage: { - customBrowseAndUpload: jestFn, - }, - }, - }, - }) - menu.exec(editor, 'test.jpg') - expect(jestFn).toBeCalled() - }) - - test('UploadImageMenu invoke exec should insert hidden input element to body', () => { - const editor = createEditor({ - config: { - MENU_CONF: { - uploadImage: { - allowedFileTypes: ['jpg', 'png'], - }, - }, - }, - }) - - // 防卫断言 - expect(document.querySelector('input')).toBeNull() - - menu.exec(editor, 'test.jpg') - - expect(document.querySelector('input') instanceof HTMLInputElement).toBeTruthy() - }) -}) diff --git a/packages/upload-image-module/package.json b/packages/upload-image-module/package.json deleted file mode 100644 index 9918879d4..000000000 --- a/packages/upload-image-module/package.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "@wangeditor/upload-image-module", - "version": "1.0.2", - "description": "wangEditor upload-image module", - "author": "wangfupeng1988 ", - "contributors": [], - "homepage": "https://github.com/wangeditor-team/wangEditor#readme", - "license": "MIT", - "types": "dist/upload-image-module/src/index.d.ts", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "browser": { - "./dist/index.js": "./dist/index.js", - "./dist/index.esm.js": "./dist/index.esm.js" - }, - "directories": { - "lib": "dist", - "test": "__tests__" - }, - "files": [ - "dist" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.com/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wangeditor-team/wangEditor.git" - }, - "scripts": { - "test": "jest", - "test-c": "jest --coverage", - "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js", - "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", - "build": "cross-env NODE_ENV=production rollup -c rollup.config.js", - "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js", - "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js" - }, - "bugs": { - "url": "https://github.com/wangeditor-team/wangEditor/issues" - }, - "peerDependencies": { - "@uppy/core": "^2.0.3", - "@uppy/xhr-upload": "^2.0.3", - "@wangeditor/basic-modules": "1.x", - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.foreach": "^4.5.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } -} diff --git a/packages/upload-image-module/rollup.config.js b/packages/upload-image-module/rollup.config.js deleted file mode 100644 index bc80f5ce7..000000000 --- a/packages/upload-image-module/rollup.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' -import pkg from './package.json' - -const name = 'WangEditorUploadImageModule' - -const configList = [] - -// esm -const esmConf = createRollupConfig({ - output: { - file: pkg.module, - format: 'esm', - name, - }, -}) -configList.push(esmConf) - -// umd -const umdConf = createRollupConfig({ - output: { - file: pkg.main, - format: 'umd', - name, - }, -}) -configList.push(umdConf) - -export default configList diff --git a/packages/upload-image-module/src/assets/index.less b/packages/upload-image-module/src/assets/index.less deleted file mode 100644 index c9e89c84d..000000000 --- a/packages/upload-image-module/src/assets/index.less +++ /dev/null @@ -1 +0,0 @@ -// styles diff --git a/packages/upload-image-module/src/constants/svg.ts b/packages/upload-image-module/src/constants/svg.ts deleted file mode 100644 index e7323973b..000000000 --- a/packages/upload-image-module/src/constants/svg.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @description icon svg - * @author wangfupeng - */ - -/** - * 【注意】svg 字符串的长度 ,否则会导致代码体积过大 - * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293 - * 找不到再从 iconfont.com 搜索 - */ - -// 上传图片 -export const UPLOAD_IMAGE_SVG = - '' diff --git a/packages/upload-image-module/src/index.ts b/packages/upload-image-module/src/index.ts deleted file mode 100644 index 81f332799..000000000 --- a/packages/upload-image-module/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @description upload image - * @author wangfupeng - */ - -import './assets/index.less' - -// 配置多语言 -import './locale/index' - -import wangEditorUploadImageModule from './module/index' -export default wangEditorUploadImageModule diff --git a/packages/upload-image-module/src/locale/en.ts b/packages/upload-image-module/src/locale/en.ts deleted file mode 100644 index 6d8e96d3a..000000000 --- a/packages/upload-image-module/src/locale/en.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n en - * @author wangfupeng - */ - -export default { - uploadImgModule: { - uploadImage: 'Upload Image', - uploadError: '{{fileName}} upload error', - }, -} diff --git a/packages/upload-image-module/src/locale/index.ts b/packages/upload-image-module/src/locale/index.ts deleted file mode 100644 index 22f6ec6cb..000000000 --- a/packages/upload-image-module/src/locale/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n entry - * @author wangfupeng - */ - -import { i18nAddResources } from '@wangeditor/core' -import enResources from './en' -import zhResources from './zh-CN' - -i18nAddResources('en', enResources) -i18nAddResources('zh-CN', zhResources) diff --git a/packages/upload-image-module/src/locale/zh-CN.ts b/packages/upload-image-module/src/locale/zh-CN.ts deleted file mode 100644 index 148930d7c..000000000 --- a/packages/upload-image-module/src/locale/zh-CN.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @description i18n zh-CN - * @author wangfupeng - */ - -export default { - uploadImgModule: { - uploadImage: '上传图片', - uploadError: '{{fileName}} 上传出错', - }, -} diff --git a/packages/upload-image-module/src/module/index.ts b/packages/upload-image-module/src/module/index.ts deleted file mode 100644 index a7e893e93..000000000 --- a/packages/upload-image-module/src/module/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @description uploadImage module - * @author wangfupeng - */ - -import { IModuleConf } from '@wangeditor/core' -import withUploadImage from './plugin' -import { uploadImageMenuConf } from './menu/index' - -const uploadImage: Partial = { - menus: [uploadImageMenuConf], - editorPlugin: withUploadImage, -} - -export default uploadImage diff --git a/packages/upload-image-module/src/module/menu/UploadImageMenu.ts b/packages/upload-image-module/src/module/menu/UploadImageMenu.ts deleted file mode 100644 index 55399f078..000000000 --- a/packages/upload-image-module/src/module/menu/UploadImageMenu.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @description upload image menu - * @author wangfupeng - */ - -import { IButtonMenu, IDomEditor, t } from '@wangeditor/core' -import { insertImageNode, isInsertImageMenuDisabled } from '@wangeditor/basic-modules' -import { UPLOAD_IMAGE_SVG } from '../../constants/svg' -import $ from '../../utils/dom' -import { IUploadConfigForImage } from './config' -import uploadImages from '../upload-images' - -class UploadImage implements IButtonMenu { - readonly title = t('uploadImgModule.uploadImage') - readonly iconSvg = UPLOAD_IMAGE_SVG - readonly tag = 'button' - - getValue(editor: IDomEditor): string | boolean { - // 插入菜单,不需要 value - return '' - } - - isActive(editor: IDomEditor): boolean { - // 任何时候,都不用激活 menu - return false - } - - isDisabled(editor: IDomEditor): boolean { - return isInsertImageMenuDisabled(editor) - } - - private getMenuConfig(editor: IDomEditor): IUploadConfigForImage { - // 获取配置,见 `./config.js` - return editor.getMenuConfig('uploadImage') as IUploadConfigForImage - } - - exec(editor: IDomEditor, value: string | boolean) { - const { allowedFileTypes = [], customBrowseAndUpload } = this.getMenuConfig(editor) - - // 自定义选择图片,并上传,如图床 - if (customBrowseAndUpload) { - customBrowseAndUpload((src, alt, href) => insertImageNode(editor, src, alt, href)) - return - } - - // 设置选择文件的类型 - let acceptAttr = '' - if (allowedFileTypes.length > 0) { - acceptAttr = `accept="${allowedFileTypes.join(', ')}"` - } - - // 添加 file input(每次重新创建 input) - const $body = $('body') - const $inputFile = $(``) - $inputFile.hide() - $body.append($inputFile) - $inputFile.click() - // 选中文件 - $inputFile.on('change', () => { - const files = ($inputFile[0] as HTMLInputElement).files - uploadImages(editor, files) // 上传文件 - }) - } -} - -export default UploadImage diff --git a/packages/upload-image-module/src/module/menu/config.ts b/packages/upload-image-module/src/module/menu/config.ts deleted file mode 100644 index 8178e64c7..000000000 --- a/packages/upload-image-module/src/module/menu/config.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @description upload image config - * @author wangfupeng - */ - -import { IUploadConfig } from '@wangeditor/core' - -type InsertFn = (src: string, alt: string, href: string) => void - -// 在通用 uploadConfig 上,扩展 image 相关配置 -export type IUploadConfigForImage = IUploadConfig & { - allowedFileTypes?: string[] - // 用户自定义插入图片 - customInsert?: (res: any, insertFn: InsertFn) => void - // 用户自定义上传图片 - customUpload?: (files: File, insertFn: InsertFn) => void - // base64 限制(单位 kb) - 小于 xxx 就插入 base64 格式 - base64LimitSize: number - // 自定义选择图片,如图床 - customBrowseAndUpload?: (insertFn: InsertFn) => void -} - -// 生成默认配置 -export function genUploadImageConfig(): IUploadConfigForImage { - return { - server: '', // server API 地址,需用户配置 - - fieldName: 'wangeditor-uploaded-image', // formData 中,文件的 key - maxFileSize: 2 * 1024 * 1024, // 2M - maxNumberOfFiles: 100, // 最多上传 xx 张图片 - allowedFileTypes: ['image/*'], - meta: { - // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。 - // 例如:token: 'xxxxx', x: 100 - }, - metaWithUrl: false, - // headers: { - // // 自定义 http headers - // // 例如:Accept: 'text/x-json', a: 100, - // }, - withCredentials: false, - timeout: 10 * 1000, // 10s - - onBeforeUpload: (files: any) => files, // 返回 false 则终止上传 - onProgress: (progress: number) => { - /* on progress */ - }, - onSuccess: (file: any, res: any) => { - /* on success */ - }, - onFailed: (file: any, res: any) => { - console.error(`'${file.name}' upload failed`, res) - }, - onError: (file: any, err: any, res: any) => { - /* on error */ - /* on timeout */ - console.error(`'${file.name}' upload error`, res) - }, - - // 自定义插入图片,用户配置 - // customInsert: (res, insertFn) => {}, - - // 自定义上传图片,用户配置 - // customUpload: (file, insertFn) => {}, - - // 小于 xxx 就插入 base64 - base64LimitSize: 0, - - // 自定义选择,并上传图片,如:图床 (用户配置) - // customBrowseAndUpload: insertFn => {}, - } -} diff --git a/packages/upload-image-module/src/module/menu/index.ts b/packages/upload-image-module/src/module/menu/index.ts deleted file mode 100644 index 21c880cb3..000000000 --- a/packages/upload-image-module/src/module/menu/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @description upload image menu - * @author wangfupeng - */ - -import UploadImageMenu from './UploadImageMenu' -import { genUploadImageConfig } from './config' - -export const uploadImageMenuConf = { - key: 'uploadImage', - factory() { - return new UploadImageMenu() - }, - - // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中 - // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改 - config: genUploadImageConfig(), -} diff --git a/packages/upload-image-module/src/module/plugin.ts b/packages/upload-image-module/src/module/plugin.ts deleted file mode 100644 index aabbf3c1c..000000000 --- a/packages/upload-image-module/src/module/plugin.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @description editor 插件,重写 editor API - * @author wangfupeng - */ - -import { IDomEditor } from '@wangeditor/core' -import { isInsertImageMenuDisabled } from '@wangeditor/basic-modules' -import uploadImages from './upload-images' - -function withUploadImage(editor: T): T { - const { insertData } = editor - const newEditor = editor - - // 重写 insertData - 粘贴图片、拖拽上传图片 - newEditor.insertData = (data: DataTransfer) => { - if (isInsertImageMenuDisabled(newEditor)) { - insertData(data) - return - } - - // 如有 text ,则优先粘贴 text - const text = data.getData('text/plain') - if (text) { - insertData(data) - return - } - - // 获取文件 - const { files } = data - if (files.length <= 0) { - insertData(data) - return - } - - // 判断是否有图片文件(可能是其他类型的文件) - const fileList = Array.prototype.slice.call(files) - let _hasImageFiles = fileList.some(file => { - const [mime] = file.type.split('/') - return mime === 'image' - }) - - if (_hasImageFiles) { - // 有图片文件,则上传图片 - uploadImages(editor, files) - } else { - // 如果没有, 则继续 insertData - insertData(data) - } - } - - // 返回 editor ,重要! - return newEditor -} - -export default withUploadImage diff --git a/packages/upload-image-module/src/module/upload-images.ts b/packages/upload-image-module/src/module/upload-images.ts deleted file mode 100644 index 9392a6936..000000000 --- a/packages/upload-image-module/src/module/upload-images.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * @description 上传文件 - * @author wangfupeng - */ - -import Uppy, { UppyFile } from '@uppy/core' -import { IDomEditor, createUploader } from '@wangeditor/core' -import { insertImageNode } from '@wangeditor/basic-modules' -import { IUploadConfigForImage } from './menu/config' - -// 存储 editor uppy 的关系 - 缓存 uppy ,不重复创建 -const EDITOR_TO_UPPY_MAP = new WeakMap() - -/** - * 获取 uppy 实例(并通过 editor 缓存) - * @param editor editor - */ -function getUppy(editor: IDomEditor): Uppy { - // 从缓存中获取 - let uppy = EDITOR_TO_UPPY_MAP.get(editor) - if (uppy != null) return uppy - - const menuConfig = getMenuConfig(editor) - const { onSuccess, onProgress, onFailed, customInsert, onError } = menuConfig - - // 上传完成之后 - const successHandler = (file: UppyFile, res: any) => { - // 预期 res 格式: - // 成功:{ errno: 0, data: { url, alt, href } } —— 注意,旧版的 data 是数组,要兼容一下 - // 失败:{ errno: !0, message: '失败信息' } - - if (customInsert) { - // 用户自定义插入图片,此时 res 格式可能不符合预期 - customInsert(res, (src, alt, href) => insertImageNode(editor, src, alt, href)) - // success 回调 - onSuccess(file, res) - return - } - - let { errno = 1, data = {} } = res - if (errno !== 0) { - // failed 回调 - onFailed(file, res) - return - } - - if (Array.isArray(data)) { - // 返回的数组(旧版的,兼容一下) - data.forEach((item: { url: string; alt?: string; href?: string }) => { - const { url = '', alt = '', href = '' } = item - // 使用 basic-module 的 insertImageNode 方法插入图片,其中有用户配置的校验和 callback - insertImageNode(editor, url, alt, href) - }) - } else { - // 返回的对象 - const { url = '', alt = '', href = '' } = data - insertImageNode(editor, url, alt, href) - } - - // success 回调 - onSuccess(file, res) - } - - // progress 显示进度条 - const progressHandler = (progress: number) => { - editor.showProgressBar(progress) - - // 回调函数 - onProgress && onProgress(progress) - } - - // onError 提示错误 - const errorHandler = (file: any, err: any, res: any) => { - // 回调函数 - onError(file, err, res) - } - - // 创建 uppy - uppy = createUploader({ - ...menuConfig, - onProgress: progressHandler, - onSuccess: successHandler, - onError: errorHandler, - }) - // 缓存 uppy - EDITOR_TO_UPPY_MAP.set(editor, uppy) - - return uppy -} - -function getMenuConfig(editor: IDomEditor) { - return editor.getMenuConfig('uploadImage') as IUploadConfigForImage -} - -/** - * 插入 base64 格式 - * @param editor editor - * @param file file - */ -async function insertBase64(editor: IDomEditor, file: File) { - return new Promise(resolve => { - const reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = () => { - const { result } = reader - if (!result) return - const src = result.toString() - let href = src.indexOf('data:image') === 0 ? '' : src // base64 格式则不设置 href - insertImageNode(editor, src, file.name, href) - - resolve('ok') - } - }) -} - -/** - * 上传图片文件 - * @param editor editor - * @param file file - */ -async function uploadFile(editor: IDomEditor, file: File) { - const uppy = getUppy(editor) - - const { name, type, size } = file - uppy.addFile({ - name, - type, - size, - data: file, - }) - await uppy.upload() -} - -/** - * 上传图片 - * @param editor editor - * @param files files - */ -export default async function (editor: IDomEditor, files: FileList | null) { - if (files == null) return - const fileList = Array.prototype.slice.call(files) - - // 获取菜单配置 - const { customUpload, base64LimitSize } = getMenuConfig(editor) - - // 按顺序上传 - for await (const file of fileList) { - const size = file.size // size kb - if (base64LimitSize && size <= base64LimitSize) { - // 允许 base64 ,而且 size 在 base64 限制之内,则插入 base64 格式 - await insertBase64(editor, file) - } else { - // 上传 - if (customUpload) { - // 自定义上传 - await customUpload(file, (src, alt, href) => insertImageNode(editor, src, alt, href)) - } else { - // 默认上传 - await uploadFile(editor, file) - } - } - } -} diff --git a/packages/upload-image-module/src/utils/dom.ts b/packages/upload-image-module/src/utils/dom.ts deleted file mode 100644 index 195645487..000000000 --- a/packages/upload-image-module/src/utils/dom.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @description DOM 操作 - * @author wangfupeng - */ - -import $, { append, on, remove, val, click, hide } from 'dom7' -export { Dom7Array } from 'dom7' - -if (append) $.fn.append = append -if (on) $.fn.on = on -if (remove) $.fn.remove = remove -if (val) $.fn.val = val -if (click) $.fn.click = click -if (hide) $.fn.hide = hide - -export default $ diff --git a/packages/upload-image-module/tsconfig.json b/packages/upload-image-module/tsconfig.json deleted file mode 100644 index 9bef938c9..000000000 --- a/packages/upload-image-module/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": {}, - "extends": "../../tsconfig.json", - "include": [ - "./src/**/*", - "../custom-types.d.ts" - ] -} \ No newline at end of file diff --git a/packages/vars.less b/packages/vars.less deleted file mode 100644 index a657e1954..000000000 --- a/packages/vars.less +++ /dev/null @@ -1,33 +0,0 @@ -// 注意:css vars 全部都定义在 packages/editor/src/assets/index.less - -@size: 14px; - -// textarea - less vars -@textarea-color: var(--w-e-textarea-color); -@textarea-bg-color: var(--w-e-textarea-bg-color); -@textarea-selected-border-color: var(--w-e-textarea-selected-border-color); -@textarea-slight-color: var(--w-e-textarea-slight-color); -@textarea-slight-bg-color: var(--w-e-textarea-slight-bg-color); -@textarea-border-color: var(--w-e-textarea-border-color); -@textarea-slight-border-color: var( --w-e-textarea-slight-border-color); -@textarea-handler-bg-color: var(--w-e-textarea-handler-bg-color); - -// toolbar - less vars -@toolbar-color: var(--w-e-toolbar-color); -@toolbar-bg-color: var(--w-e-toolbar-bg-color); -@toolbar-active-color: var(--w-e-toolbar-active-color); -@toolbar-active-bg-color: var(--w-e-toolbar-active-bg-color); -@toolbar-disabled-color: var(--w-e-toolbar-disabled-color); -@toolbar-border-color: var(--w-e-toolbar-border-color); -@toolbar-height: 40px; - -// modal - less vars -@modal-button-bg-color: var(--w-e-modal-button-bg-color); -@modal-button-border-color: var(--w-e-modal-button-border-color); - -// less mixins -.shadowBordered(@shadowRadius: 5px) { - border: 1px solid @toolbar-border-color; - border-radius: 3px; - box-shadow: 0 2px @shadowRadius #0000001f; -} diff --git a/packages/video-module/CHANGELOG.md b/packages/video-module/CHANGELOG.md deleted file mode 100644 index 356721c8d..000000000 --- a/packages/video-module/CHANGELOG.md +++ /dev/null @@ -1,91 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.1.4](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/video-module@1.1.3...@wangeditor/video-module@1.1.4) (2022-09-27) - -**Note:** Version bump only for package @wangeditor/video-module - - - - - -## [1.1.3](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/video-module@1.1.2...@wangeditor/video-module@1.1.3) (2022-09-15) - - -### Bug Fixes - -* customInsert 不触发 onSuccess ([d6f4a1b](https://github.com/wangeditor-team/wangEditor/commit/d6f4a1b1494864b116a1310cce2d9e8632c92c6f)) -* 上传视频 - customBrowseAndUpload 缺少 poster ([c24627a](https://github.com/wangeditor-team/wangEditor/commit/c24627aaa4c173c5d435e3077dfe8f6b4a9a87b1)) - - - - - -## [1.1.2](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/video-module@1.1.1...@wangeditor/video-module@1.1.2) (2022-08-30) - - -### Bug Fixes - -* checkVideo 增加 poster 参数 ([c0402e1](https://github.com/wangeditor-team/wangEditor/commit/c0402e155470233d256e037d863dab74c026b7f6)) - - - - - -## [1.1.1](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/video-module@1.1.0...@wangeditor/video-module@1.1.1) (2022-07-14) - - -### Bug Fixes - -* video poster(不想升级大版本,所有暂用 fix 不用 feature) ([5a2aff9](https://github.com/wangeditor-team/wangEditor/commit/5a2aff92bc23f240bd249a7294874940cfc9f717)) - - - - - -# [1.1.0](https://github.com/wangeditor-team/wangEditor/compare/@wangeditor/video-module@1.0.1...@wangeditor/video-module@1.1.0) (2022-05-25) - - -### Features - -* editVideoSize ([375eecb](https://github.com/wangeditor-team/wangEditor/commit/375eecba826eac681268c55c47bcd922f7157d63)) -* enter menu ([988fc31](https://github.com/wangeditor-team/wangEditor/commit/988fc31f31de3d37dffbf54abb784cceb8e6118d)) -* setHtml ([f4f91b8](https://github.com/wangeditor-team/wangEditor/commit/f4f91b883298091e3679ca6b206ae0d796003772)) -* 表格拖拽列宽 ([46ea2c0](https://github.com/wangeditor-team/wangEditor/commit/46ea2c0f831b03ebca5fddfd59d682fed0b3476e)) - - - - - -## 1.0.1 (2022-04-18) - - -### Bug Fixes - -* 部分菜单 disabled ([87f1233](https://github.com/wangeditor-team/wangEditor/commit/87f12332a087072406c1988dc5cef2eae8335375)) -* 插入图片的 < > 替换 ([5721560](https://github.com/wangeditor-team/wangEditor/commit/57215609ada8b9d15f5505d1ba52e49707b5b183)) -* 更新各包之间依赖版本 ([75c552c](https://github.com/wangeditor-team/wangEditor/commit/75c552cc8ed54765bebb86a7ec5329a7fc79e85f)) -* 修复 pnpm 安装 @wangeditor/editor 出现警告的问题 ([4087fbe](https://github.com/wangeditor-team/wangEditor/commit/4087fbee01c76bdd55e747a5e86c5e4a8d6a8353)) -* 修复视频无法被xml-formatter解析的问题 ([e081518](https://github.com/wangeditor-team/wangEditor/commit/e08151863628e0241fe4a3d5858cda4c8ea57949)), closes [#101](https://github.com/wangeditor-team/wangEditor/issues/101) [#95](https://github.com/wangeditor-team/wangEditor/issues/95) [#70](https://github.com/wangeditor-team/wangEditor/issues/70) [#69](https://github.com/wangeditor-team/wangEditor/issues/69) -* 移除了每个包下的 publishConfig directory 配置 ([16559f0](https://github.com/wangeditor-team/wangEditor/commit/16559f052545c111318be760e64291a521bdcc65)) -* fix插入视频报错的问题 ([f78b06d](https://github.com/wangeditor-team/wangEditor/commit/f78b06d7f75c288f306f04fbfec1dfeb1332a861)) -* fix视频插入iframe时报错的问题 ([ad8f9ce](https://github.com/wangeditor-team/wangEditor/commit/ad8f9cea0f7eae1cb0bc51dba64585be05dfda2f)) -* menu active ([10829e2](https://github.com/wangeditor-team/wangEditor/commit/10829e2e9e1d864d4900821ee3d5fa516b8cca2a)) -* parse html - v4 video ([8dca822](https://github.com/wangeditor-team/wangEditor/commit/8dca822f9f1b52fd71dd6e17f0954d6aa016324b)) -* rename es module filename ([1821d4e](https://github.com/wangeditor-team/wangEditor/commit/1821d4eef49e64efcb41b848849ca7a5e6472044)) -* shadow dom 中 modal 输入框异常 ([ef3b199](https://github.com/wangeditor-team/wangEditor/commit/ef3b199a3e74c6b8ba61ed781e1aa13a1c5acfde)) -* table - elemToHtml ([e36e609](https://github.com/wangeditor-team/wangEditor/commit/e36e6092ef721723169afc8bf0560a47ac9f4dfc)) -* video - 键盘删除 ([5a6bedd](https://github.com/wangeditor-team/wangEditor/commit/5a6bedd80fa0d758270731f62115637ad7f313d0)) - - -### Features - -* 增加 enable disable API(删除 setConfig setMenuConfig API) ([984fc50](https://github.com/wangeditor-team/wangEditor/commit/984fc50520061fc34ea08f4136bdeb93dee46564)) -* i18n ([c11b244](https://github.com/wangeditor-team/wangEditor/commit/c11b2440f91b99d40bca18b675c66a22b6e160c9)) -* parse html ([2a5eace](https://github.com/wangeditor-team/wangEditor/commit/2a5eace00f33cded50b68e8164748ec2480213fd)) -* parse src (link image video) ([715a841](https://github.com/wangeditor-team/wangEditor/commit/715a841fc6c730ee2b448a1799a07ce778128aad)) -* toHtml 机制 ([1c4d872](https://github.com/wangeditor-team/wangEditor/commit/1c4d8729f84aaab6a448f23064b34a20596305e9)) -* upload video ([ac8e6f8](https://github.com/wangeditor-team/wangEditor/commit/ac8e6f8b5258e593714676a6f6be359ba525833c)) -* video menu config ([7fa3783](https://github.com/wangeditor-team/wangEditor/commit/7fa3783c42aa83f7d53c8be34be3c8b7c8a64754)) diff --git a/packages/video-module/README.md b/packages/video-module/README.md deleted file mode 100644 index 6956ae84e..000000000 --- a/packages/video-module/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# wangEditor video-module - -Video module built in [wangEditor](https://www.wangeditor.com/) by default. diff --git a/packages/video-module/__tests__/elem-to-html.test.ts b/packages/video-module/__tests__/elem-to-html.test.ts deleted file mode 100644 index a0272b33e..000000000 --- a/packages/video-module/__tests__/elem-to-html.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @description video menu test - * @author luochao - */ - -import videoModule from '../src/' - -const videoToHtmlConf = videoModule.elemsToHtml![0] - -describe('videoModule module', () => { - describe('module elem-to-html', () => { - test('videoToHtmlConf should return object that include "type" and "elemToHtml" property', () => { - expect(videoToHtmlConf.type).toBe('video') - expect(typeof videoToHtmlConf.elemToHtml).toBe('function') - }) - - test('videoToHtmlConf elemToHtml fn should return html video string', () => { - const element = { - type: 'video', - src: 'test.mp4', - poster: 'xxx.png', - children: [], - } - const res = videoToHtmlConf.elemToHtml(element, '') - - expect(res).toEqual( - '
              \n\n
              ' - ) - }) - - test('videoToHtmlConf elemToHtml should return original string if src is a iframe html string', () => { - const element = { - type: 'video', - src: '', - poster: 'xxx.png', - width: '500', - height: '300', - children: [], - } - const res = videoToHtmlConf.elemToHtml(element, '') - - expect(res).toEqual( - '
              \n\n
              ' - ) - }) - }) -}) diff --git a/packages/video-module/__tests__/helpler.test.ts b/packages/video-module/__tests__/helpler.test.ts deleted file mode 100644 index 77b8f124f..000000000 --- a/packages/video-module/__tests__/helpler.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import createEditor from '../../../tests/utils/create-editor' -import insertVideo from '../src/module/helper/insert-video' -import uploadVideos from '../src/module/helper/upload-videos' -import * as slate from 'slate' -import nock from 'nock' - -const server = 'https://fake-endpoint.wangeditor-v5.com' - -let editor: ReturnType -describe('Video module helper', () => { - beforeEach(() => { - editor = createEditor() - }) - - describe('insert-video helper', () => { - test('it should return if give empty src', async () => { - expect(await insertVideo(editor, '', '')).toBeUndefined() - }) - - test('it should alert result if checkVideo return result that data type is string', async () => { - const editor = createEditor({ - config: { - MENU_CONF: { - insertVideo: { - checkVideo: (_src: string, _poster: string) => 'check result', - }, - }, - }, - }) - const fn = jest.fn() - editor.alert = fn - - await insertVideo(editor, 'test.mp4', 'xxx.png') - - expect(fn).toBeCalledWith('check result', 'error') - }) - - test('it should return if checkVideo return null', async () => { - const editor = createEditor({ - config: { - MENU_CONF: { - insertVideo: { - checkVideo: (_src: string, _poster: string) => null, - }, - }, - }, - }) - - expect(await insertVideo(editor, 'test.mp4', 'xxx.png')).toBeUndefined() - }) - - test('it should invoke slate insertNodes method if give right src', done => { - const fn = jest.fn() - jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(fn) - - insertVideo(editor, 'test.mp4', 'xxx.png').then(() => { - setTimeout(() => { - expect(fn).toBeCalled() - done() - }) - }) - }) - - test('it should invoke onInsertedVideo callback if pass the option when create editor', done => { - const fn = jest.fn() - - const editor = createEditor({ - config: { - MENU_CONF: { - insertVideo: { - onInsertedVideo: fn, - }, - }, - }, - }) - - insertVideo(editor, 'test.mp4', 'xxx.png').then(() => { - expect(fn).toBeCalled() - done() - }) - }) - - test('it should parse iframe if give iframe element', done => { - const fn = jest.fn() - jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(fn) - - insertVideo(editor, '').then(() => { - setTimeout(() => { - expect(fn).toBeCalled() - done() - }) - }) - }) - }) - - describe('upload-video helper', () => { - test('it should return if give null', async () => { - expect(await uploadVideos(editor, null)).toBeUndefined() - }) - - test('it should invoke customUpload if give the option when create editor', async () => { - const fn = jest.fn() - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - customUpload: fn, - }, - }, - }, - }) - - await uploadVideos(editor, [new File(['123'], 'test.png')] as unknown as FileList) - - expect(fn).toBeCalled() - }) - - test('it should invoke onSuccess callback if give the option when create editor', async () => { - const fn = jest.fn() - nock(server) - .defaultReplyHeaders({ - 'access-control-allow-method': 'POST', - 'access-control-allow-origin': '*', - }) - .options('/') - .reply(200, {}) - .post('/') - .reply(200, { errno: 0 }) - - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - server, - onSuccess: fn, - }, - }, - }, - }) - - await uploadVideos(editor, [new File(['test123'], 'foo.jpg')] as unknown as FileList) - - expect(fn).toBeCalled() - }) - - test('it should invoke onProgress callback and show progress bar if uploading', async () => { - const mockOnProgress = jest.fn() - nock(server) - .defaultReplyHeaders({ - 'access-control-allow-method': 'POST', - 'access-control-allow-origin': '*', - }) - .options('/') - .reply(200, {}) - .post('/') - .reply(200, { errno: 0 }) - - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - server, - onProgress: mockOnProgress, - }, - }, - }, - }) - - const mockShowProgressBar = jest.fn() - editor.showProgressBar = mockShowProgressBar - - await uploadVideos(editor, [new File(['test123'], 'foo.jpg')] as unknown as FileList) - - expect(mockOnProgress).toBeCalled() - expect(mockShowProgressBar).toBeCalled() - }) - - test('it should invoke onError callback if upload failed', () => { - const fn = jest.fn() - nock(server) - .defaultReplyHeaders({ - 'access-control-allow-method': 'POST', - 'access-control-allow-origin': '*', - }) - .options('/') - .reply(200, {}) - .post('/') - .reply(400, {}) - - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - server, - onError: fn, - }, - }, - }, - }) - - uploadVideos(editor, [new File(['test123'], 'foo.jpg')] as unknown as FileList).catch(() => { - expect(fn).toBeCalled() - }) - }) - - test('it should invoke onFail callback if upload result with error', async () => { - const fn = jest.fn() - nock(server) - .defaultReplyHeaders({ - 'access-control-allow-method': 'POST', - 'access-control-allow-origin': '*', - }) - .options('/') - .reply(200, {}) - .post('/') - .reply(200, { error: 1 }) - - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - server, - onFailed: fn, - }, - }, - }, - }) - - await uploadVideos(editor, [new File(['test123'], 'foo.jpg')] as unknown as FileList) - - expect(fn).toBeCalled() - }) - - test('it should invoke customInsert callback if upload successfully', async () => { - const fn = jest.fn() - nock(server) - .defaultReplyHeaders({ - 'access-control-allow-method': 'POST', - 'access-control-allow-origin': '*', - }) - .options('/') - .reply(200, {}) - .post('/') - .reply(200, { error: 0 }) - - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - server, - customInsert: fn, - }, - }, - }, - }) - - await uploadVideos(editor, [new File(['test123'], 'foo.jpg')] as unknown as FileList) - - expect(fn).toBeCalled() - }) - }) -}) diff --git a/packages/video-module/__tests__/menu/delete-video-menu.test.ts.bak b/packages/video-module/__tests__/menu/delete-video-menu.test.ts.bak deleted file mode 100644 index 5b05b3574..000000000 --- a/packages/video-module/__tests__/menu/delete-video-menu.test.ts.bak +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @description video menu test - * @author luochao - */ - -import createEditor from '../../../../tests/utils/create-editor' -import DeleteVideoMenu from '../../src/module/menu' -import * as core from '@wangeditor/core' -import * as slate from 'slate' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} - -describe('videoModule module', () => { - describe('module DeleteVideoMenu', () => { - const deleteVideoMenu = new DeleteVideoMenu() - const editor = createEditor() - - test('DeleteVideoMenu invoke getValue function should be empty string', () => { - expect(deleteVideoMenu.getValue(editor)).toBe('') - }) - - test('DeleteVideoMenu invoke isActive function should be false', () => { - expect(deleteVideoMenu.isActive(editor)).toBe(false) - }) - - test('DeleteVideoMenu invoke isDisabled function if editor selected video element should be false', () => { - jest - .spyOn(core.DomEditor, 'getSelectedNodeByType') - .mockReturnValue({ type: 'video', children: [{ text: '' }] } as any) - - setEditorSelection(editor) - - expect(deleteVideoMenu.isDisabled(editor)).toBe(false) - }) - - test('DeleteVideoMenu invoke isDisabled function if editor do not selected video element should be true', () => { - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockReturnValue(null) - - setEditorSelection(editor) - - expect(deleteVideoMenu.isDisabled(editor)).toBe(true) - }) - - test('DeleteVideoMenu invoke exec function if video menu is disabled should return directly', () => { - jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockReturnValue(null) - const fn = jest.spyOn(slate.Transforms, 'removeNodes') - - setEditorSelection(editor) - - deleteVideoMenu.exec(editor, '') - - expect(fn).not.toBeCalled() - }) - - test('DeleteVideoMenu invoke exec function if video menu is disabled should execute transform removeNodes', () => { - jest - .spyOn(core.DomEditor, 'getSelectedNodeByType') - .mockReturnValue({ type: 'video', children: [{ text: '' }] } as any) - - const fn = jest.spyOn(slate.Transforms, 'removeNodes') - - setEditorSelection(editor) - - deleteVideoMenu.exec(editor, '') - - expect(fn).toBeCalled() - }) - }) -}) diff --git a/packages/video-module/__tests__/menu/insert-video-menu.test.ts b/packages/video-module/__tests__/menu/insert-video-menu.test.ts deleted file mode 100644 index 5e15ee1a7..000000000 --- a/packages/video-module/__tests__/menu/insert-video-menu.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @description video menu test - * @author luochao - */ - -import createEditor from '../../../../tests/utils/create-editor' -import InsertVideoMenu from '../../src/module/menu/InsertVideoMenu' -import * as core from '@wangeditor/core' -import * as slate from 'slate' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} - -describe('videoModule module', () => { - describe('module InsertVideoMenu', () => { - const insertVideoMenu = new InsertVideoMenu() - const editor = createEditor() - - test('InsertVideoMenu invoke getValue function should be empty string', () => { - expect(insertVideoMenu.getValue(editor)).toBe('') - }) - - test('InsertVideoMenu invoke isActive function should be false', () => { - expect(insertVideoMenu.isActive(editor)).toBe(false) - }) - - test('InsertVideoMenu invoke isDisabled if editor selection is null that the function return true', () => { - setEditorSelection(editor, null) - expect(insertVideoMenu.isDisabled(editor)).toBe(true) - }) - - test('InsertVideoMenu invoke isDisabled if editor selection is not collapsed that the function return true', () => { - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockReturnValue(false) - expect(insertVideoMenu.isDisabled(editor)).toBe(true) - }) - - test('InsertVideoMenu invoke isDisabled if editor selection is not null and collapsed that the function return false', () => { - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockReturnValue(true) - expect(insertVideoMenu.isDisabled(editor)).toBe(false) - }) - - test('InsertVideoMenu invoke getModalPositionNode should return null', () => { - expect(insertVideoMenu.getModalPositionNode(editor)).toBeNull() - }) - - test('InsertVideoMenu invoke getModalContentElem should return HTML element', () => { - expect(insertVideoMenu.getModalContentElem(editor) instanceof HTMLElement).toBe(true) - }) - }) -}) diff --git a/packages/video-module/__tests__/menu/upload-video-menu.test.ts b/packages/video-module/__tests__/menu/upload-video-menu.test.ts deleted file mode 100644 index cd37e9bbe..000000000 --- a/packages/video-module/__tests__/menu/upload-video-menu.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @description video menu test - * @author luochao - */ - -import createEditor from '../../../../tests/utils/create-editor' -import UploadVideoMenu from '../../src/module/menu/UploadVideoMenu' -import * as core from '@wangeditor/core' -import * as slate from 'slate' -import $ from '../../src/utils/dom' - -function setEditorSelection( - editor: core.IDomEditor, - selection: slate.Selection = { - anchor: { path: [0, 0], offset: 0 }, - focus: { path: [0, 0], offset: 0 }, - } -) { - editor.selection = selection -} - -describe('videoModule module', () => { - describe('module UploadVideoMenu', () => { - const uploadVideoMenu = new UploadVideoMenu() - const editor = createEditor() - - test('UploadVideoMenu invoke getValue function should be empty string', () => { - expect(uploadVideoMenu.getValue(editor)).toBe('') - }) - - test('UploadVideoMenu invoke isActive function should be false', () => { - expect(uploadVideoMenu.isActive(editor)).toBe(false) - }) - - test('UploadVideoMenu invoke isDisabled if editor selection is null that the function return true', () => { - setEditorSelection(editor, null) - expect(uploadVideoMenu.isDisabled(editor)).toBe(true) - }) - - test('UploadVideoMenu invoke isDisabled if editor selection is not collapsed that the function return true', () => { - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockReturnValue(false) - expect(uploadVideoMenu.isDisabled(editor)).toBe(true) - }) - - test('UploadVideoMenu invoke isDisabled if editor selection is not null and collapsed that the function return false', () => { - setEditorSelection(editor) - - jest.spyOn(slate.Range, 'isCollapsed').mockReturnValue(true) - expect(uploadVideoMenu.isDisabled(editor)).toBe(false) - }) - - test('UploadVideoMenu invoke customBrowseAndUpload if editor give customBrowseAndUpload option', () => { - const fn = jest.fn() - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - customBrowseAndUpload: fn, - }, - }, - }, - }) - - uploadVideoMenu.exec(editor, '') - - expect(fn).toBeCalled() - }) - - test('it should insert input element to body if invoke exec method', () => { - const editor = createEditor() - - expect($('input').length).toBe(0) - - uploadVideoMenu.exec(editor, '') - - expect($('input').length).toBeGreaterThan(0) - }) - - test('it should insert input element with accept attr if editor config allowedFileTypes', () => { - const editor = createEditor({ - config: { - MENU_CONF: { - uploadVideo: { - allowedFileTypes: ['video/*'], - }, - }, - }, - }) - - uploadVideoMenu.exec(editor, '') - - expect($('input')[0].getAttribute('accept')).toBe('video/*') - }) - }) -}) diff --git a/packages/video-module/__tests__/parse-html.test.ts b/packages/video-module/__tests__/parse-html.test.ts deleted file mode 100644 index 41db9f3c3..000000000 --- a/packages/video-module/__tests__/parse-html.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @description parse html test - * @author wangfupeng - */ - -import { $ } from 'dom7' -import createEditor from '../../../tests/utils/create-editor' -import videoModule from '../src' - -const { parseElemsHtml, preParseHtml } = videoModule -const [parseHtmlConf] = parseElemsHtml! -const [preParseHtmlConf] = preParseHtml! - -describe('video - pre parse html', () => { - it('iframe', () => { - const $iframe = $('') - - // match selector - expect($iframe[0].matches(preParseHtmlConf.selector)).toBeTruthy() - - // pre parse - const res = preParseHtmlConf.preParseHtml($iframe[0]) - expect(res.outerHTML).toBe( - '
              ' - ) - }) - - it('video', () => { - const $video = $('') - - // match selector - expect($video[0].matches(preParseHtmlConf.selector)).toBeTruthy() - - // pre parse - const res = preParseHtmlConf.preParseHtml($video[0]) - expect(res.outerHTML).toBe( - '
              ' - ) - }) - - it('it should parse video element which is wrapped by p', () => { - const $video = $('

              ') - - // match selector - expect($video[0].matches(preParseHtmlConf.selector)).toBeTruthy() - - // pre parse - const res = preParseHtmlConf.preParseHtml($video[0]) - expect(res.outerHTML).toBe( - '
              ' - ) - }) -}) - -describe('video - parse html', () => { - const editor = createEditor() - - it('iframe', () => { - const iframeHtml = '' - const $container = $(`
              ${iframeHtml}
              `) - - // match selector - expect($container[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - expect(parseHtmlConf.parseElemHtml($container[0], [], editor)).toEqual({ - type: 'video', - src: iframeHtml, - poster: '', - width: '500', - height: '300', - children: [{ text: '' }], // void 元素有一个空 text - }) - }) - - it('video', () => { - const src = 'xxx.mp4' - const poster = 'xxx.png' - const videoHtml = `` - const $container = $(`
              ${videoHtml}
              `) - - // match selector - expect($container[0].matches(parseHtmlConf.selector)).toBeTruthy() - - // parse - expect(parseHtmlConf.parseElemHtml($container[0], [], editor)).toEqual({ - type: 'video', - src, - poster, - width: 'auto', - height: 'auto', - children: [{ text: '' }], // void 元素有一个空 text - }) - }) -}) diff --git a/packages/video-module/__tests__/plugin.test.ts b/packages/video-module/__tests__/plugin.test.ts deleted file mode 100644 index ff36b69b2..000000000 --- a/packages/video-module/__tests__/plugin.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @description video menu test - * @author luochao - */ - -import withVideo from '../src/module/plugin' -import createEditor from '../../../tests/utils/create-editor' - -describe('videoModule module', () => { - describe('module plugin', () => { - test('withVideo should override editor "isVoid" and "normalizeNode" methods', () => { - const editor = createEditor() - const originalIsVoidFn = editor.isVoid - const originalNormalizeNode = editor.normalizeNode - - const newEditor = withVideo(editor) - - expect(originalIsVoidFn).not.toEqual(newEditor.isVoid) - expect(originalNormalizeNode).not.toEqual(newEditor.normalizeNode) - }) - - test('使用 withVideo 插件后,Editor 会将 Video 元素视为 void 元素', () => { - const editor = createEditor() - const newEditor = withVideo(editor) - const videoElem = { - type: 'video', - src: 'test.mp4', - children: [], - } - expect(newEditor.isVoid(videoElem)).toBeTruthy() - }) - - test('使用 withVideo 插件后,对于非 video 元素,直接调用 original isVoid 方法', () => { - const editor = createEditor() - const fn = jest.fn() - editor.isVoid = fn - - const newEditor = withVideo(editor) - const videoElem = { - type: 'paragraph', - children: [{ text: '' }], - } - newEditor.isVoid(videoElem) - - expect(fn).toBeCalled() - }) - - test('使用 withVideo 插件后,Editor 调用 normalizeNode 方法确保 Video 元素后面有 paragraph、block、header 等元素', () => { - const videoElem = { - type: 'video', - src: 'test.mp4', - children: [], - } - const editor = createEditor({ - content: [videoElem], - }) - const newEditor = withVideo(editor) - - newEditor.normalizeNode([videoElem, [0]]) - expect(newEditor.children).toEqual([ - { - type: 'video', - src: 'test.mp4', - children: [{ text: '' }], - }, - { type: 'paragraph', children: [{ text: '' }] }, - ]) - }) - }) -}) diff --git a/packages/video-module/__tests__/render-elem.test.ts b/packages/video-module/__tests__/render-elem.test.ts deleted file mode 100644 index 4e1d190ea..000000000 --- a/packages/video-module/__tests__/render-elem.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @description video render elem test - * @author luochao - */ - -import createEditor from '../../../tests/utils/create-editor' -import { renderVideoConf } from '../src/module/render-elem' - -describe('video module - render elem', () => { - const editor = createEditor() - - it('render video elem', () => { - expect(renderVideoConf.type).toBe('video') - - const elem = { type: 'video', src: 'test.mp4', poster: 'xxx.png', children: [] } - const vnode = renderVideoConf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('div') - }) - - it('render video with iframe', () => { - expect(renderVideoConf.type).toBe('video') - - const elem = { type: 'video', src: '', children: [] } - const vnode = renderVideoConf.renderElem(elem, null, editor) - expect(vnode.sel).toBe('div') - }) -}) diff --git a/packages/video-module/__tests__/util.test.ts b/packages/video-module/__tests__/util.test.ts deleted file mode 100644 index 8ba0f999d..000000000 --- a/packages/video-module/__tests__/util.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @description video menu test - * @author luochao - */ - -import { genRandomStr } from '../src/utils/util' - -describe('videoModule util', () => { - describe('utils util', () => { - test('genRandomStr should generate a random string every time', () => { - const str1 = genRandomStr() - const str2 = genRandomStr() - - expect(str1).not.toBe(str2) - }) - - test('genRandomStr should generate a random string that specify a prefix string', () => { - const str = genRandomStr('wangeditor') - - expect(str.indexOf('wangeditor-')).toEqual(0) - }) - }) -}) diff --git a/packages/video-module/package.json b/packages/video-module/package.json deleted file mode 100644 index 73655ad7b..000000000 --- a/packages/video-module/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@wangeditor/video-module", - "version": "1.1.4", - "description": "wangEditor video module", - "author": "wangfupeng1988 ", - "contributors": [], - "homepage": "https://github.com/wangeditor-team/wangEditor#readme", - "license": "MIT", - "types": "dist/video-module/src/index.d.ts", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "browser": { - "./dist/index.js": "./dist/index.js", - "./dist/index.esm.js": "./dist/index.esm.js" - }, - "directories": { - "lib": "dist", - "test": "__tests__" - }, - "files": [ - "dist" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.com/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wangeditor-team/wangEditor.git" - }, - "scripts": { - "test": "jest", - "test-c": "jest --coverage", - "dev": "cross-env NODE_ENV=development rollup -c rollup.config.js", - "dev-watch": "cross-env NODE_ENV=development rollup -c rollup.config.js -w", - "build": "cross-env NODE_ENV=production rollup -c rollup.config.js", - "dev-size-stats": "cross-env NODE_ENV=development:size_stats rollup -c rollup.config.js", - "size-stats": "cross-env NODE_ENV=production:size_stats rollup -c rollup.config.js" - }, - "bugs": { - "url": "https://github.com/wangeditor-team/wangEditor/issues" - }, - "peerDependencies": { - "@uppy/core": "^2.1.4", - "@uppy/xhr-upload": "^2.0.7", - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } -} diff --git a/packages/video-module/rollup.config.js b/packages/video-module/rollup.config.js deleted file mode 100644 index 38b2b7a10..000000000 --- a/packages/video-module/rollup.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' -import pkg from './package.json' - -const name = 'WangEditorVideoModule' - -const configList = [] - -// esm -const esmConf = createRollupConfig({ - output: { - file: pkg.module, - format: 'esm', - name, - }, -}) -configList.push(esmConf) - -// umd -const umdConf = createRollupConfig({ - output: { - file: pkg.main, - format: 'umd', - name, - }, -}) -configList.push(umdConf) - -export default configList diff --git a/packages/video-module/src/assets/index.less b/packages/video-module/src/assets/index.less deleted file mode 100644 index ada5274ac..000000000 --- a/packages/video-module/src/assets/index.less +++ /dev/null @@ -1,13 +0,0 @@ -@import "../../../vars.less"; - -.w-e-textarea-video-container { - text-align: center; - border: 1px dashed @textarea-border-color; - padding: 10px 0; - margin: 0 auto; - margin-top: 10px; - border-radius: 5px; - background-position: 0px 0px, 10px 10px; - background-size: 20px 20px; - background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee 100%),linear-gradient(45deg, #eee 25%, white 25%, white 75%, #eee 75%, #eee 100%); -} diff --git a/packages/video-module/src/constants/svg.ts b/packages/video-module/src/constants/svg.ts deleted file mode 100644 index b8f0110ac..000000000 --- a/packages/video-module/src/constants/svg.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @description icon svg - * @author wangfupeng - */ - -/** - * 【注意】svg 字符串的长度 ,否则会导致代码体积过大 - * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293 - * 找不到再从 iconfont.com 搜索 - */ - -// 视频 -export const VIDEO_SVG = - '' - -// 上传视频 -export const UPLOAD_VIDEO_SVG = - '' - -// // 垃圾桶(删除) -// export const TRASH_SVG = -// '' diff --git a/packages/video-module/src/index.ts b/packages/video-module/src/index.ts deleted file mode 100644 index 6df002395..000000000 --- a/packages/video-module/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @description video module - * @author wangfupeng - */ - -import './assets/index.less' - -// 配置多语言 -import './locale/index' - -import wangEditorVideoModule from './module/index' -export default wangEditorVideoModule diff --git a/packages/video-module/src/locale/en.ts b/packages/video-module/src/locale/en.ts deleted file mode 100644 index 78220ae37..000000000 --- a/packages/video-module/src/locale/en.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @description i18n en - * @author wangfupeng - */ - -export default { - videoModule: { - delete: 'Delete', - uploadVideo: 'Upload video', - insertVideo: 'Insert video', - videoSrc: 'Video source', - videoSrcPlaceHolder: 'Video file url, or third-party "/>\n
              \n \n
              \n
              ', + // 事件绑定 + events: [{ + selector: '#' + btnId, + type: 'click', + fn: function fn() { + var $text = $('#' + textValId); + var val = $text.val().trim(); + + // 测试用视频地址 + // + + if (val) { + // 插入视频 + _this._insert(val); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // panel end + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入视频 + _insert: function _insert(val) { + var editor = this.editor; + editor.cmd.do('insertHTML', val + '


              '); + } +}; + +/* + menu - img +*/ +// 构造函数 +function Image(editor) { + this.editor = editor; + this.$elem = $('
              '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Image.prototype = { + constructor: Image, + + onClick: function onClick() { + if (this._active) { + this._createEditPanel(); + } else { + this._createInsertPanel(); + } + }, + + _createEditPanel: function _createEditPanel() { + var editor = this.editor; + + // id + var width30 = getRandom('width-30'); + var width50 = getRandom('width-50'); + var width100 = getRandom('width-100'); + var delBtn = getRandom('del-btn'); + + // tab 配置 + var tabsConfig = [{ + title: '编辑图片', + tpl: '
              \n
              \n \u6700\u5927\u5BBD\u5EA6\uFF1A\n \n \n \n
              \n
              \n \n \n
              ', + events: [{ + selector: '#' + width30, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '30%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width50, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '50%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width100, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '100%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + delBtn, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.remove(); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }]; + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfig + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + _createInsertPanel: function _createInsertPanel() { + var editor = this.editor; + var uploadImg = editor.uploadImg; + var config = editor.config; + + // id + var upTriggerId = getRandom('up-trigger'); + var upFileId = getRandom('up-file'); + var linkUrlId = getRandom('link-url'); + var linkBtnId = getRandom('link-btn'); + + // tabs 的配置 + var tabsConfig = [{ + title: '上传图片', + tpl: '
              \n
              \n \n
              \n
              \n \n
              \n
              ', + events: [{ + // 触发选择图片 + selector: '#' + upTriggerId, + type: 'click', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (fileElem) { + fileElem.click(); + } else { + // 返回 true 可关闭 panel + return true; + } + } + }, { + // 选择图片完毕 + selector: '#' + upFileId, + type: 'change', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (!fileElem) { + // 返回 true 可关闭 panel + return true; + } + + // 获取选中的 file 对象列表 + var fileList = fileElem.files; + if (fileList.length) { + uploadImg.uploadImg(fileList); + } + + // 返回 true 可关闭 panel + return true; + } + }] + }, // first tab end + { + title: '网络图片', + tpl: '
              \n \n
              \n \n
              \n
              ', + events: [{ + selector: '#' + linkBtnId, + type: 'click', + fn: function fn() { + var $linkUrl = $('#' + linkUrlId); + var url = $linkUrl.val().trim(); + + if (url) { + uploadImg.insertLinkImg(url); + } + + // 返回 true 表示函数执行结束之后关闭 panel + return true; + } + }] + } // second tab end + ]; // tabs end + + // 判断 tabs 的显示 + var tabsConfigResult = []; + if ((config.uploadImgShowBase64 || config.uploadImgServer) && window.FileReader) { + // 显示“上传图片” + tabsConfigResult.push(tabsConfig[0]); + } + if (config.showLinkImg) { + // 显示“网络图片” + tabsConfigResult.push(tabsConfig[1]); + } + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfigResult + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor._selectedImg) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + 所有菜单的汇总 +*/ + +// 存储菜单的构造函数 +var MenuConstructors = {}; + +MenuConstructors.bold = Bold; + +MenuConstructors.head = Head; + +MenuConstructors.link = Link; + +MenuConstructors.italic = Italic; + +MenuConstructors.redo = Redo; + +MenuConstructors.strikeThrough = StrikeThrough; + +MenuConstructors.underline = Underline; + +MenuConstructors.undo = Undo; + +MenuConstructors.list = List; + +MenuConstructors.justify = Justify; + +MenuConstructors.foreColor = BackColor; + +MenuConstructors.backColor = ForeColor$1; + +MenuConstructors.quote = Quote; + +MenuConstructors.code = Code; + +MenuConstructors.emoticon = Emoticon; + +MenuConstructors.table = Table; + +MenuConstructors.video = Video; + +MenuConstructors.image = Image; + +/* + 菜单集合 +*/ +// 构造函数 +function Menus(editor) { + this.editor = editor; + this.menus = {}; +} + +// 修改原型 +Menus.prototype = { + constructor: Menus, + + // 初始化菜单 + init: function init() { + var _this = this; + + var editor = this.editor; + var config = editor.config || {}; + var configMenus = config.menus || []; // 获取配置中的菜单 + + // 根据配置信息,创建菜单 + configMenus.forEach(function (menuKey) { + var MenuConstructor = MenuConstructors[menuKey]; + if (MenuConstructor && typeof MenuConstructor === 'function') { + // 创建单个菜单 + _this.menus[menuKey] = new MenuConstructor(editor); + } + }); + + // 添加到菜单栏 + this._addToToolbar(); + + // 绑定事件 + this._bindEvent(); + }, + + // 添加到菜单栏 + _addToToolbar: function _addToToolbar() { + var editor = this.editor; + var $toolbarElem = editor.$toolbarElem; + var menus = this.menus; + objForEach(menus, function (key, menu) { + var $elem = menu.$elem; + if ($elem) { + $toolbarElem.append($elem); + } + }); + }, + + // 绑定菜单 click mouseenter 事件 + _bindEvent: function _bindEvent() { + var menus = this.menus; + var editor = this.editor; + objForEach(menus, function (key, menu) { + var type = menu.type; + if (!type) { + return; + } + var $elem = menu.$elem; + var droplist = menu.droplist; + var panel = menu.panel; + + // 点击类型,例如 bold + if (type === 'click' && menu.onClick) { + $elem.on('click', function (e) { + if (editor.selection.getRange() == null) { + return; + } + menu.onClick(e); + }); + } + + // 下拉框,例如 head + if (type === 'droplist' && droplist) { + $elem.on('mouseenter', function (e) { + if (editor.selection.getRange() == null) { + return; + } + // 显示 + droplist.showTimeoutId = setTimeout(function () { + droplist.show(); + }, 200); + }).on('mouseleave', function (e) { + // 隐藏 + droplist.hideTimeoutId = setTimeout(function () { + droplist.hide(); + }, 0); + }); + } + + // 弹框类型,例如 link + if (type === 'panel' && menu.onClick) { + $elem.on('click', function (e) { + e.stopPropagation(); + if (editor.selection.getRange() == null) { + return; + } + // 在自定义事件中显示 panel + menu.onClick(e); + }); + } + }); + }, + + // 尝试修改菜单状态 + changeActive: function changeActive() { + var menus = this.menus; + objForEach(menus, function (key, menu) { + if (menu.tryChangeActive) { + setTimeout(function () { + menu.tryChangeActive(); + }, 100); + } + }); + } +}; + +/* + 粘贴信息的处理 +*/ + +// 获取粘贴的纯文本 +function getPasteText(e) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + } + + return replaceHtmlSymbol(pasteText); +} + +// 获取粘贴的html +function getPasteHtml(e) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0, + pasteHtml = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + pasteHtml = clipboardData.getData('text/html'); + } + if (!pasteHtml && pasteText) { + pasteHtml = '

              ' + replaceHtmlSymbol(pasteText) + '

              '; + } + if (!pasteHtml) { + return; + } + + // 过滤word中状态过来的无用字符 + var docSplitHtml = pasteHtml.split(''); + if (docSplitHtml.length === 2) { + pasteHtml = docSplitHtml[0]; + } + + // 过滤无用标签 + pasteHtml = pasteHtml.replace(/<(meta|script|link).+?>/igm, ''); + + // 过滤样式 + pasteHtml = pasteHtml.replace(/\s?(class|style)=('|").+?('|")/igm, ''); + + return pasteHtml; +} + +// 获取粘贴的图片文件 +function getPasteImgs(e) { + var result = []; + var txt = getPasteText(e); + if (txt) { + // 有文字,就忽略图片 + return result; + } + + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData || {}; + var items = clipboardData.items; + if (!items) { + return result; + } + + objForEach(items, function (key, value) { + var type = value.type; + if (/image/i.test(type)) { + result.push(value.getAsFile()); + } + }); + + return result; +} + +/* + 编辑区域 +*/ + +// 构造函数 +function Text(editor) { + this.editor = editor; +} + +// 修改原型 +Text.prototype = { + constructor: Text, + + // 初始化 + init: function init() { + // 绑定事件 + this._bindEvent(); + }, + + // 清空内容 + clear: function clear() { + this.html('


              '); + }, + + // 获取 设置 html + html: function html(val) { + var editor = this.editor; + var $textElem = editor.$textElem; + if (val == null) { + return $textElem.html(); + } else { + $textElem.html(val); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + } + }, + + // 获取 设置 text + text: function text(val) { + var editor = this.editor; + var $textElem = editor.$textElem; + if (val == null) { + return $textElem.text(); + } else { + $textElem.text('

              ' + val + '

              '); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + } + }, + + // 追加内容 + append: function append(html) { + var editor = this.editor; + var $textElem = editor.$textElem; + $textElem.append($(html)); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + }, + + // 绑定事件 + _bindEvent: function _bindEvent() { + // 实时保存选取 + this._saveRangeRealTime(); + + // 按回车建时的特殊处理 + this._enterKeyHandle(); + + // 清空时保留


              + this._clearHandle(); + + // 粘贴事件(粘贴文字,粘贴图片) + this._pasteHandle(); + + // tab 特殊处理 + this._tabHandle(); + + // img 点击 + this._imgHandle(); + }, + + // 实时保存选取 + _saveRangeRealTime: function _saveRangeRealTime() { + var editor = this.editor; + var $textElem = editor.$textElem; + + // 保存当前的选区 + function saveRange(e) { + // 随时保存选区 + editor.selection.saveRange(); + // 更新按钮 ative 状态 + editor.menus.changeActive(); + } + // 按键后保存 + $textElem.on('keyup', saveRange); + $textElem.on('mousedown', function (e) { + // mousedown 状态下,鼠标滑动到编辑区域外面,也需要保存选区 + $textElem.on('mouseleave', saveRange); + }); + $textElem.on('mouseup', function (e) { + saveRange(); + // 在编辑器区域之内完成点击,取消鼠标滑动到编辑区外面的事件 + $textElem.off('mouseleave', saveRange); + }); + }, + + // 按回车键时的特殊处理 + _enterKeyHandle: function _enterKeyHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + // 将回车之后生成的非

              的顶级标签,改为

              + function pHandle(e) { + var $selectionElem = editor.selection.getSelectionContainerElem(); + var $parentElem = $selectionElem.parent(); + if (!$parentElem.equal($textElem)) { + // 不是顶级标签 + return; + } + var nodeName = $selectionElem.getNodeName(); + if (nodeName === 'P') { + // 当前的标签是 P ,不用做处理 + return; + } + + if ($selectionElem.text()) { + // 有内容,不做处理 + return; + } + + // 插入

              ,并将选取定位到

              ,删除当前标签 + var $p = $('


              '); + $p.insertBefore($selectionElem); + editor.selection.createRangeByElem($p, true); + editor.selection.restoreSelection(); + $selectionElem.remove(); + } + + $textElem.on('keyup', function (e) { + if (e.keyCode !== 13) { + // 不是回车键 + return; + } + // 将回车之后生成的非

              的顶级标签,改为

              + pHandle(e); + }); + + //

              回车时 特殊处理 + function codeHandle(e) { + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var $parentElem = $selectionElem.parent(); + var selectionNodeName = $selectionElem.getNodeName(); + var parentNodeName = $parentElem.getNodeName(); + + if (selectionNodeName !== 'CODE' || parentNodeName !== 'PRE') { + // 不符合要求 忽略 + return; + } + + if (!editor.cmd.queryCommandSupported('insertHTML')) { + // 必须原生支持 insertHTML 命令 + return; + } + + var _startOffset = editor.selection.getRange().startOffset; + editor.cmd.do('insertHTML', '\n'); + editor.selection.saveRange(); + if (editor.selection.getRange().startOffset === _startOffset) { + // 没起作用,再来一遍 + editor.cmd.do('insertHTML', '\n'); + } + + // 阻止默认行为 + e.preventDefault(); + } + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 13) { + // 不是回车键 + return; + } + //
              回车时 特殊处理 + codeHandle(e); + }); + }, + + // 清空时保留


              + _clearHandle: function _clearHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 8) { + return; + } + var txtHtml = $textElem.html().toLowerCase().trim(); + if (txtHtml === '


              ') { + // 最后剩下一个空行,就不再删除了 + e.preventDefault(); + return; + } + }); + + $textElem.on('keyup', function (e) { + if (e.keyCode !== 8) { + return; + } + var $p = void 0; + var txtHtml = $textElem.html().toLowerCase().trim(); + + // firefox 时用 txtHtml === '
              ' 判断,其他用 !txtHtml 判断 + if (!txtHtml || txtHtml === '
              ') { + // 内容空了 + $p = $('


              '); + $textElem.html(''); // 一定要先清空,否则在 firefox 下有问题 + $textElem.append($p); + editor.selection.createRangeByElem($p, false, true); + editor.selection.restoreSelection(); + } + }); + }, + + // 粘贴事件(粘贴文字 粘贴图片) + _pasteHandle: function _pasteHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + // 粘贴文字 + $textElem.on('paste', function (e) { + if (UA.isIE()) { + // IE 下放弃下面的判断 + return; + } + + // 阻止默认行为,使用 execCommand 的粘贴命令 + e.preventDefault(); + + // 获取粘贴的文字 + var pasteHtml = getPasteHtml(e); + var pasteText = getPasteText(e); + pasteText = pasteText.replace(/\n/gm, '
              '); + + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var nodeName = $selectionElem.getNodeName(); + + // code 中粘贴忽略 + if (nodeName === 'CODE' || nodeName === 'PRE') { + return; + } + + // 表格中忽略,可能会出现异常问题 + if (nodeName === 'TD' || nodeName === 'TH') { + return; + } + + if (nodeName === 'DIV' || $textElem.html() === '


              ') { + // 是 div,可粘贴过滤样式的文字和链接 + if (!pasteHtml) { + return; + } + try { + // firefox 中,获取的 pasteHtml 可能是没有
                包裹的
              • + // 因此执行 insertHTML 会报错 + editor.cmd.do('insertHTML', pasteHtml); + } catch (ex) { + // 此时使用 pasteText 来兼容一下 + editor.cmd.do('insertHTML', '

                ' + pasteText + '

                '); + } + } else { + // 不是 div,证明在已有内容的元素中粘贴,只粘贴纯文本 + if (!pasteText) { + return; + } + editor.cmd.do('insertHTML', '

                ' + pasteText + '

                '); + } + }); + + // 粘贴图片 + $textElem.on('paste', function (e) { + e.preventDefault(); + + // 获取粘贴的图片 + var pasteFiles = getPasteImgs(e); + if (!pasteFiles || !pasteFiles.length) { + return; + } + + // 获取当前的元素 + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var nodeName = $selectionElem.getNodeName(); + + // code 中粘贴忽略 + if (nodeName === 'CODE' || nodeName === 'PRE') { + return; + } + + // 上传图片 + var uploadImg = editor.uploadImg; + uploadImg.uploadImg(pasteFiles); + }); + }, + + // tab 特殊处理 + _tabHandle: function _tabHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 9) { + return; + } + if (!editor.cmd.queryCommandSupported('insertHTML')) { + // 必须原生支持 insertHTML 命令 + return; + } + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var $parentElem = $selectionElem.parent(); + var selectionNodeName = $selectionElem.getNodeName(); + var parentNodeName = $parentElem.getNodeName(); + + if (selectionNodeName === 'CODE' && parentNodeName === 'PRE') { + //
                 里面
                +                editor.cmd.do('insertHTML', '    ');
                +            } else {
                +                // 普通文字
                +                editor.cmd.do('insertHTML', '    ');
                +            }
                +
                +            e.preventDefault();
                +        });
                +    },
                +
                +    // img 点击
                +    _imgHandle: function _imgHandle() {
                +        var editor = this.editor;
                +        var $textElem = editor.$textElem;
                +        var selectedClass = 'w-e-selected';
                +
                +        // 为图片增加 selected 样式
                +        $textElem.on('click', 'img', function (e) {
                +            var img = this;
                +            var $img = $(img);
                +
                +            // 去掉所有图片的 selected 样式
                +            $textElem.find('img').removeClass(selectedClass);
                +
                +            // 为点击的图片增加样式,并记录当前图片
                +            $img.addClass(selectedClass);
                +            editor._selectedImg = $img;
                +
                +            // 修改选取
                +            editor.selection.createRangeByElem($img);
                +        });
                +
                +        // 去掉图片的 selected 样式
                +        $textElem.on('click  keyup', function (e) {
                +            if (e.target.matches('img')) {
                +                // 点击的是图片,忽略
                +                return;
                +            }
                +            // 取消掉 selected 样式,并删除记录
                +            $textElem.find('img').removeClass(selectedClass);
                +            editor._selectedImg = null;
                +        });
                +    }
                +};
                +
                +/*
                +    命令,封装 document.execCommand
                +*/
                +
                +// 构造函数
                +function Command(editor) {
                +    this.editor = editor;
                +}
                +
                +// 修改原型
                +Command.prototype = {
                +    constructor: Command,
                +
                +    // 执行命令
                +    do: function _do(name, value) {
                +        var editor = this.editor;
                +
                +        // 如果无选区,忽略
                +        if (!editor.selection.getRange()) {
                +            return;
                +        }
                +
                +        // 恢复选取
                +        editor.selection.restoreSelection();
                +
                +        // 执行
                +        var _name = '_' + name;
                +        if (this[_name]) {
                +            // 有自定义事件
                +            this[_name](value);
                +        } else {
                +            // 默认 command
                +            this._execCommand(name, value);
                +        }
                +
                +        // 修改菜单状态
                +        editor.menus.changeActive();
                +
                +        // 最后,恢复选取保证光标在原来的位置闪烁
                +        editor.selection.saveRange();
                +        editor.selection.restoreSelection();
                +
                +        // 触发 onchange
                +        editor.change && editor.change();
                +    },
                +
                +    // 自定义 insertHTML 事件
                +    _insertHTML: function _insertHTML(html) {
                +        var editor = this.editor;
                +        var range = editor.selection.getRange();
                +
                +        // 保证传入的参数是 html 代码
                +        var test = /^<.+>$/.test(html);
                +        if (!test && !UA.isWebkit()) {
                +            // webkit 可以插入非 html 格式的文字
                +            throw new Error('执行 insertHTML 命令时传入的参数必须是 html 格式');
                +        }
                +
                +        if (this.queryCommandSupported('insertHTML')) {
                +            // W3C
                +            this._execCommand('insertHTML', html);
                +        } else if (range.insertNode) {
                +            // IE
                +            range.deleteContents();
                +            range.insertNode($(html)[0]);
                +        } else if (range.pasteHTML) {
                +            // IE <= 10
                +            range.pasteHTML(html);
                +        }
                +    },
                +
                +    // 插入 elem
                +    _insertElem: function _insertElem($elem) {
                +        var editor = this.editor;
                +        var range = editor.selection.getRange();
                +
                +        if (range.insertNode) {
                +            range.deleteContents();
                +            range.insertNode($elem[0]);
                +        }
                +    },
                +
                +    // 封装 execCommand
                +    _execCommand: function _execCommand(name, value) {
                +        document.execCommand(name, false, value);
                +    },
                +
                +    // 封装 document.queryCommandValue
                +    queryCommandValue: function queryCommandValue(name) {
                +        return document.queryCommandValue(name);
                +    },
                +
                +    // 封装 document.queryCommandState
                +    queryCommandState: function queryCommandState(name) {
                +        return document.queryCommandState(name);
                +    },
                +
                +    // 封装 document.queryCommandSupported
                +    queryCommandSupported: function queryCommandSupported(name) {
                +        return document.queryCommandSupported(name);
                +    }
                +};
                +
                +/*
                +    selection range API
                +*/
                +
                +// 构造函数
                +function API(editor) {
                +    this.editor = editor;
                +    this._currentRange = null;
                +}
                +
                +// 修改原型
                +API.prototype = {
                +    constructor: API,
                +
                +    // 获取 range 对象
                +    getRange: function getRange() {
                +        return this._currentRange;
                +    },
                +
                +    // 保存选区
                +    saveRange: function saveRange(_range) {
                +        if (_range) {
                +            // 保存已有选区
                +            this._currentRange = _range;
                +            return;
                +        }
                +
                +        // 获取当前的选区
                +        var selection = window.getSelection();
                +        if (selection.rangeCount === 0) {
                +            return;
                +        }
                +        var range = selection.getRangeAt(0);
                +
                +        // 判断选区内容是否在编辑内容之内
                +        var $containerElem = this.getSelectionContainerElem(range);
                +        if (!$containerElem) {
                +            return;
                +        }
                +        var editor = this.editor;
                +        var $textElem = editor.$textElem;
                +        if ($textElem.isContain($containerElem)) {
                +            // 是编辑内容之内的
                +            this._currentRange = range;
                +        }
                +    },
                +
                +    // 折叠选区
                +    collapseRange: function collapseRange(toStart) {
                +        if (toStart == null) {
                +            // 默认为 false
                +            toStart = false;
                +        }
                +        var range = this._currentRange;
                +        if (range) {
                +            range.collapse(toStart);
                +        }
                +    },
                +
                +    // 选中区域的文字
                +    getSelectionText: function getSelectionText() {
                +        var range = this._currentRange;
                +        if (range) {
                +            return this._currentRange.toString();
                +        } else {
                +            return '';
                +        }
                +    },
                +
                +    // 选区的 $Elem
                +    getSelectionContainerElem: function getSelectionContainerElem(range) {
                +        range = range || this._currentRange;
                +        var elem = void 0;
                +        if (range) {
                +            elem = range.commonAncestorContainer;
                +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
                +        }
                +    },
                +    getSelectionStartElem: function getSelectionStartElem(range) {
                +        range = range || this._currentRange;
                +        var elem = void 0;
                +        if (range) {
                +            elem = range.startContainer;
                +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
                +        }
                +    },
                +    getSelectionEndElem: function getSelectionEndElem(range) {
                +        range = range || this._currentRange;
                +        var elem = void 0;
                +        if (range) {
                +            elem = range.endContainer;
                +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
                +        }
                +    },
                +
                +    // 选区是否为空
                +    isSelectionEmpty: function isSelectionEmpty() {
                +        var range = this._currentRange;
                +        if (range && range.startContainer) {
                +            if (range.startContainer === range.endContainer) {
                +                if (range.startOffset === range.endOffset) {
                +                    return true;
                +                }
                +            }
                +        }
                +        return false;
                +    },
                +
                +    // 恢复选区
                +    restoreSelection: function restoreSelection() {
                +        var selection = window.getSelection();
                +        selection.removeAllRanges();
                +        selection.addRange(this._currentRange);
                +    },
                +
                +    // 创建一个空白(即 ​ 字符)选区
                +    createEmptyRange: function createEmptyRange() {
                +        var editor = this.editor;
                +        var range = this.getRange();
                +        var $elem = void 0;
                +
                +        if (!range) {
                +            // 当前无 range
                +            return;
                +        }
                +        if (!this.isSelectionEmpty()) {
                +            // 当前选区必须没有内容才可以
                +            return;
                +        }
                +
                +        // 目前只支持 webkit 内核
                +        if (UA.isWebkit()) {
                +            // 插入 ​
                +            editor.cmd.do('insertHTML', '​');
                +            // 修改 offset 位置
                +            range.setEnd(range.endContainer, range.endOffset + 1);
                +            // 存储
                +            this.saveRange(range);
                +        } else {
                +            $elem = $('');
                +            editor.cmd.do('insertElem', $elem);
                +            this.createRangeByElem($elem, true);
                +        }
                +    },
                +
                +    // 根据 $Elem 设置选区
                +    createRangeByElem: function createRangeByElem($elem, toStart, isContent) {
                +        // $elem - 经过封装的 elem
                +        // toStart - true 开始位置,false 结束位置
                +        // isContent - 是否选中Elem的内容
                +        if (!$elem.length) {
                +            return;
                +        }
                +
                +        var elem = $elem[0];
                +        var range = document.createRange();
                +
                +        if (isContent) {
                +            range.selectNodeContents(elem);
                +        } else {
                +            range.selectNode(elem);
                +        }
                +
                +        if (typeof toStart === 'boolean') {
                +            range.collapse(toStart);
                +        }
                +
                +        // 存储 range
                +        this.saveRange(range);
                +    }
                +};
                +
                +/*
                +    上传进度条
                +*/
                +
                +function Progress(editor) {
                +    this.editor = editor;
                +    this._time = 0;
                +    this._isShow = false;
                +    this._isRender = false;
                +    this._timeoutId = 0;
                +    this.$textContainer = editor.$textContainerElem;
                +    this.$bar = $('
                '); +} + +Progress.prototype = { + constructor: Progress, + + show: function show(progress) { + var _this = this; + + // 状态处理 + if (this._isShow) { + return; + } + this._isShow = true; + + // 渲染 + var $bar = this.$bar; + if (!this._isRender) { + var $textContainer = this.$textContainer; + $textContainer.append($bar); + } else { + this._isRender = true; + } + + // 改变进度(节流,100ms 渲染一次) + if (Date.now() - this._time > 100) { + if (progress <= 1) { + $bar.css('width', progress * 100 + '%'); + this._time = Date.now(); + } + } + + // 隐藏 + var timeoutId = this._timeoutId; + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(function () { + _this._hide(); + }, 500); + }, + + _hide: function _hide() { + var $bar = this.$bar; + $bar.remove(); + + // 修改状态 + this._time = 0; + this._isShow = false; + this._isRender = false; + } +}; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + +/* + 上传图片 +*/ + +// 构造函数 +function UploadImg(editor) { + this.editor = editor; +} + +// 原型 +UploadImg.prototype = { + constructor: UploadImg, + + // 根据 debug 弹出不同的信息 + _alert: function _alert(alertInfo, debugInfo) { + var editor = this.editor; + var debug = editor.config.debug; + + if (debug) { + throw new Error('wangEditor: ' + (debugInfo || alertInfo)); + } else { + alert(alertInfo); + } + }, + + // 根据链接插入图片 + insertLinkImg: function insertLinkImg(link) { + var _this2 = this; + + if (!link) { + return; + } + var editor = this.editor; + + var img = document.createElement('img'); + img.onload = function () { + img = null; + editor.cmd.do('insertHTML', ''); + }; + img.onerror = function () { + img = null; + // 无法成功下载图片 + _this2._alert('插入图片错误', 'wangEditor: \u63D2\u5165\u56FE\u7247\u51FA\u9519\uFF0C\u56FE\u7247\u94FE\u63A5\u662F "' + link + '"\uFF0C\u4E0B\u8F7D\u8BE5\u94FE\u63A5\u5931\u8D25'); + return; + }; + img.onabort = function () { + img = null; + }; + img.src = link; + }, + + // 上传图片 + uploadImg: function uploadImg(files) { + var _this3 = this; + + if (!files || !files.length) { + return; + } + + // ------------------------------ 获取配置信息 ------------------------------ + var editor = this.editor; + var config = editor.config; + var maxSize = config.uploadImgMaxSize; + var maxSizeM = maxSize / 1000 / 1000; + var maxLength = config.uploadImgMaxLength || 10000; + var uploadImgServer = config.uploadImgServer; + var uploadImgShowBase64 = config.uploadImgShowBase64; + var uploadFileName = config.uploadFileName || ''; + var uploadImgParams = config.uploadImgParams || {}; + var uploadImgHeaders = config.uploadImgHeaders || {}; + var hooks = config.uploadImgHooks || {}; + var timeout = config.uploadImgTimeout || 3000; + var withCredentials = config.withCredentials; + if (withCredentials == null) { + withCredentials = false; + } + + // ------------------------------ 验证文件信息 ------------------------------ + var resultFiles = []; + var errInfo = []; + arrForEach(files, function (file) { + var name = file.name; + var size = file.size; + if (/\.(jpg|jpeg|png|bmp|gif)$/i.test(name) === false) { + // 后缀名不合法,不是图片 + errInfo.push('\u3010' + name + '\u3011\u4E0D\u662F\u56FE\u7247'); + return; + } + if (maxSize < size) { + // 上传图片过大 + errInfo.push('\u3010' + name + '\u3011\u5927\u4E8E ' + maxSizeM + 'M'); + return; + } + + // 验证通过的加入结果列表 + resultFiles.push(file); + }); + // 抛出验证信息 + if (errInfo.length) { + this._alert('图片验证未通过: \n' + errInfo.join('\n')); + return; + } + if (resultFiles.length > maxLength) { + this._alert('一次最多上传' + maxLength + '张图片'); + return; + } + + // 添加图片数据 + var formdata = new FormData(); + arrForEach(resultFiles, function (file) { + var name = uploadFileName || file.name; + formdata.append(name, file); + }); + + // ------------------------------ 上传图片 ------------------------------ + if (uploadImgServer && typeof uploadImgServer === 'string') { + // 添加参数 + var uploadImgServerArr = uploadImgServer.split('#'); + uploadImgServer = uploadImgServerArr[0]; + var uploadImgServerHash = uploadImgServerArr[1] || ''; + objForEach(uploadImgParams, function (key, val) { + val = encodeURIComponent(val); + + // 第一,将参数拼接到 url 中 + if (uploadImgServer.indexOf('?') > 0) { + uploadImgServer += '&'; + } else { + uploadImgServer += '?'; + } + uploadImgServer = uploadImgServer + key + '=' + val; + + // 第二,将参数添加到 formdata 中 + formdata.append(key, val); + }); + if (uploadImgServerHash) { + uploadImgServer += '#' + uploadImgServerHash; + } + + // 定义 xhr + var xhr = new XMLHttpRequest(); + xhr.open('POST', uploadImgServer); + + // 设置超时 + xhr.timeout = timeout; + xhr.ontimeout = function () { + // hook - timeout + if (hooks.timeout && typeof hooks.timeout === 'function') { + hooks.timeout(xhr, editor); + } + + _this3._alert('上传图片超时'); + }; + + // 监控 progress + if (xhr.upload) { + xhr.upload.onprogress = function (e) { + var percent = void 0; + // 进度条 + var progressBar = new Progress(editor); + if (e.lengthComputable) { + percent = e.loaded / e.total; + progressBar.show(percent); + } + }; + } + + // 返回数据 + xhr.onreadystatechange = function () { + var result = void 0; + if (xhr.readyState === 4) { + if (xhr.status < 200 || xhr.status >= 300) { + // hook - error + if (hooks.error && typeof hooks.error === 'function') { + hooks.error(xhr, editor); + } + + // xhr 返回状态错误 + _this3._alert('上传图片发生错误', '\u4E0A\u4F20\u56FE\u7247\u53D1\u751F\u9519\u8BEF\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u72B6\u6001\u662F ' + xhr.status); + return; + } + + result = xhr.responseText; + if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) !== 'object') { + try { + result = JSON.parse(result); + } catch (ex) { + // hook - fail + if (hooks.fail && typeof hooks.fail === 'function') { + hooks.fail(xhr, editor, result); + } + + _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果是: ' + result); + return; + } + } + if (!hooks.customInsert && result.errno != '0') { + // hook - fail + if (hooks.fail && typeof hooks.fail === 'function') { + hooks.fail(xhr, editor, result); + } + + // 数据错误 + _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果 errno=' + result.errno); + } else { + if (hooks.customInsert && typeof hooks.customInsert === 'function') { + // 使用者自定义插入方法 + hooks.customInsert(_this3.insertLinkImg.bind(_this3), result, editor); + } else { + // 将图片插入编辑器 + var data = result.data || []; + data.forEach(function (link) { + _this3.insertLinkImg(link); + }); + } + + // hook - success + if (hooks.success && typeof hooks.success === 'function') { + hooks.success(xhr, editor, result); + } + } + } + }; + + // hook - before + if (hooks.before && typeof hooks.before === 'function') { + hooks.before(xhr, editor, resultFiles); + } + + // 自定义 headers + objForEach(uploadImgHeaders, function (key, val) { + xhr.setRequestHeader(key, val); + }); + + // 跨域传 cookie + xhr.withCredentials = withCredentials; + + // 发送请求 + xhr.send(formdata); + + // 注意,要 return 。不去操作接下来的 base64 显示方式 + return; + } + + // 显示 base64 格式 + if (uploadImgShowBase64) { + arrForEach(files, function (file) { + var _this = _this3; + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function () { + _this.insertLinkImg(this.result); + }; + }); + } + } +}; + +/* + 编辑器构造函数 +*/ + +// id,累加 +var editorId = 1; + +// 构造函数 +function Editor(toolbarSelector, textSelector) { + if (toolbarSelector == null) { + // 没有传入任何参数,报错 + throw new Error('错误:初始化编辑器时候未传入任何参数,请查阅文档'); + } + // id,用以区分单个页面不同的编辑器对象 + this.id = 'wangEditor-' + editorId++; + + this.toolbarSelector = toolbarSelector; + this.textSelector = textSelector; + + // 自定义配置 + this.customConfig = {}; +} + +// 修改原型 +Editor.prototype = { + constructor: Editor, + + // 初始化配置 + _initConfig: function _initConfig() { + // _config 是默认配置,this.customConfig 是用户自定义配置,将它们 merge 之后再赋值 + var target = {}; + this.config = Object.assign(target, config, this.customConfig); + }, + + // 初始化 DOM + _initDom: function _initDom() { + var _this = this; + + var toolbarSelector = this.toolbarSelector; + var $toolbarSelector = $(toolbarSelector); + var textSelector = this.textSelector; + + var config$$1 = this.config; + var zIndex = config$$1.zIndex || '10000'; + + // 定义变量 + var $toolbarElem = void 0, + $textContainerElem = void 0, + $textElem = void 0, + $children = void 0; + + if (textSelector == null) { + // 只传入一个参数,即是容器的选择器或元素,toolbar 和 text 的元素自行创建 + $toolbarElem = $('
                '); + $textContainerElem = $('
                '); + + // 将编辑器区域原有的内容,暂存起来 + $children = $toolbarSelector.children(); + + // 添加到 DOM 结构中 + $toolbarSelector.append($toolbarElem).append($textContainerElem); + + // 自行创建的,需要配置默认的样式 + $toolbarElem.css('background-color', '#f1f1f1').css('border', '1px solid #ccc'); + $textContainerElem.css('border', '1px solid #ccc').css('border-top', 'none').css('height', '300px'); + } else { + // toolbar 和 text 的选择器都有值,记录属性 + $toolbarElem = $toolbarSelector; + $textContainerElem = $(textSelector); + // 将编辑器区域原有的内容,暂存起来 + $children = $textContainerElem.children(); + } + + // 编辑区域 + $textElem = $('
                '); + $textElem.attr('contenteditable', 'true').css('width', '100%').css('height', '100%'); + + // 初始化编辑区域内容 + if ($children && $children.length) { + $textElem.append($children); + } else { + $textElem.append($('


                ')); + } + + // 编辑区域加入DOM + $textContainerElem.append($textElem); + + // 设置通用的 class + $toolbarElem.addClass('w-e-toolbar'); + $textContainerElem.addClass('w-e-text-container'); + $textContainerElem.css('z-index', zIndex); + $textElem.addClass('w-e-text'); + + // 记录属性 + this.$toolbarElem = $toolbarElem; + this.$textContainerElem = $textContainerElem; + this.$textElem = $textElem; + + // 绑定 onchange + $textContainerElem.on('click keyup', function () { + _this.change && _this.change(); + }); + $toolbarElem.on('click', function () { + this.change && this.change(); + }); + }, + + // 封装 command + _initCommand: function _initCommand() { + this.cmd = new Command(this); + }, + + // 封装 selection range API + _initSelectionAPI: function _initSelectionAPI() { + this.selection = new API(this); + }, + + // 添加图片上传 + _initUploadImg: function _initUploadImg() { + this.uploadImg = new UploadImg(this); + }, + + // 初始化菜单 + _initMenus: function _initMenus() { + this.menus = new Menus(this); + this.menus.init(); + }, + + // 添加 text 区域 + _initText: function _initText() { + this.txt = new Text(this); + this.txt.init(); + }, + + // 初始化选区,将光标定位到内容尾部 + initSelection: function initSelection() { + var $textElem = this.$textElem; + var $children = $textElem.children(); + if (!$children.length) { + // 如果编辑器区域无内容,添加一个空行,重新设置选区 + $textElem.append($('


                ')); + this.initSelection(); + return; + } + + var $last = $children.last(); + var html = $last.html().toLowerCase(); + var nodeName = $last.getNodeName(); + if (html !== '
                ' && html !== '' || nodeName !== 'P') { + // 最后一个元素不是


                ,添加一个空行,重新设置选区 + $textElem.append($('


                ')); + this.initSelection(); + return; + } + + this.selection.createRangeByElem($last, true); + this.selection.restoreSelection(); + }, + + // 绑定事件 + _bindEvent: function _bindEvent() { + // -------- 绑定 onchange 事件 -------- + var onChangeTimeoutId = 0; + var beforeChangeHtml = this.txt.html(); + var config$$1 = this.config; + var onchange = config$$1.onchange; + if (onchange && typeof onchange === 'function') { + // 触发 change 的有三个场景: + // 1. $textContainerElem.on('click keyup') + // 2. $toolbarElem.on('click') + // 3. editor.cmd.do() + this.change = function () { + // 判断是否有变化 + var currentHtml = this.txt.html(); + if (currentHtml.length === beforeChangeHtml.length) { + return; + } + + // 执行,使用节流 + if (onChangeTimeoutId) { + clearTimeout(onChangeTimeoutId); + } + onChangeTimeoutId = setTimeout(function () { + // 触发配置的 onchange 函数 + onchange(currentHtml); + beforeChangeHtml = currentHtml; + }, 200); + }; + } + }, + + // 创建编辑器 + create: function create() { + // 初始化配置信息 + this._initConfig(); + + // 初始化 DOM + this._initDom(); + + // 封装 command API + this._initCommand(); + + // 封装 selection range API + this._initSelectionAPI(); + + // 添加 text + this._initText(); + + // 初始化菜单 + this._initMenus(); + + // 添加 图片上传 + this._initUploadImg(); + + // 初始化选区,将光标定位到内容尾部 + this.initSelection(); + + // 绑定事件 + this._bindEvent(); + } +}; + +// 检验是否浏览器环境 +try { + document; +} catch (ex) { + throw new Error('请在浏览器环境下运行'); +} + +// polyfill +polyfill(); + +// 这里的 `inlinecss` 将被替换成 css 代码的内容,详情可去 ./gulpfile.js 中搜索 `inlinecss` 关键字 +var inlinecss = '.w-e-toolbar,.w-e-text-container,.w-e-menu-panel { padding: 0; margin: 0; box-sizing: border-box;}.w-e-toolbar *,.w-e-text-container *,.w-e-menu-panel * { padding: 0; margin: 0; box-sizing: border-box;}.w-e-clear-fix:after { content: ""; display: table; clear: both;}.w-e-toolbar .w-e-droplist { position: absolute; left: 0; top: 0; background-color: #fff; border: 1px solid #f1f1f1; border-right-color: #ccc; border-bottom-color: #ccc;}.w-e-toolbar .w-e-droplist .w-e-dp-title { text-align: center; color: #999; line-height: 2; border-bottom: 1px solid #f1f1f1; font-size: 13px;}.w-e-toolbar .w-e-droplist ul.w-e-list { list-style: none; line-height: 1;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item { color: #333; padding: 5px 0;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item:hover { background-color: #f1f1f1;}.w-e-toolbar .w-e-droplist ul.w-e-block { list-style: none; text-align: left; padding: 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item { display: inline-block; *display: inline; *zoom: 1; padding: 3px 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item:hover { background-color: #f1f1f1;}@font-face { font-family: \'icomoon\'; src: url(data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAABXAAAsAAAAAFXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxIPAmNtYXAAAAFoAAAA9AAAAPRAxxN6Z2FzcAAAAlwAAAAIAAAACAAAABBnbHlmAAACZAAAEHwAABB8kRGt5WhlYWQAABLgAAAANgAAADYN4rlyaGhlYQAAExgAAAAkAAAAJAfEA99obXR4AAATPAAAAHwAAAB8cAcDvGxvY2EAABO4AAAAQAAAAEAx8jYEbWF4cAAAE/gAAAAgAAAAIAAqALZuYW1lAAAUGAAAAYYAAAGGmUoJ+3Bvc3QAABWgAAAAIAAAACAAAwAAAAMD3AGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA8fwDwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEANgAAAAyACAABAASAAEAIOkG6Q3pEulH6Wbpd+m56bvpxunL6d/qDepl6mjqcep58A3wFPEg8dzx/P/9//8AAAAAACDpBukN6RLpR+ll6Xfpuem76cbpy+nf6g3qYupo6nHqd/AN8BTxIPHc8fz//f//AAH/4xb+FvgW9BbAFqMWkxZSFlEWRxZDFjAWAxWvFa0VpRWgEA0QBw78DkEOIgADAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAIAAP/ABAADwAAEABMAAAE3AScBAy4BJxM3ASMBAyUBNQEHAYCAAcBA/kCfFzsyY4ABgMD+gMACgAGA/oBOAUBAAcBA/kD+nTI7FwERTgGA/oD9gMABgMD+gIAABAAAAAAEAAOAABAAIQAtADQAAAE4ATEROAExITgBMRE4ATEhNSEiBhURFBYzITI2NRE0JiMHFAYjIiY1NDYzMhYTITUTATM3A8D8gAOA/IAaJiYaA4AaJiYagDgoKDg4KCg4QP0A4AEAQOADQP0AAwBAJhr9ABomJhoDABom4Cg4OCgoODj9uIABgP7AwAAAAgAAAEAEAANAACgALAAAAS4DIyIOAgcOAxUUHgIXHgMzMj4CNz4DNTQuAicBEQ0BA9U2cXZ5Pz95dnE2Cw8LBgYLDws2cXZ5Pz95dnE2Cw8LBgYLDwv9qwFA/sADIAgMCAQECAwIKVRZWy8vW1lUKQgMCAQECAwIKVRZWy8vW1lUKf3gAYDAwAAAAAACAMD/wANAA8AAEwAfAAABIg4CFRQeAjEwPgI1NC4CAyImNTQ2MzIWFRQGAgBCdVcyZHhkZHhkMld1QlBwcFBQcHADwDJXdUJ4+syCgsz6eEJ1VzL+AHBQUHBwUFBwAAABAAAAAAQAA4AAIQAAASIOAgcnESEnPgEzMh4CFRQOAgcXPgM1NC4CIwIANWRcUiOWAYCQNYtQUItpPBIiMB5VKEAtGFCLu2oDgBUnNyOW/oCQNDw8aYtQK1FJQRpgI1ZibDlqu4tQAAEAAAAABAADgAAgAAATFB4CFzcuAzU0PgIzMhYXByERBy4DIyIOAgAYLUAoVR4wIhI8aYtQUIs1kAGAliNSXGQ1aruLUAGAOWxiViNgGkFJUStQi2k8PDSQAYCWIzcnFVCLuwACAAAAQAQBAwAAHgA9AAATMh4CFRQOAiMiLgI1JzQ+AjMVIgYHDgEHPgEhMh4CFRQOAiMiLgI1JzQ+AjMVIgYHDgEHPgHhLlI9IyM9Ui4uUj0jAUZ6o11AdS0JEAcIEgJJLlI9IyM9Ui4uUj0jAUZ6o11AdS0JEAcIEgIAIz1SLi5SPSMjPVIuIF2jekaAMC4IEwoCASM9Ui4uUj0jIz1SLiBdo3pGgDAuCBMKAgEAAAYAQP/ABAADwAADAAcACwARAB0AKQAAJSEVIREhFSERIRUhJxEjNSM1ExUzFSM1NzUjNTMVFREjNTM1IzUzNSM1AYACgP2AAoD9gAKA/YDAQEBAgMCAgMDAgICAgICAAgCAAgCAwP8AwED98jJAkjwyQJLu/sBAQEBAQAAGAAD/wAQAA8AAAwAHAAsAFwAjAC8AAAEhFSERIRUhESEVIQE0NjMyFhUUBiMiJhE0NjMyFhUUBiMiJhE0NjMyFhUUBiMiJgGAAoD9gAKA/YACgP2A/oBLNTVLSzU1S0s1NUtLNTVLSzU1S0s1NUsDgID/AID/AIADQDVLSzU1S0v+tTVLSzU1S0v+tTVLSzU1S0sAAwAAAAAEAAOgAAMADQAUAAA3IRUhJRUhNRMhFSE1ISUJASMRIxEABAD8AAQA/ACAAQABAAEA/WABIAEg4IBAQMBAQAEAgIDAASD+4P8AAQAAAAAAAgBT/8wDrQO0AC8AXAAAASImJy4BNDY/AT4BMzIWFx4BFAYPAQYiJyY0PwE2NCcuASMiBg8BBhQXFhQHDgEjAyImJy4BNDY/ATYyFxYUDwEGFBceATMyNj8BNjQnJjQ3NjIXHgEUBg8BDgEjAbgKEwgjJCQjwCNZMTFZIyMkJCNYDywPDw9YKSkUMxwcMxTAKSkPDwgTCrgxWSMjJCQjWA8sDw8PWCkpFDMcHDMUwCkpDw8PKxAjJCQjwCNZMQFECAckWl5aJMAiJSUiJFpeWiRXEBAPKw9YKXQpFBUVFMApdCkPKxAHCP6IJSIkWl5aJFcQEA8rD1gpdCkUFRUUwCl0KQ8rEA8PJFpeWiTAIiUAAAAABQAA/8AEAAPAABMAJwA7AEcAUwAABTI+AjU0LgIjIg4CFRQeAhMyHgIVFA4CIyIuAjU0PgITMj4CNw4DIyIuAiceAyc0NjMyFhUUBiMiJiU0NjMyFhUUBiMiJgIAaruLUFCLu2pqu4tQUIu7alaYcUFBcZhWVphxQUFxmFYrVVFMIwU3Vm8/P29WNwUjTFFV1SUbGyUlGxslAYAlGxslJRsbJUBQi7tqaruLUFCLu2pqu4tQA6BBcZhWVphxQUFxmFZWmHFB/gkMFSAUQ3RWMTFWdEMUIBUM9yg4OCgoODgoKDg4KCg4OAAAAAADAAD/wAQAA8AAEwAnADMAAAEiDgIVFB4CMzI+AjU0LgIDIi4CNTQ+AjMyHgIVFA4CEwcnBxcHFzcXNyc3AgBqu4tQUIu7amq7i1BQi7tqVphxQUFxmFZWmHFBQXGYSqCgYKCgYKCgYKCgA8BQi7tqaruLUFCLu2pqu4tQ/GBBcZhWVphxQUFxmFZWmHFBAqCgoGCgoGCgoGCgoAADAMAAAANAA4AAEgAbACQAAAE+ATU0LgIjIREhMj4CNTQmATMyFhUUBisBEyMRMzIWFRQGAsQcIChGXTX+wAGANV1GKET+hGUqPDwpZp+fnyw+PgHbIlQvNV1GKPyAKEZdNUZ0AUZLNTVL/oABAEs1NUsAAAIAwAAAA0ADgAAbAB8AAAEzERQOAiMiLgI1ETMRFBYXHgEzMjY3PgE1ASEVIQLAgDJXdUJCdVcygBsYHEkoKEkcGBv+AAKA/YADgP5gPGlOLS1OaTwBoP5gHjgXGBsbGBc4Hv6ggAAAAQCAAAADgAOAAAsAAAEVIwEzFSE1MwEjNQOAgP7AgP5AgAFAgAOAQP0AQEADAEAAAQAAAAAEAAOAAD0AAAEVIx4BFRQGBw4BIyImJy4BNTMUFjMyNjU0JiMhNSEuAScuATU0Njc+ATMyFhceARUjNCYjIgYVFBYzMhYXBADrFRY1MCxxPj5xLDA1gHJOTnJyTv4AASwCBAEwNTUwLHE+PnEsMDWAck5OcnJOO24rAcBAHUEiNWIkISQkISRiNTRMTDQ0TEABAwEkYjU1YiQhJCQhJGI1NExMNDRMIR8AAAAHAAD/wAQAA8AAAwAHAAsADwATABsAIwAAEzMVIzczFSMlMxUjNzMVIyUzFSMDEyETMxMhEwEDIQMjAyEDAICAwMDAAQCAgMDAwAEAgIAQEP0AECAQAoAQ/UAQAwAQIBD9gBABwEBAQEBAQEBAQAJA/kABwP6AAYD8AAGA/oABQP7AAAAKAAAAAAQAA4AAAwAHAAsADwATABcAGwAfACMAJwAAExEhEQE1IRUdASE1ARUhNSMVITURIRUhJSEVIRE1IRUBIRUhITUhFQAEAP2AAQD/AAEA/wBA/wABAP8AAoABAP8AAQD8gAEA/wACgAEAA4D8gAOA/cDAwEDAwAIAwMDAwP8AwMDAAQDAwP7AwMDAAAAFAAAAAAQAA4AAAwAHAAsADwATAAATIRUhFSEVIREhFSERIRUhESEVIQAEAPwAAoD9gAKA/YAEAPwABAD8AAOAgECA/wCAAUCA/wCAAAAAAAUAAAAABAADgAADAAcACwAPABMAABMhFSEXIRUhESEVIQMhFSERIRUhAAQA/ADAAoD9gAKA/YDABAD8AAQA/AADgIBAgP8AgAFAgP8AgAAABQAAAAAEAAOAAAMABwALAA8AEwAAEyEVIQUhFSERIRUhASEVIREhFSEABAD8AAGAAoD9gAKA/YD+gAQA/AAEAPwAA4CAQID/AIABQID/AIAAAAAAAQA/AD8C5gLmACwAACUUDwEGIyIvAQcGIyIvASY1ND8BJyY1ND8BNjMyHwE3NjMyHwEWFRQPARcWFQLmEE4QFxcQqKgQFxYQThAQqKgQEE4QFhcQqKgQFxcQThAQqKgQwxYQThAQqKgQEE4QFhcQqKgQFxcQThAQqKgQEE4QFxcQqKgQFwAAAAYAAAAAAyUDbgAUACgAPABNAFUAggAAAREUBwYrASInJjURNDc2OwEyFxYVMxEUBwYrASInJjURNDc2OwEyFxYXERQHBisBIicmNRE0NzY7ATIXFhMRIREUFxYXFjMhMjc2NzY1ASEnJicjBgcFFRQHBisBERQHBiMhIicmNREjIicmPQE0NzY7ATc2NzY7ATIXFh8BMzIXFhUBJQYFCCQIBQYGBQgkCAUGkgUFCCUIBQUFBQglCAUFkgUFCCUIBQUFBQglCAUFSf4ABAQFBAIB2wIEBAQE/oABABsEBrUGBAH3BgUINxobJv4lJhsbNwgFBQUFCLEoCBcWF7cXFhYJKLAIBQYCEv63CAUFBQUIAUkIBQYGBQj+twgFBQUFCAFJCAUGBgUI/rcIBQUFBQgBSQgFBgYF/lsCHf3jDQsKBQUFBQoLDQJmQwUCAgVVJAgGBf3jMCIjISIvAiAFBggkCAUFYBUPDw8PFWAFBQgAAgAHAEkDtwKvABoALgAACQEGIyIvASY1ND8BJyY1ND8BNjMyFwEWFRQHARUUBwYjISInJj0BNDc2MyEyFxYBTv72BgcIBR0GBuHhBgYdBQgHBgEKBgYCaQUFCP3bCAUFBQUIAiUIBQUBhf72BgYcBggHBuDhBgcHBh0FBf71BQgHBv77JQgFBQUFCCUIBQUFBQAAAAEAIwAAA90DbgCzAAAlIicmIyIHBiMiJyY1NDc2NzY3Njc2PQE0JyYjISIHBh0BFBcWFxYzFhcWFRQHBiMiJyYjIgcGIyInJjU0NzY3Njc2NzY9ARE0NTQ1NCc0JyYnJicmJyYnJiMiJyY1NDc2MzIXFjMyNzYzMhcWFRQHBiMGBwYHBh0BFBcWMyEyNzY9ATQnJicmJyY1NDc2MzIXFjMyNzYzMhcWFRQHBgciBwYHBhURFBcWFxYXMhcWFRQHBiMDwRkzMhoZMjMZDQgHCQoNDBEQChIBBxX+fhYHARUJEhMODgwLBwcOGzU1GhgxMRgNBwcJCQsMEA8JEgECAQIDBAQFCBIRDQ0KCwcHDho1NRoYMDEYDgcHCQoMDRAQCBQBBw8BkA4HARQKFxcPDgcHDhkzMhkZMTEZDgcHCgoNDRARCBQUCRERDg0KCwcHDgACAgICDAsPEQkJAQEDAwUMROAMBQMDBQzUUQ0GAQIBCAgSDwwNAgICAgwMDhEICQECAwMFDUUhAdACDQ0ICA4OCgoLCwcHAwYBAQgIEg8MDQICAgINDA8RCAgBAgEGDFC2DAcBAQcMtlAMBgEBBgcWDwwNAgICAg0MDxEICAEBAgYNT/3mRAwGAgIBCQgRDwwNAAACAAD/twP/A7cAEwA5AAABMhcWFRQHAgcGIyInJjU0NwE2MwEWFxYfARYHBiMiJyYnJicmNRYXFhcWFxYzMjc2NzY3Njc2NzY3A5soHh4avkw3RUg0NDUBbSEp/fgXJicvAQJMTHtHNjYhIRARBBMUEBASEQkXCA8SExUVHR0eHikDtxsaKCQz/plGNDU0SUkwAUsf/bErHx8NKHpNTBobLi86OkQDDw4LCwoKFiUbGhERCgsEBAIAAQAAAAAAANox8glfDzz1AAsEAAAAAADVYbp/AAAAANVhun8AAP+3BAEDwAAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAA//8EAQABAAAAAAAAAAAAAAAAAAAAHwQAAAAAAAAAAAAAAAIAAAAEAAAABAAAAAQAAAAEAADABAAAAAQAAAAEAAAABAAAQAQAAAAEAAAABAAAUwQAAAAEAAAABAAAwAQAAMAEAACABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAAyUAPwMlAAADvgAHBAAAIwP/AAAAAAAAAAoAFAAeAEwAlADaAQoBPgFwAcgCBgJQAnoDBAN6A8gEAgQ2BE4EpgToBTAFWAWABaoF7gamBvAH4gg+AAEAAAAfALQACgAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAHAAAAAQAAAAAAAgAHAGAAAQAAAAAAAwAHADYAAQAAAAAABAAHAHUAAQAAAAAABQALABUAAQAAAAAABgAHAEsAAQAAAAAACgAaAIoAAwABBAkAAQAOAAcAAwABBAkAAgAOAGcAAwABBAkAAwAOAD0AAwABBAkABAAOAHwAAwABBAkABQAWACAAAwABBAkABgAOAFIAAwABBAkACgA0AKRpY29tb29uAGkAYwBvAG0AbwBvAG5WZXJzaW9uIDEuMABWAGUAcgBzAGkAbwBuACAAMQAuADBpY29tb29uAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG5SZWd1bGFyAFIAZQBnAHUAbABhAHJpY29tb29uAGkAYwBvAG0AbwBvAG5Gb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format(\'truetype\'); font-weight: normal; font-style: normal;}[class^="w-e-icon-"],[class*=" w-e-icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: \'icomoon\' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}.w-e-icon-close:before { content: "\\f00d";}.w-e-icon-upload2:before { content: "\\e9c6";}.w-e-icon-trash-o:before { content: "\\f014";}.w-e-icon-header:before { content: "\\f1dc";}.w-e-icon-pencil2:before { content: "\\e906";}.w-e-icon-paint-brush:before { content: "\\f1fc";}.w-e-icon-image:before { content: "\\e90d";}.w-e-icon-play:before { content: "\\e912";}.w-e-icon-location:before { content: "\\e947";}.w-e-icon-undo:before { content: "\\e965";}.w-e-icon-redo:before { content: "\\e966";}.w-e-icon-quotes-left:before { content: "\\e977";}.w-e-icon-list-numbered:before { content: "\\e9b9";}.w-e-icon-list2:before { content: "\\e9bb";}.w-e-icon-link:before { content: "\\e9cb";}.w-e-icon-happy:before { content: "\\e9df";}.w-e-icon-bold:before { content: "\\ea62";}.w-e-icon-underline:before { content: "\\ea63";}.w-e-icon-italic:before { content: "\\ea64";}.w-e-icon-strikethrough:before { content: "\\ea65";}.w-e-icon-table2:before { content: "\\ea71";}.w-e-icon-paragraph-left:before { content: "\\ea77";}.w-e-icon-paragraph-center:before { content: "\\ea78";}.w-e-icon-paragraph-right:before { content: "\\ea79";}.w-e-icon-terminal:before { content: "\\f120";}.w-e-icon-page-break:before { content: "\\ea68";}.w-e-icon-cancel-circle:before { content: "\\ea0d";}.w-e-toolbar { display: -webkit-box; display: -ms-flexbox; display: flex; padding: 0 5px; /* 单个菜单 */}.w-e-toolbar .w-e-menu { position: relative; z-index: 10001; text-align: center; padding: 5px 10px; cursor: pointer;}.w-e-toolbar .w-e-menu i { color: #999;}.w-e-toolbar .w-e-menu:hover i { color: #333;}.w-e-toolbar .w-e-active i { color: #1e88e5;}.w-e-toolbar .w-e-active:hover i { color: #1e88e5;}.w-e-text-container .w-e-panel-container { position: absolute; top: 0; left: 50%; border: 1px solid #ccc; border-top: 0; box-shadow: 1px 1px 2px #ccc; color: #333; background-color: #fff; /* 为 emotion panel 定制的样式 */ /* 上传图片的 panel 定制样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-close { position: absolute; right: 0; top: 0; padding: 5px; margin: 2px 5px 0 0; cursor: pointer; color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-close:hover { color: #333;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title { list-style: none; display: -webkit-box; display: -ms-flexbox; display: flex; font-size: 14px; margin: 2px 10px 0 10px; border-bottom: 1px solid #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-item { padding: 3px 5px; color: #999; cursor: pointer; margin: 0 3px; position: relative; top: 1px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-active { color: #333; border-bottom: 1px solid #333; cursor: default; font-weight: 700;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content { padding: 10px 15px 10px 15px; font-size: 16px; /* 输入框的样式 */ /* 按钮的样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content button:focus { outline: none;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea { width: 100%; border: 1px solid #ccc; padding: 5px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus { border-color: #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text] { border: none; border-bottom: 1px solid #ccc; font-size: 14px; height: 20px; color: #333; text-align: left;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].small { width: 30px; text-align: center;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].block { display: block; width: 100%; margin: 10px 0;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text]:focus { border-bottom: 2px solid #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button { font-size: 14px; color: #1e88e5; border: none; padding: 5px 10px; background-color: #fff; cursor: pointer; border-radius: 3px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.left { float: left; margin-right: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.right { float: right; margin-left: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.gray { color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.red { color: #c24f4a;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button:hover { background-color: #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container:after { content: ""; display: table; clear: both;}.w-e-text-container .w-e-panel-container .w-e-emoticon-container .w-e-item { cursor: pointer; font-size: 18px; padding: 0 3px; display: inline-block; *display: inline; *zoom: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container { text-align: center;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn { display: inline-block; *display: inline; *zoom: 1; color: #999; cursor: pointer; font-size: 60px; line-height: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn:hover { color: #333;}.w-e-text-container { position: relative;}.w-e-text-container .w-e-progress { position: absolute; background-color: #1e88e5; bottom: 0; left: 0; height: 1px;}.w-e-text { padding: 0 10px; overflow-y: scroll;}.w-e-text p,.w-e-text h1,.w-e-text h2,.w-e-text h3,.w-e-text h4,.w-e-text h5,.w-e-text table,.w-e-text pre { margin: 10px 0; line-height: 1.5;}.w-e-text ul,.w-e-text ol { margin: 10px 0 10px 20px;}.w-e-text blockquote { display: block; border-left: 8px solid #d0e5f2; padding: 5px 10px; margin: 10px 0; line-height: 1.4; font-size: 100%; background-color: #f1f1f1;}.w-e-text code { display: inline-block; *display: inline; *zoom: 1; background-color: #f1f1f1; border-radius: 3px; padding: 3px 5px; margin: 0 3px;}.w-e-text pre code { display: block;}.w-e-text table { border-top: 1px solid #ccc; border-left: 1px solid #ccc;}.w-e-text table td,.w-e-text table th { border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; padding: 3px 5px;}.w-e-text table th { border-bottom: 2px solid #ccc; text-align: center;}.w-e-text:focus { outline: none;}.w-e-text img { cursor: pointer;}.w-e-text img:hover { box-shadow: 0 0 5px #333;}.w-e-text img.w-e-selected { border: 2px solid #1e88e5;}.w-e-text img.w-e-selected:hover { box-shadow: none;}'; + +// 将 css 代码添加到