feat: 完成todo功能
This commit is contained in:
parent
ad179c686d
commit
fcceeba68c
3
.babelrc
3
.babelrc
|
@ -19,7 +19,6 @@
|
|||
"regenerator": true,
|
||||
"useESModules": false
|
||||
}
|
||||
],
|
||||
"istanbul"
|
||||
]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
# 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-*'
|
||||
paths:
|
||||
- '.github/workflows/*'
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
- 'examples/**'
|
||||
- 'build/**'
|
||||
- 'cypress/**'
|
||||
|
||||
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: npm install
|
||||
- name: Run build
|
||||
run: npm run build:dev
|
||||
- uses: cypress-io/github-action@v2
|
||||
with:
|
||||
browser: chrome
|
||||
start: npm run example
|
||||
wait-on: 'http://localhost:8881/examples/index.html'
|
|
@ -17,7 +17,7 @@ module.exports = {
|
|||
publish: false,
|
||||
},
|
||||
plugins: {
|
||||
'@release-it/conventional-changelog': {
|
||||
'./conventional-changelog.js': {
|
||||
preset: 'angular',
|
||||
infile: 'CHANGELOG.md',
|
||||
},
|
||||
|
|
48
CHANGELOG.md
48
CHANGELOG.md
|
@ -1,3 +1,51 @@
|
|||
## [4.5.2](https://github.com/wangeditor-team/wangEditor/compare/v4.5.1...v4.5.2) (2020-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 错误提示类型优化 ([61cc9b4](https://github.com/wangeditor-team/wangEditor/commit/61cc9b4d5081ce7e0733753e138ae2ff4f157921))
|
||||
* 多实例全屏的问题 ([83f6b43](https://github.com/wangeditor-team/wangEditor/commit/83f6b43db87a4671d46d302f975a5e6bf7b8b070))
|
||||
* 去掉测试全屏的代码 ([03a3f81](https://github.com/wangeditor-team/wangEditor/commit/03a3f811a01255bb5aeb8f6a64985919df76e271))
|
||||
* 添加 custom alert 的 html 文档 ([baba963](https://github.com/wangeditor-team/wangEditor/commit/baba96388c819e8b51288fb8997d14998f3c7447))
|
||||
* 添加居中样式 ([8db384a](https://github.com/wangeditor-team/wangEditor/commit/8db384ab19987943e255b22fe77144c0be9ebf8a))
|
||||
* fix wrap wrap in firefox ([7ebdbbf](https://github.com/wangeditor-team/wangEditor/commit/7ebdbbf3dbccf5a83d02518698d74ef643a1576b))
|
||||
* fix: 某些情况下,无法成功粘贴 ([dbfe2eb](https://github.com/wangeditor-team/wangEditor/commit/dbfe2eb3db0426a22fe3296fa3e62c1dc44b6537)), closes [#2530](https://github.com/wangeditor-team/wangEditor/issues/2530) [#2530](https://github.com/wangeditor-team/wangEditor/issues/2530)
|
||||
* img 添加 重置 效果 ([10df1bb](https://github.com/wangeditor-team/wangEditor/commit/10df1bbcda00b723299a4935077a3636f4a09906))
|
||||
|
||||
## [4.5.1](https://github.com/wangeditor-team/wangEditor/compare/v4.5.0...v4.5.1) (2020-11-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* setJSON的表格不成功的问题解决 ([f57395b](https://github.com/wangeditor-team/wangEditor/commit/f57395b3445fe05debdeaf4eaae7ddd1ce44da1e))
|
||||
* uploadImgAccept 类型 ([18b7a42](https://github.com/wangeditor-team/wangEditor/commit/18b7a42e02a6079502d3ce7583524f3f391a082f))
|
||||
* 去掉console.log ([6197747](https://github.com/wangeditor-team/wangEditor/commit/6197747700ce99616831624688f6395b4baaae9b))
|
||||
* 变量名优化 ([5d20096](https://github.com/wangeditor-team/wangEditor/commit/5d20096319a63c11bd9071dfe107245fac632597))
|
||||
* 完善了设置字体大小、样式、背景、文字颜色等菜单的功能 ([3072543](https://github.com/wangeditor-team/wangEditor/commit/3072543efdff2cb36f594ac396eb6c2c61815d13))
|
||||
* 添加自定义setJSON表格按钮 ([7bd76c6](https://github.com/wangeditor-team/wangEditor/commit/7bd76c6ebab4011e40fab4d78fa59c74903df7b6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 support custom accept for image [#1655](https://github.com/wangeditor-team/wangEditor/issues/1655) ([5af4dcd](https://github.com/wangeditor-team/wangEditor/commit/5af4dcd505a41a3f4fbe6b1e885c0005bcf887d8))
|
||||
|
||||
# [4.6.0](https://github.com/wangeditor-team/wangEditor/compare/v4.5.0...v4.6.0) (2020-11-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* setJSON的表格不成功的问题解决 ([f57395b](https://github.com/wangeditor-team/wangEditor/commit/f57395b3445fe05debdeaf4eaae7ddd1ce44da1e))
|
||||
* uploadImgAccept 类型 ([18b7a42](https://github.com/wangeditor-team/wangEditor/commit/18b7a42e02a6079502d3ce7583524f3f391a082f))
|
||||
* 去掉console.log ([6197747](https://github.com/wangeditor-team/wangEditor/commit/6197747700ce99616831624688f6395b4baaae9b))
|
||||
* 变量名优化 ([5d20096](https://github.com/wangeditor-team/wangEditor/commit/5d20096319a63c11bd9071dfe107245fac632597))
|
||||
* 完善了设置字体大小、样式、背景、文字颜色等菜单的功能 ([3072543](https://github.com/wangeditor-team/wangEditor/commit/3072543efdff2cb36f594ac396eb6c2c61815d13))
|
||||
* 添加自定义setJSON表格按钮 ([7bd76c6](https://github.com/wangeditor-team/wangEditor/commit/7bd76c6ebab4011e40fab4d78fa59c74903df7b6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 support custom accept for image [#1655](https://github.com/wangeditor-team/wangEditor/issues/1655) ([5af4dcd](https://github.com/wangeditor-team/wangEditor/commit/5af4dcd505a41a3f4fbe6b1e885c0005bcf887d8))
|
||||
|
||||
# [4.5.0](https://github.com/wangeditor-team/wangEditor/compare/v4.4.2...v4.5.0) (2020-11-20)
|
||||
|
||||
|
||||
|
|
16
README.md
16
README.md
|
@ -19,13 +19,27 @@
|
|||
|
||||
## 基本使用
|
||||
|
||||
npm 安装 `npm i wangeditor --save` ,几行代码即可创建一个编辑器
|
||||
### NPM
|
||||
```bash
|
||||
npm i wangeditor --save
|
||||
```
|
||||
安装后几行代码即可创建一个编辑器:
|
||||
|
||||
```js
|
||||
import E from "wangeditor";
|
||||
const editor = new E("#div1");
|
||||
editor.create();
|
||||
```
|
||||
### CDN
|
||||
```html
|
||||
<script type="text/javascript" src="https://unpkg.com/wangeditor/dist/wangEditor.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
const E = window.wangEditor
|
||||
const editor = new E('#div1')
|
||||
// 或者 const editor = new E(document.getElementById('div1'))
|
||||
editor.create()
|
||||
</script>
|
||||
```
|
||||
|
||||
更多使用方法,可参考[文档](http://www.wangeditor.com/doc/)。
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
const { EOL } = require('os')
|
||||
const fs = require('fs')
|
||||
const { Plugin } = require('release-it')
|
||||
const conventionalChangelog = require('conventional-changelog')
|
||||
const concat = require('concat-stream')
|
||||
const prependFile = require('prepend-file')
|
||||
|
||||
class ConventionalChangelog extends Plugin {
|
||||
getInitialOptions(options, namespace) {
|
||||
options[namespace].tagName = options.git.tagName
|
||||
return options[namespace]
|
||||
}
|
||||
|
||||
async bump(version) {
|
||||
this.setContext({ version })
|
||||
const { previousTag, currentTag } = await this.getConventionalConfig()
|
||||
this.setContext({ previousTag, currentTag })
|
||||
const changelog = await this.generateChangelog()
|
||||
this.setContext({ changelog })
|
||||
}
|
||||
|
||||
async getConventionalConfig() {
|
||||
const version = this.getContext('version')
|
||||
|
||||
const previousTag = this.config.getContext('latestTag')
|
||||
const tagTemplate =
|
||||
this.options.tagName || ((previousTag || '').match(/^v/) ? 'v${version}' : '${version}')
|
||||
const currentTag = tagTemplate.replace('${version}', version)
|
||||
|
||||
return { version, previousTag, currentTag }
|
||||
}
|
||||
|
||||
getChangelogStream(options = {}) {
|
||||
const { version, previousTag, currentTag } = this.getContext()
|
||||
return conventionalChangelog(
|
||||
Object.assign(options, this.options),
|
||||
{ version, previousTag, currentTag },
|
||||
{
|
||||
debug: this.config.isDebug ? this.debug : null,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
generateChangelog(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const resolver = result => resolve(result.toString().trim())
|
||||
const changelogStream = this.getChangelogStream(options)
|
||||
changelogStream.pipe(concat(resolver))
|
||||
changelogStream.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
async writeChangelog() {
|
||||
const { infile } = this.options
|
||||
let { changelog } = this.getContext()
|
||||
|
||||
let hasInfile = false
|
||||
try {
|
||||
fs.accessSync(infile)
|
||||
hasInfile = true
|
||||
} catch (err) {
|
||||
this.debug(err)
|
||||
}
|
||||
|
||||
if (!hasInfile) {
|
||||
changelog = await this.generateChangelog({ releaseCount: 0 })
|
||||
this.debug({ changelog })
|
||||
}
|
||||
|
||||
await prependFile(infile, changelog + EOL + EOL)
|
||||
|
||||
if (!hasInfile) {
|
||||
await this.exec(`git add ${infile}`)
|
||||
}
|
||||
}
|
||||
|
||||
async beforeRelease() {
|
||||
const { infile } = this.options
|
||||
const { isDryRun } = this.config
|
||||
|
||||
this.log.exec(`Writing changelog to ${infile}`, isDryRun)
|
||||
|
||||
if (infile && !isDryRun) {
|
||||
await this.writeChangelog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConventionalChangelog
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:8881",
|
||||
"defaultCommandTimeout": 8000
|
||||
"defaultCommandTimeout": 8000,
|
||||
"video": false
|
||||
}
|
||||
|
|
|
@ -62,12 +62,12 @@ describe('表情', () => {
|
|||
cy.get('@emotionList')
|
||||
.eq(0)
|
||||
.as('emotion')
|
||||
.click()
|
||||
.click({ timeout: 1000 })
|
||||
.then($el => {
|
||||
const img = $el.find('img')
|
||||
const src = (img.get(0) as HTMLImageElement).src
|
||||
|
||||
cy.get('@Editable').find('img').should('have.attr', 'src', src)
|
||||
cy.get('@Editable').find('img', { timeout: 20000 }).should('have.attr', 'src', src)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -7,8 +7,7 @@ describe('插入视频', () => {
|
|||
cy.get('@Editable').clear()
|
||||
})
|
||||
|
||||
const videoUrl =
|
||||
'https://player.vimeo.com/external/288190258.sd.mp4?s=8bbc2d25f23cf412a99bd2dbdfa08688fd973ce8&profile_id=164&oauth2_token_id=57447761'
|
||||
const videoUrl = 'https://www.bilibili.com/video/BV14p4y1v776/'
|
||||
|
||||
it('点击菜单打开插入视频的面板', () => {
|
||||
cy.getByClass('toolbar').children().eq(17).as('videoMenu').click()
|
||||
|
@ -33,15 +32,14 @@ describe('插入视频', () => {
|
|||
cy.get('@Editable').find('video').should('not.exist')
|
||||
|
||||
cy.getByClass('toolbar').children().eq(17).as('imgMenu').click()
|
||||
const videoEl = `<video src="${videoUrl}" controls></video>`
|
||||
const videoEl = `<iframe src="${videoUrl}" controls></iframe>`
|
||||
cy.get('@imgMenu').find('.w-e-panel-container').as('Panel').find('input').type(videoEl)
|
||||
cy.get('@Panel').find('.w-e-button-container button').click()
|
||||
|
||||
cy.get('@Editable')
|
||||
.find('video')
|
||||
cy.get('.w-e-text-container iframe', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.then($video => {
|
||||
const video = $video.get(0)
|
||||
const video = $video.get(0) as HTMLVideoElement
|
||||
expect(video.src).to.eq(videoUrl)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
describe('添加todo', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/examples/index.html')
|
||||
|
||||
cy.getByClass('text-container').children().first().as('Editable')
|
||||
|
||||
cy.get('@Editable').clear()
|
||||
})
|
||||
|
||||
const text = 'text1234'
|
||||
|
||||
it('点击todo菜单插入todo样式', () => {
|
||||
cy.get('@Editable').type(text)
|
||||
cy.get('@Editable').contains(text)
|
||||
|
||||
cy.getByClass('toolbar').children().eq(23).click()
|
||||
cy.getByClass('toolbar').children().eq(23).should('have.class', 'w-e-active')
|
||||
|
||||
cy.get('@Editable').find('ul').should('contain.text', text).find('input')
|
||||
})
|
||||
|
||||
it('再次点击todo菜单移除todo', () => {
|
||||
cy.get('@Editable').type(text)
|
||||
cy.get('@Editable').contains(text)
|
||||
|
||||
cy.getByClass('toolbar').children().eq(23).as('todo').click()
|
||||
cy.getByClass('toolbar').children().eq(23).should('have.class', 'w-e-active')
|
||||
|
||||
cy.get('@Editable').find('ul').should('contain.text', text).find('input')
|
||||
|
||||
cy.get('@todo').click()
|
||||
cy.get('@todo').should('not.have.class', 'w-e-active')
|
||||
|
||||
cy.get('@Editable').find('input').should('not.exist')
|
||||
cy.get('@Editable').find('p').contains(text)
|
||||
})
|
||||
})
|
|
@ -7,6 +7,7 @@
|
|||
- 语言:`typescript`
|
||||
- 依赖框架和工具:无(编辑器作为第三方工具,应该控制自身的代码体积和依赖,让用户简单实用)
|
||||
- 打包工具:`webpack`
|
||||
- 测试工具 `jest` `cypress`
|
||||
|
||||
## 主要目录
|
||||
|
||||
|
|
|
@ -2,6 +2,24 @@
|
|||
|
||||
欢迎非团队成员贡献源码,我们会及时审核、合并。
|
||||
|
||||
- fork 源码
|
||||
- 修改代码,并提交
|
||||
- fork 源码,下载到本地,并 `npm i`
|
||||
- 修改代码,运行 `npm start` 自测
|
||||
- 运行 `npm run all-check` 执行最后的检查
|
||||
- 提交代码到你的 github 仓库
|
||||
- 提交 Pull Request ,合并到 `feature-third-contribution` 分支(注意,其他分支不予通过!!!)
|
||||
|
||||
注意,提交 Pull Request 时,请一定说明你这次改动的目的。模板可参考:
|
||||
|
||||
```md
|
||||
## 遇到了什么问题
|
||||
|
||||
*请详细描述问题,或者贴一个 issue 链接*
|
||||
|
||||
## 你的预期是什么
|
||||
|
||||
*请详细描述,你修改代码之后的样子*
|
||||
|
||||
## 是否进行了详细的自测?
|
||||
|
||||
*是/否*
|
||||
```
|
||||
|
|
26
docs/cr.md
26
docs/cr.md
|
@ -4,7 +4,7 @@
|
|||
|
||||
## 检查 PR
|
||||
|
||||
- 是否要往 `dev` 分支合并,而不是其他的分支
|
||||
- 是否要往 `dev` 分支(或其他指定的分支)合并,而不是其他的分支
|
||||
- commits 描述是否符合开发规范
|
||||
- github actions 检查是否成功
|
||||
|
||||
|
@ -22,14 +22,26 @@
|
|||
代码走查如果有问题,会在 Pull Request 上回复评论意见,并通知开发者。
|
||||
开发者根据评论意见,继续修改,然后重新提交,重新代码走查。
|
||||
|
||||
## 合并代码
|
||||
## 反馈评审意见
|
||||
|
||||
代码走查没有问题,则通过检查,并 Merge Pull Request 合并到 dev 分支。
|
||||
第一,针对每一行,提交评审意见。
|
||||
|
||||
如果合并出现冲突,开发者需要重新修改代码,重新提交 Pull Request 。
|
||||
![](./imgs/cr1.png)
|
||||
|
||||
成功合并了 dev 分支之后,确保 github actions 的任务能执行通过。如果 actions 任务有问题,要查看日志,解决问题。
|
||||
第二,待所有代码都审核完,统一提交本次 review 的意见,通知任务负责人。
|
||||
|
||||
## 回归测试 dev 分支
|
||||
![](./imgs/cr2.png)
|
||||
|
||||
打开浏览器访问 `http://106.55.153.217:8881/dev/examples/index.html` ,测试功能。
|
||||
第三,待任务负责人修改之后,重新审核。
|
||||
|
||||
## 通过审核
|
||||
|
||||
提交 Approve review ,通知任务负责人。
|
||||
|
||||
![](./imgs/cr3.png)
|
||||
|
||||
## 不用合并代码
|
||||
|
||||
代码审核通过之后,**不用**合并代码,通知负责人即可。
|
||||
|
||||
负责人等待所有 code review 结束之后,群里通知作者(群主)合并代码。
|
||||
|
|
26
docs/dev.md
26
docs/dev.md
|
@ -28,7 +28,11 @@ git config user.email xxx@xxx.com
|
|||
|
||||
## 运行代码
|
||||
|
||||
打开两个控制台,进入项目目录,分别运行 `npm run dev` 和 `npm run example` ,然后浏览器访问 `http://127.0.0.1:8881/examples/` 即可。
|
||||
```sh
|
||||
npm install
|
||||
npm run start
|
||||
# 浏览器访问 http://localhost:8881/examples/index.html
|
||||
```
|
||||
|
||||
## 创建分支
|
||||
|
||||
|
@ -36,8 +40,10 @@ git config user.email xxx@xxx.com
|
|||
|
||||
- `master` 主干分支,当前正在运行的代码。**不可**直接往 `master` 提交代码。
|
||||
- `dev` 开发分支,当前正在开发、但尚未发布的代码。**不可**直接往 `dev` 提交代码,但可以合并其他分支。
|
||||
- `server` 开发分支,用于部署 server 端功能,**不可**直接往 `server` 提交代码,但可以合并其他分支。
|
||||
- `feature-xxx` 开发新功能
|
||||
- `fix-xxx` bug 修复
|
||||
- `hotfix-xxx` 高优紧急 bug 修复,修复完需紧急上线
|
||||
- `doc-xxx` 仅修改文档,不修改代码
|
||||
|
||||
例如你要开发一个图片上传的功能,可以根据 master 分支拉一个新的分支 `git checkout -b feature-upload-img`
|
||||
|
@ -56,7 +62,7 @@ git config user.email xxx@xxx.com
|
|||
写完代码之后,一定要进行自测:
|
||||
|
||||
- 运行 `npm run test` 进行单元测试
|
||||
- 运行 `npm run dev` 和 `npm run example` 打开页面,进行功能测试
|
||||
- 运行 `npm start` 打开页面,进行功能测试
|
||||
- 自己的功能正常
|
||||
- 其他功能不影响
|
||||
|
||||
|
@ -91,7 +97,7 @@ git config user.email xxx@xxx.com
|
|||
|
||||
## 自动部署远程测试页
|
||||
|
||||
说明:只有以 `feature-` 和 `fix-` 开头的分支,才具有这个功能。
|
||||
说明:只有以 `feature-` `fix-` 和 `hotfix-` 开头的分支,才具有这个功能。
|
||||
|
||||
当提交完自己的分支之后,github actions 会自动触发部署到腾讯云测试机。
|
||||
查看 [actions 列表](https://github.com/wangeditor-team/wangEditor/actions),待所有任务运行完成之后。
|
||||
|
@ -107,7 +113,15 @@ git config user.email xxx@xxx.com
|
|||
|
||||
然后,一定要自己先看一看 PR 的 **Files Changed** ,看是否符合自己的预期,重要!!如果不符合预期,则把这个 PR 关掉,再重新修改代码,重新提交 PR 。
|
||||
|
||||
拿到 Pull Request 的链接,然后
|
||||
将 Pull Request 的链接贴到任务卡片中,这样其他人就能看到了。
|
||||
|
||||
- 如果你是开发小组成员,将 PR 链接粘贴到任务卡片中,并通知导师来代码走查
|
||||
- 如果你不是开发小组成员,可在 QQ 群 @ 通知群主,并给出 PR 链接
|
||||
## 剩下的步骤
|
||||
|
||||
剩下的步骤,也非常重要,加入团队之后可以从团队知识库中找到说明。
|
||||
|
||||
- 交叉测试
|
||||
- code review
|
||||
|
||||
## 非团队人员贡献源码
|
||||
|
||||
自己测试,自己 code review,具体参考 [contribution.md](./contribution.md)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
Binary file not shown.
After Width: | Height: | Size: 127 KiB |
58
docs/pub.md
58
docs/pub.md
|
@ -1,25 +1,54 @@
|
|||
# 发布到 npm
|
||||
|
||||
发布项目要有节奏,每周定期发布。除非遇到紧急 bug ,需紧急应对。
|
||||
|
||||
## 技术方案
|
||||
|
||||
- release-it 发布 tag 和 ChangeLog
|
||||
- github actions 监控 tag 提交,自动发布到 npm
|
||||
|
||||
### release-it
|
||||
|
||||
`v4.1.0` 版本开始,采用 [release-it](https://github.com/release-it/release-it) 来执行发布操作。
|
||||
不过,release-it 仅仅用来创建 tag 并 push ,真正 publish 到 npm 是在 github actions 中实现的,代码见 `.github/workflows/npm-publish.yml` 。
|
||||
|
||||
## set upstream
|
||||
### set upstream
|
||||
|
||||
切换到 master 分支,执行 `git branch --set-upstream-to=origin/master master`
|
||||
|
||||
## 明确发布的范围
|
||||
|
||||
打开以下页面,分别找到“待发布”的任务
|
||||
|
||||
- bugs https://github.com/wangeditor-team/wangEditor/projects/1
|
||||
- feature https://www.teambition.com/project/5eb8b4e2ce8c00002237bb81/tasks/view/all
|
||||
|
||||
要确定这些任务的 pr ,都已经被合并到了 `dev` 分支。否则,终止发布,联系任务负责人确认。
|
||||
|
||||
## 合并代码
|
||||
|
||||
到 https://github.com/wangeditor-team/wangEditor/pulls 创建 pr ,分别将以下分支,合并到 `master` 分支。
|
||||
|
||||
- `dev`
|
||||
- `feature-third-contribution` (其他人贡献的代码,会合并到这里)
|
||||
|
||||
然后,等待 master 分支的 [github actions](https://github.com/wangeditor-team/wangEditor/actions) 执行完成,主要 jest 和 cypress 测试的流程。
|
||||
|
||||
## 触发 release
|
||||
|
||||
执行 `npm run release` 。主要进行如下步骤(配置见 `.release-it.js`)
|
||||
本地执行 `npm run release` 。主要进行如下步骤(配置见 `.release-it.js`)
|
||||
|
||||
- 必须是 master 分支
|
||||
- 首先 `git pull origin master`
|
||||
- 然后 `npm run all-check`
|
||||
- 创建 tag 并 push (它会自动推荐 tag 的版本号,我们默认用即可)
|
||||
|
||||
**【注意】千万不要随意执行 `npm run release` !!!**
|
||||
## release 注意事项
|
||||
|
||||
**【注意1】选择版本时,一定要慎重!!!** 日常的小改动,选择 `patch` 版本。
|
||||
如果要选择 `minor` 甚至 `major` 版本,请一定与作者联系确定!!!
|
||||
|
||||
**【注意2】千万不要随意执行 `npm run release` !!!**
|
||||
如果想要体验一下发布过程,可执行 `npm run just-try-release` ,这个随便玩。
|
||||
|
||||
## 发布到 npm
|
||||
|
@ -28,19 +57,26 @@
|
|||
|
||||
待 github actions 执行完成,只要没有报错,即表示发布完成。
|
||||
|
||||
## 合并代码到开发分支
|
||||
|
||||
提交 pr ,把 master 的代码合并到以下分支,以便接接下来开发。
|
||||
|
||||
- `dev`
|
||||
- `feature-third-contribution`
|
||||
|
||||
## 回归测试
|
||||
|
||||
### 自动流程
|
||||
发布完成之后,访问 http://106.55.153.217:8881/publish-npm-test/ 即可得到最新版本的 demo 。
|
||||
|
||||
github actions 在发布完 npm 之后,会自动安装最新版,并打包出一个测试页 http://106.55.153.217:8881/publish-npm-test/ 用于回归测试。
|
||||
## 修改任务状态
|
||||
|
||||
PS:配置代码见 `.github/workflows/npm-publish.yml`
|
||||
将任务列表中,“待发布”的任务,拖拽到“已发布”阶段。并通知任务负责人。
|
||||
|
||||
### 手动测试
|
||||
如果修改的是 issue ,则回复“已修复,请更新到最新版本”,并关闭。
|
||||
|
||||
可下载测试 demo `git clone git@github.com:wangeditor-team/we-demo.git` ,安装最新的包,本地运行。
|
||||
## 看是否需要修改文档
|
||||
|
||||
## 合并代码到 dev
|
||||
刚刚已发布的任务,如果是新增的功能或者配置,可能需要修改用户文档。
|
||||
和任务负责人确认一下,如果需要,则尽快修改文档。
|
||||
|
||||
发布完成之后,将 master 代码合并到 dev 分支,并提交。
|
||||
以便后续开发 merge 时,代码更简洁。
|
||||
【注意】必须先发布功能,再修改文档。顺序不能反了。
|
||||
|
|
11
docs/test.md
11
docs/test.md
|
@ -5,14 +5,11 @@
|
|||
单元测试是一个项目最基础的测试,也是一个项目质量保证的第一关,所以确保一定的单元测试覆盖率还是很重要的。我们项目的单元测试使用了业界内比较受欢迎的 `jest` ,文档完善,生态发展也不错,也比较适合来写单元测试。
|
||||
### 目录介绍
|
||||
目前我们的单元测试放在根目录下的 `test` 目录,下面是具体的目录介绍:
|
||||
- config 主要用来存放与编辑器默认配置相关的测试文件;
|
||||
- editor 主要用来存放与编辑器构造器相关的测试文件;
|
||||
- fns 主要用来存放一些帮助方便测试的 `utils`;
|
||||
- menus 主要用来存放菜单功能的测试文件;
|
||||
- text 主要用来存放文本处理相关的测试文件;
|
||||
- utils 主要用来存放项目 `utils` 相关的测试文件;
|
||||
- helpers 用来存放配合测试的一些 utils;
|
||||
- setup 用来引入一些更加方便我们测试的 `jest` 库,比如在这里我们就引入了 `jest-dom` 来配合我们测试 HTML 相关的部分;
|
||||
- unit 主要用来存放所有单元测试文件,里面就基本按我们项目核心代码的目录结构存放对应的单元测试,比如 editor 目录就是存放编辑器相关的测试文件,以此类推。
|
||||
|
||||
可以看到,单元测试目录结构基本是跟项目核心代码的结构保持一致的,这样也让我可以很清晰地定位到相关功能的测试文件。
|
||||
可以看到,单元测试目录结构职责还是比较分明,unit下的测试文件基本是跟项目核心代码的结构保持一致的,这样也让我可以很清晰地定位到相关功能的测试文件。
|
||||
|
||||
### 编写测试
|
||||
可以根据你需要测试的功能选择对应的目录创建测试文件,目前的目录结构基本满足了编辑器所有功能了。如果之前没有写过或者学习过单元测试的同学,可以先去 [jest](https://jestjs.io/) 官网先进行一定的学习,再加入到单元测试中来。
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>wangEditor example</title>
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>wangEditor demo</p>
|
||||
<div id="div1">
|
||||
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
|
||||
<p><img src="http://www.wangeditor.com/imgs/logo.jpeg"/></p>
|
||||
</div>
|
||||
|
||||
<script src="../dist/wangEditor.js"></script>
|
||||
<script>
|
||||
// 改为使用var声明,才能在window对象上获取到编辑器实例,方便e2e测试
|
||||
var E = window.wangEditor
|
||||
var editor = new E('#div1')
|
||||
|
||||
editor.config.customAlert = function (str, code, err) {
|
||||
console.log('customAlert', str, code, err)
|
||||
alert(`str:${str}, code:${code}, err: ${err}`)
|
||||
}
|
||||
|
||||
editor.config.linkCheck = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
editor.create()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>wangEditor example</title>
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
wangEditor demo
|
||||
</p>
|
||||
<form onsubmit="console.log('submit')">
|
||||
<div id="div1">
|
||||
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
|
||||
<p>
|
||||
<img src="http://www.wangeditor.com/imgs/logo.jpeg"/>
|
||||
</p>
|
||||
</div>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
|
||||
<script src="../dist/wangEditor.js"></script>
|
||||
<script>
|
||||
// 改为使用var声明,才能在window对象上获取到编辑器实例,方便e2e测试
|
||||
var E = window.wangEditor
|
||||
var editor = new E('#div1')
|
||||
editor.create()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -14,14 +14,13 @@
|
|||
wangEditor getJSON
|
||||
</p>
|
||||
<div id="div1">
|
||||
<p>我是一行文字</p>
|
||||
<p>我是一行<b>这里加粗</b>文字</p>
|
||||
<p>我是一行<a href="123" target="_blank">链接</a>文字</p>
|
||||
<p><img src="https://www.tslang.cn/assets/images/fork_me_ribbon.svg" style="width: 200px;" /></p>
|
||||
|
||||
</div>
|
||||
|
||||
<button onclick="setJsonOne()">setJSON</button>
|
||||
<button onclick="setJsonTwo()">setJSON(自定义 JSON)</button>
|
||||
<button onclick="getJson()">getJSON</button>
|
||||
<button onclick="setTable()">setJSON Table</button>
|
||||
|
||||
<script src="../dist/wangEditor.js"></script>
|
||||
<script>
|
||||
|
@ -34,7 +33,14 @@
|
|||
|
||||
console.log(editor.txt.getJSON())
|
||||
let myJson = editor.txt.getJSON()
|
||||
|
||||
function getJson(){
|
||||
myJson = editor.txt.getJSON()
|
||||
console.log(myJson, JSON.stringify(myJson))
|
||||
}
|
||||
|
||||
function setJsonOne() {
|
||||
console.log(myJson)
|
||||
editor.txt.setJSON(myJson)
|
||||
}
|
||||
|
||||
|
@ -69,6 +75,11 @@
|
|||
}
|
||||
])
|
||||
}
|
||||
|
||||
function setTable(){
|
||||
let tableJson = [{"tag":"p","attrs":[],"children":["setJSON表格设置成功"]},{"tag":"table","attrs":[{"name":"border","value":"0"},{"name":"width","value":"100%"},{"name":"cellpadding","value":"0"},{"name":"cellspacing","value":"0"}],"children":[{"tag":"tbody","attrs":[],"children":[{"tag":"tr","attrs":[],"children":[{"tag":"th","attrs":[],"children":["1"]},{"tag":"th","attrs":[],"children":["2"]}]},{"tag":"tr","attrs":[],"children":[{"tag":"td","attrs":[],"children":["3"]},{"tag":"td","attrs":[],"children":["2"]}]}]}]},{"tag":"p","attrs":[],"children":[{"tag":"br","attrs":[],"children":[]}]}]
|
||||
editor.txt.setJSON(tableJson)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>wangEditor example</title>
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p> wangEditor demo </p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div id="div1">
|
||||
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
|
||||
<p>
|
||||
<img src="http://www.wangeditor.com/imgs/logo.jpeg"/>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script src="../dist/wangEditor.js"></script>
|
||||
<script>
|
||||
// 改为使用var声明,才能在window对象上获取到编辑器实例,方便e2e测试
|
||||
var E = window.wangEditor
|
||||
var editor = new E('#div1')
|
||||
editor.create()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>wangEditor example</title>
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
wangEditor demo
|
||||
</p>
|
||||
<div id="div1">
|
||||
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
|
||||
<p>
|
||||
<img src="//www.baidu.com/img/flexible/logo/pc/result@2.png" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="../dist/wangEditor.js"></script>
|
||||
<script>
|
||||
// 改为使用var声明,才能在window对象上获取到编辑器实例,方便e2e测试
|
||||
var E = window.wangEditor
|
||||
var editor = new E('#div1')
|
||||
|
||||
// 测试如果输入'测试',返回false,停止插入
|
||||
editor.config.onlineVideoCheck = function (video) {
|
||||
if (video === '测试') {
|
||||
return '测试禁止插入';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
editor.config.onlineVideoCallback = function (video) {
|
||||
console.log('video', video)
|
||||
}
|
||||
|
||||
editor.create()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -2,11 +2,14 @@ module.exports = {
|
|||
roots: ['<rootDir>/test'],
|
||||
testRegex: 'test/(.+)\\.test\\.(js?|ts?)$',
|
||||
transform: {
|
||||
'^.+\\.(css|less)$': '<rootDir>/test/fns/styleMock.js',
|
||||
'^.+\\.(css|less)$': '<rootDir>/test/helpers/styleMock.js',
|
||||
'^.+\\.ts?$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^@/(.*)$': '<rootDir>/src/unit/$1',
|
||||
},
|
||||
collectCoverageFrom: ['src/**/*.ts', 'src/**/*.js'],
|
||||
setupFilesAfterEnv: ['./test/setup/index.ts']
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wangeditor",
|
||||
"version": "4.5.0",
|
||||
"version": "4.5.2",
|
||||
"description": "wangEditor - 轻量级 web 富文本编辑器,配置方便,使用简单,开源免费",
|
||||
"homepage": "http://www.wangeditor.com/",
|
||||
"keywords": [
|
||||
|
@ -16,6 +16,7 @@
|
|||
"example": "cross-env PORT=8881 nodemon server/www.js",
|
||||
"server": "cross-env NODE_ENV=prd_dev pm2 start server/pm2.conf.json",
|
||||
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js",
|
||||
"build:dev": "cross-env NODE_ENV=development webpack --config build/webpack.dev.js",
|
||||
"build:un-min": "cross-env NODE_ENV=production webpack --config build/webpack.un-min.prod.js",
|
||||
"build:analyzer": "cross-env NODE_ENV=production_analyzer webpack --config build/webpack.prod.js",
|
||||
"lint": "eslint '{src,test,cypress,build}/**/*.{js,ts}'",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"@babel/preset-env": "^7.11.5",
|
||||
"@cypress/code-coverage": "^3.8.3",
|
||||
"@release-it/conventional-changelog": "^2.0.0",
|
||||
"@testing-library/jest-dom": "^5.11.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/jquery": "^3.3.38",
|
||||
"@types/lodash": "^4.14.150",
|
||||
|
@ -59,7 +61,9 @@
|
|||
"clean-webpack-plugin": "^3.0.0",
|
||||
"commitlint": "^11.0.0",
|
||||
"commitlint-config-cz": "^0.13.2",
|
||||
"concat-stream": "^2.0.0",
|
||||
"concurrently": "^5.3.0",
|
||||
"conventional-changelog": "^3.1.24",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"cypress": "^5.5.0",
|
||||
|
@ -86,6 +90,7 @@
|
|||
"nodemon": "^2.0.6",
|
||||
"nyc": "^15.1.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"prepend-file": "^2.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
"release-it": "^14.2.0",
|
||||
"style-loader": "^1.2.1",
|
||||
|
|
|
@ -5,6 +5,24 @@
|
|||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
h1 {
|
||||
font-size: 2em !important;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5em !important;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.17em !important;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1em !important;
|
||||
}
|
||||
h5 {
|
||||
font-size: 0.83em !important;
|
||||
}
|
||||
p {
|
||||
font-size: 1em !important;
|
||||
}
|
||||
/*表情菜单样式*/
|
||||
.eleImg{
|
||||
cursor: pointer;
|
||||
|
|
|
@ -97,7 +97,8 @@
|
|||
box-shadow: 0 2px 8px 0 rgba(0,0,0,.15);
|
||||
border-radius: 4px;
|
||||
padding: 4px 5px 6px;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
// 下箭头
|
||||
.w-e-tooltip-up::after {
|
||||
|
@ -128,4 +129,4 @@
|
|||
color: #ccc;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,19 @@ export type TCatalog = {
|
|||
text: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
* @param alertInfo alert info
|
||||
* @param alertType 错误提示类型
|
||||
* @param debugInfo debug info
|
||||
*/
|
||||
function customAlert(alertInfo: string, alertType: string, debugInfo?: string): void {
|
||||
window.alert(alertInfo)
|
||||
if (debugInfo) {
|
||||
console.error('wangEditor: ' + debugInfo)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
onchangeTimeout: 200,
|
||||
|
||||
|
@ -19,4 +32,5 @@ export default {
|
|||
onblur: EMPTY_FN,
|
||||
|
||||
onCatalogChange: null,
|
||||
customAlert,
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ export default {
|
|||
// 插入图片成功之后的回调函数
|
||||
linkImgCallback: EMPTY_FN,
|
||||
|
||||
// accept
|
||||
uploadImgAccept: ['jpg', 'jpeg', 'png', 'gif', 'bmp'],
|
||||
|
||||
// 服务端地址
|
||||
uploadImgServer: '',
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import imageConfig, { UploadImageHooksType } from './image'
|
|||
import textConfig from './text'
|
||||
import langConfig from './lang'
|
||||
import historyConfig from './history'
|
||||
import videoConfig from './video'
|
||||
|
||||
// 字典类型
|
||||
export type DicType = {
|
||||
|
@ -45,6 +46,7 @@ export type ConfigType = {
|
|||
zIndexFullScreen: number
|
||||
showFullScreen: boolean
|
||||
showLinkImg: boolean
|
||||
uploadImgAccept: string[]
|
||||
uploadImgServer: string
|
||||
uploadImgShowBase64: boolean
|
||||
uploadImgMaxSize: number
|
||||
|
@ -57,7 +59,7 @@ export type ConfigType = {
|
|||
uploadImgTimeout: number
|
||||
withCredentials: boolean
|
||||
customUploadImg: Function | null
|
||||
customAlert: Function | null
|
||||
customAlert: Function
|
||||
|
||||
onCatalogChange: Function | null
|
||||
|
||||
|
@ -70,6 +72,9 @@ export type ConfigType = {
|
|||
historyMaxSize: number
|
||||
|
||||
focus: boolean
|
||||
|
||||
onlineVideoCheck: Function
|
||||
onlineVideoCallback: Function
|
||||
}
|
||||
|
||||
export type Resource = {
|
||||
|
@ -98,6 +103,7 @@ const defaultConfig = Object.assign(
|
|||
textConfig,
|
||||
langConfig,
|
||||
historyConfig,
|
||||
videoConfig,
|
||||
//链接校验的配置函数
|
||||
{
|
||||
linkCheck: function (text: string, link: string): string | boolean {
|
||||
|
|
|
@ -3,6 +3,7 @@ export default {
|
|||
languages: {
|
||||
'zh-CN': {
|
||||
wangEditor: {
|
||||
重置: '重置',
|
||||
插入: '插入',
|
||||
默认: '默认',
|
||||
创建: '创建',
|
||||
|
@ -103,6 +104,7 @@ export default {
|
|||
},
|
||||
en: {
|
||||
wangEditor: {
|
||||
重置: 'reset',
|
||||
插入: 'insert',
|
||||
默认: 'default',
|
||||
创建: 'create',
|
||||
|
|
|
@ -59,6 +59,7 @@ export default {
|
|||
'splitLine',
|
||||
'undo',
|
||||
'redo',
|
||||
'todo',
|
||||
],
|
||||
|
||||
fontNames: [
|
||||
|
|
|
@ -7,6 +7,6 @@ export default {
|
|||
focus: true,
|
||||
height: 300,
|
||||
placeholder: '请输入正文',
|
||||
zIndexFullScreen: 10000,
|
||||
zIndexFullScreen: 10002,
|
||||
showFullScreen: true,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* @description 视频相关的配置
|
||||
* @author hutianhao
|
||||
*/
|
||||
|
||||
import { EMPTY_FN } from '../utils/const'
|
||||
|
||||
export default {
|
||||
// 插入网络视频前的回调函数
|
||||
onlineVideoCheck: (video: string): string | boolean => {
|
||||
return true
|
||||
},
|
||||
|
||||
// 插入网络视频成功之后的回调函数
|
||||
onlineVideoCallback: EMPTY_FN,
|
||||
}
|
|
@ -9,7 +9,7 @@ import { UA, toArray } from '../../../../utils/util'
|
|||
/**
|
||||
* 数据类型
|
||||
*/
|
||||
function compileType(data: string) {
|
||||
export function compileType(data: string) {
|
||||
switch (data) {
|
||||
case 'childList':
|
||||
return 'node'
|
||||
|
@ -23,7 +23,7 @@ function compileType(data: string) {
|
|||
/**
|
||||
* 获取当前的文本内容
|
||||
*/
|
||||
function compileValue(data: MutationRecord) {
|
||||
export function compileValue(data: MutationRecord) {
|
||||
switch (data.type) {
|
||||
case 'attributes':
|
||||
return (data.target as Element).getAttribute(data.attributeName as string) || ''
|
||||
|
@ -37,7 +37,7 @@ function compileValue(data: MutationRecord) {
|
|||
/**
|
||||
* addedNodes/removedNodes
|
||||
*/
|
||||
function complieNodes(data: MutationRecord) {
|
||||
export function complieNodes(data: MutationRecord) {
|
||||
const temp: DiffNodes = {}
|
||||
if (data.addedNodes.length) {
|
||||
temp.add = toArray(data.addedNodes)
|
||||
|
@ -51,7 +51,7 @@ function complieNodes(data: MutationRecord) {
|
|||
/**
|
||||
* addedNodes/removedNodes 的相对位置
|
||||
*/
|
||||
function compliePosition(data: MutationRecord) {
|
||||
export function compliePosition(data: MutationRecord) {
|
||||
let temp: TargetPosition
|
||||
if (data.previousSibling) {
|
||||
temp = {
|
||||
|
|
|
@ -240,14 +240,33 @@ class SelectionAndRange {
|
|||
}
|
||||
|
||||
/**
|
||||
* 移动光标位置
|
||||
* 移动光标位置,默认情况下在尾部
|
||||
* 有一个特殊情况是firefox下的文本节点会自动补充一个br元素,会导致自动换行
|
||||
* 所以默认情况下在firefox下的文本节点会自动移动到br前面
|
||||
* @param {Node} node 元素节点
|
||||
* @param {Boolean} toStart 为true光标在开始位置 为false在结束位置 默认在结束位置
|
||||
* @param {number} position 光标的位置
|
||||
*/
|
||||
public moveCursor(node: Node, toStart: boolean = false) {
|
||||
public moveCursor(node: Node, position?: number) {
|
||||
const range = this.getRange()
|
||||
const pos = toStart ? 0 : node.childNodes.length
|
||||
|
||||
//对文本节点特殊处理
|
||||
let len: number
|
||||
if (node.nodeType === 3) {
|
||||
len = node.nodeValue?.length as number
|
||||
// 在firefox下文本节点下会自带一个br导致的自动换行问题
|
||||
if (UA.isFirefox && len !== 0) {
|
||||
len = len - 1
|
||||
}
|
||||
} else {
|
||||
len = node.childNodes.length
|
||||
// 在firefox下文本节点下会自带一个br导致的自动换行问题
|
||||
if (UA.isFirefox && len !== 0) {
|
||||
if (node.childNodes[len - 1].nodeName === 'BR') {
|
||||
len = len - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果position变量存在取positon,position不存在取默认的len
|
||||
let pos: number = position || position === 0 ? position : len
|
||||
if (!range) {
|
||||
return
|
||||
}
|
||||
|
@ -257,6 +276,15 @@ class SelectionAndRange {
|
|||
this.restoreSelection()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光标在当前选区的位置
|
||||
*/
|
||||
public getCursorPos(): number | undefined {
|
||||
const selection = window.getSelection()
|
||||
|
||||
return selection?.anchorOffset
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectionAndRange
|
||||
|
|
|
@ -8,7 +8,7 @@ import DropListMenu from '../menu-constructors/DropListMenu'
|
|||
import $ from '../../utils/dom-core'
|
||||
import Editor from '../../editor/index'
|
||||
import { MenuActive } from '../menu-constructors/Menu'
|
||||
|
||||
import { hexToRgb } from '../../utils/util'
|
||||
class BackColor extends DropListMenu implements MenuActive {
|
||||
constructor(editor: Editor) {
|
||||
const $elem = $(
|
||||
|
@ -41,7 +41,29 @@ class BackColor extends DropListMenu implements MenuActive {
|
|||
*/
|
||||
public command(value: string): void {
|
||||
const editor = this.editor
|
||||
const isEmptySelection = editor.selection.isSelectionEmpty()
|
||||
const $selectionElem = editor.selection.getSelectionContainerElem()?.elems[0]
|
||||
const isSpan = $selectionElem?.nodeName.toLowerCase() !== 'p'
|
||||
const bgColor = $selectionElem?.style.backgroundColor
|
||||
const isSameColor = hexToRgb(value) === bgColor
|
||||
|
||||
if (isEmptySelection) {
|
||||
if (isSpan && !isSameColor) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
// 插入空白选区
|
||||
editor.selection.createEmptyRange()
|
||||
}
|
||||
|
||||
editor.cmd.do('backColor', value)
|
||||
|
||||
if (isEmptySelection) {
|
||||
// 需要将选区范围折叠起来
|
||||
editor.selection.collapseRange()
|
||||
editor.selection.restoreSelection()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -91,7 +91,7 @@ export default function (editor: Editor, text: string, languageType: string): Pa
|
|||
'"'
|
||||
)}</textarea>
|
||||
<div class="w-e-button-container">
|
||||
<button id="${btnOkId}" class="right">${
|
||||
<button type="button" id="${btnOkId}" class="right">${
|
||||
isActive(editor) ? t('修改') : t('插入')
|
||||
}</button>
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,28 @@ class FontColor extends DropListMenu implements MenuActive {
|
|||
*/
|
||||
public command(value: string): void {
|
||||
const editor = this.editor
|
||||
const isEmptySelection = editor.selection.isSelectionEmpty()
|
||||
const $selectionElem = editor.selection.getSelectionContainerElem()?.elems[0]
|
||||
const isFont = $selectionElem?.nodeName.toLowerCase() !== 'p'
|
||||
const isSameColor = $selectionElem?.getAttribute('color') === value
|
||||
|
||||
if (isEmptySelection) {
|
||||
if (isFont && !isSameColor) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
// 插入空白选区
|
||||
editor.selection.createEmptyRange()
|
||||
}
|
||||
|
||||
editor.cmd.do('foreColor', value)
|
||||
|
||||
if (isEmptySelection) {
|
||||
// 需要将选区范围折叠起来
|
||||
editor.selection.collapseRange()
|
||||
editor.selection.restoreSelection()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,7 +37,29 @@ class FontSize extends DropListMenu implements MenuActive {
|
|||
*/
|
||||
public command(value: string): void {
|
||||
const editor = this.editor
|
||||
const isEmptySelection = editor.selection.isSelectionEmpty()
|
||||
|
||||
const $selectionElem = editor.selection.getSelectionContainerElem()?.elems[0]
|
||||
const isFont = $selectionElem?.nodeName.toLowerCase() !== 'p'
|
||||
const isSameSize = $selectionElem?.getAttribute('size') === value
|
||||
|
||||
if (isEmptySelection) {
|
||||
if (isFont && !isSameSize) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
// 插入空白选区
|
||||
editor.selection.createEmptyRange()
|
||||
}
|
||||
|
||||
editor.cmd.do('fontSize', value)
|
||||
|
||||
if (isEmptySelection) {
|
||||
// 需要将选区范围折叠起来
|
||||
editor.selection.collapseRange()
|
||||
editor.selection.restoreSelection()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,7 +37,29 @@ class FontStyle extends DropListMenu implements MenuActive {
|
|||
*/
|
||||
public command(value: string): void {
|
||||
const editor = this.editor
|
||||
const isEmptySelection = editor.selection.isSelectionEmpty()
|
||||
|
||||
const $selectionElem = editor.selection.getSelectionContainerElem()?.elems[0]
|
||||
const isFont = $selectionElem?.nodeName.toLowerCase() !== 'p'
|
||||
const isSameValue = $selectionElem?.getAttribute('face') === value
|
||||
|
||||
if (isEmptySelection) {
|
||||
if (isFont && !isSameValue) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
// 插入空白选区
|
||||
editor.selection.createEmptyRange()
|
||||
}
|
||||
|
||||
editor.cmd.do('fontName', value)
|
||||
|
||||
if (isEmptySelection) {
|
||||
// 需要将选区范围折叠起来
|
||||
editor.selection.collapseRange()
|
||||
editor.selection.restoreSelection()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,9 @@ import Editor from '../../../editor/index'
|
|||
*/
|
||||
function createShowHideFn(editor: Editor) {
|
||||
let tooltip: Tooltip | null
|
||||
const t = (text: string, prefix: string = ''): string => {
|
||||
return editor.i18next.t(prefix + text)
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示 tooltip
|
||||
|
@ -56,6 +59,16 @@ function createShowHideFn(editor: Editor) {
|
|||
$node.attr('width', '100%')
|
||||
$node.removeAttr('height')
|
||||
|
||||
// 返回 true,表示执行完之后,隐藏 tooltip。否则不隐藏。
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
$elem: $(`<span>${t('重置')}</span>`),
|
||||
onClick: (editor: Editor, $node: DomElement) => {
|
||||
$node.removeAttr('width')
|
||||
$node.removeAttr('height')
|
||||
|
||||
// 返回 true,表示执行完之后,隐藏 tooltip。否则不隐藏。
|
||||
return true
|
||||
},
|
||||
|
|
|
@ -44,22 +44,24 @@ export default function (editor: Editor): PanelConf {
|
|||
} else if (check === true) {
|
||||
//用户通过了开发者的校验
|
||||
if (flag === false) {
|
||||
alert(
|
||||
config.customAlert(
|
||||
`${t('您插入的网络图片无法识别', 'validate.')},${t(
|
||||
'请替换为支持的图片类型',
|
||||
'validate.'
|
||||
)}:jpg | png | gif ...`
|
||||
)}:jpg | png | gif ...`,
|
||||
'warning'
|
||||
)
|
||||
} else return true
|
||||
} else {
|
||||
//用户未能通过开发者的校验,开发者希望我们提示这一字符串
|
||||
alert(check)
|
||||
config.customAlert(check, 'error')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tabs 配置 -----------------------------------------
|
||||
const fileMultipleAttr = config.uploadImgMaxLength === 1 ? '' : 'multiple="multiple"'
|
||||
const accepts: string = config.uploadImgAccept.map((item: string) => `image/${item}`).join(',')
|
||||
const tabsConf: PanelTabConf[] = [
|
||||
// first tab
|
||||
{
|
||||
|
@ -71,7 +73,7 @@ export default function (editor: Editor): PanelConf {
|
|||
<i class="w-e-icon-upload2"></i>
|
||||
</div>
|
||||
<div style="display:none;">
|
||||
<input id="${upFileId}" type="file" ${fileMultipleAttr} accept="image/jpg,image/jpeg,image/png,image/gif,image/bmp"/>
|
||||
<input id="${upFileId}" type="file" ${fileMultipleAttr} accept="${accepts}"/>
|
||||
</div>
|
||||
</div>`,
|
||||
// 事件绑定
|
||||
|
@ -126,7 +128,10 @@ export default function (editor: Editor): PanelConf {
|
|||
placeholder="${t('图片链接')}"/>
|
||||
</td>
|
||||
<div class="w-e-button-container">
|
||||
<button id="${linkBtnId}" class="right">${t('插入', '')}</button>
|
||||
<button type="button" id="${linkBtnId}" class="right">${t(
|
||||
'插入',
|
||||
''
|
||||
)}</button>
|
||||
</div>
|
||||
</div>`,
|
||||
events: [
|
||||
|
|
|
@ -20,24 +20,6 @@ class UploadImg {
|
|||
this.editor = editor
|
||||
}
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
* @param alertInfo alert info
|
||||
* @param debugInfo debug info
|
||||
*/
|
||||
private alert(alertInfo: string, debugInfo?: string): void {
|
||||
const customAlert = this.editor.config.customAlert
|
||||
if (customAlert) {
|
||||
customAlert(alertInfo)
|
||||
} else {
|
||||
window.alert(alertInfo)
|
||||
}
|
||||
|
||||
if (debugInfo) {
|
||||
console.error('wangEditor: ' + debugInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 往编辑区域插入图片
|
||||
* @param src 图片地址
|
||||
|
@ -62,8 +44,9 @@ class UploadImg {
|
|||
img = null
|
||||
}
|
||||
img.onerror = () => {
|
||||
this.alert(
|
||||
config.customAlert(
|
||||
t('插入图片错误'),
|
||||
'error',
|
||||
`wangEditor: ${t('插入图片错误')},${t('图片链接')} "${src}",${t('下载链接失败')}`
|
||||
)
|
||||
img = null
|
||||
|
@ -155,11 +138,11 @@ class UploadImg {
|
|||
})
|
||||
// 抛出验证信息
|
||||
if (errInfos.length) {
|
||||
this.alert(`${t('图片验证未通过')}: \n` + errInfos.join('\n'))
|
||||
config.customAlert(`${t('图片验证未通过')}: \n` + errInfos.join('\n'), 'warning')
|
||||
return
|
||||
}
|
||||
if (resultFiles.length > maxLength) {
|
||||
this.alert(t('一次最多上传') + maxLength + t('张图片'))
|
||||
config.customAlert(t('一次最多上传') + maxLength + t('张图片'), 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -219,7 +202,7 @@ class UploadImg {
|
|||
if (hooks.before) return hooks.before(xhr, editor, resultFiles)
|
||||
},
|
||||
onTimeout: xhr => {
|
||||
this.alert(t('上传图片超时'))
|
||||
config.customAlert(t('上传图片超时'), 'error')
|
||||
if (hooks.timeout) hooks.timeout(xhr, editor)
|
||||
},
|
||||
onProgress: (percent, e) => {
|
||||
|
@ -230,15 +213,17 @@ class UploadImg {
|
|||
}
|
||||
},
|
||||
onError: xhr => {
|
||||
this.alert(
|
||||
config.customAlert(
|
||||
t('上传图片错误'),
|
||||
'error',
|
||||
`${t('上传图片错误')},${t('服务器返回状态')}: ${xhr.status}`
|
||||
)
|
||||
if (hooks.error) hooks.error(xhr, editor)
|
||||
},
|
||||
onFail: (xhr, resultStr) => {
|
||||
this.alert(
|
||||
config.customAlert(
|
||||
t('上传图片失败'),
|
||||
'error',
|
||||
t('上传图片返回结果错误') + `,${t('返回结果')}: ` + resultStr
|
||||
)
|
||||
if (hooks.fail) hooks.fail(xhr, editor, resultStr)
|
||||
|
@ -251,8 +236,9 @@ class UploadImg {
|
|||
}
|
||||
if (result.errno != '0') {
|
||||
// 返回格式不对,应该为 { errno: 0, data: [...] }
|
||||
this.alert(
|
||||
config.customAlert(
|
||||
t('上传图片失败'),
|
||||
'error',
|
||||
`${t('上传图片返回结果错误')},${t('返回结果')} errno=${result.errno}`
|
||||
)
|
||||
if (hooks.fail) hooks.fail(xhr, editor, result)
|
||||
|
@ -271,7 +257,7 @@ class UploadImg {
|
|||
})
|
||||
if (typeof xhr === 'string') {
|
||||
// 上传被阻止
|
||||
this.alert(xhr)
|
||||
config.customAlert(xhr, 'error')
|
||||
}
|
||||
|
||||
// 阻止以下代码执行,重要!!!
|
||||
|
|
|
@ -79,7 +79,7 @@ export default function (editor: Editor, text: string, link: string): PanelConf
|
|||
return true
|
||||
} else {
|
||||
//用户未能通过开发者的校验,开发者希望我们提示这一字符串
|
||||
alert(check)
|
||||
editor.config.customAlert(check, 'warning')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -95,25 +95,25 @@ export default function (editor: Editor, text: string, link: string): PanelConf
|
|||
title: editor.i18next.t('menus.panelMenus.link.链接'),
|
||||
// 模板
|
||||
tpl: `<div>
|
||||
<input
|
||||
id="${inputTextId}"
|
||||
type="text"
|
||||
class="block"
|
||||
value="${text}"
|
||||
<input
|
||||
id="${inputTextId}"
|
||||
type="text"
|
||||
class="block"
|
||||
value="${text}"
|
||||
placeholder="${editor.i18next.t('menus.panelMenus.link.链接文字')}"/>
|
||||
</td>
|
||||
<input
|
||||
id="${inputLinkId}"
|
||||
type="text"
|
||||
class="block"
|
||||
value="${link}"
|
||||
<input
|
||||
id="${inputLinkId}"
|
||||
type="text"
|
||||
class="block"
|
||||
value="${link}"
|
||||
placeholder="${editor.i18next.t('如')} https://..."/>
|
||||
</td>
|
||||
<div class="w-e-button-container">
|
||||
<button id="${btnOkId}" class="right">
|
||||
<button type="button" id="${btnOkId}" class="right">
|
||||
${editor.i18next.t('插入')}
|
||||
</button>
|
||||
<button id="${btnDelId}" class="gray right" style="display:${delBtnDisplay}">
|
||||
<button type="button" id="${btnDelId}" class="gray right" style="display:${delBtnDisplay}">
|
||||
${editor.i18next.t('menus.panelMenus.link.取消链接')}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -26,6 +26,7 @@ import Redo from './redo/index'
|
|||
import Table from './table/index'
|
||||
import Code from './code'
|
||||
import SplitLine from './split-line/index'
|
||||
import Todo from './todo'
|
||||
|
||||
export type MenuListType = {
|
||||
[key: string]: any
|
||||
|
@ -55,4 +56,5 @@ export default {
|
|||
table: Table,
|
||||
code: Code,
|
||||
splitLine: SplitLine,
|
||||
todo: Todo,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ function bindEvent(editor: Editor) {
|
|||
const $newLine = $('<p><br></p>')
|
||||
$newLine.insertAfter($topSelectElem)
|
||||
// 将光标移动br前面
|
||||
editor.selection.moveCursor($newLine.getNode(), true)
|
||||
editor.selection.moveCursor($newLine.getNode(), 0)
|
||||
}
|
||||
|
||||
// 当blockQuote中没有内容回车后移除blockquote
|
||||
|
|
|
@ -55,7 +55,7 @@ class Quote extends BtnMenu implements MenuActive {
|
|||
// 兼容firefox(firefox下空行情况下选区会在br后,造成自动换行的问题)
|
||||
moveNode.textContent
|
||||
? editor.selection.moveCursor(moveNode)
|
||||
: editor.selection.moveCursor(moveNode, true)
|
||||
: editor.selection.moveCursor(moveNode, 0)
|
||||
// 即时更新btn状态
|
||||
this.tryChangeActive()
|
||||
// 防止最后一行无法跳出
|
||||
|
|
|
@ -38,7 +38,9 @@ export default function (editor: Editor): PanelConf {
|
|||
}</span>
|
||||
</div>
|
||||
<div class="w-e-button-container">
|
||||
<button id="${insertBtnId}" class="right">${t('插入')}</button>
|
||||
<button type="button" id="${insertBtnId}" class="right">${t(
|
||||
'插入'
|
||||
)}</button>
|
||||
</div>
|
||||
</div>`,
|
||||
events: [
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import Editor from '../../../editor/index'
|
||||
import $ from '../../../utils/dom-core'
|
||||
import { getCursorNextNode, isAllTodo } from '../util'
|
||||
import createTodo from '../todo'
|
||||
|
||||
/**
|
||||
* todolist 内部逻辑
|
||||
* @param editor
|
||||
*/
|
||||
function bindEvent(editor: Editor) {
|
||||
/**
|
||||
* todo的自定义回车事件
|
||||
* @param e 事件属性
|
||||
*/
|
||||
function todoEnter(e: Event) {
|
||||
// 判断是否为todo节点
|
||||
if (isAllTodo(editor)) {
|
||||
e.preventDefault()
|
||||
const selection = editor.selection
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes(editor)[0]
|
||||
const $li = $topSelectElem.childNodes()?.get(0)
|
||||
const selectionNode = window.getSelection()?.anchorNode as Node
|
||||
|
||||
// 回车时内容为空时,删去此行
|
||||
if ($topSelectElem.text() === '') {
|
||||
const $p = $(`<p><br></p>`)
|
||||
$p.insertAfter($topSelectElem)
|
||||
selection.moveCursor($p.getNode())
|
||||
$topSelectElem.remove()
|
||||
return
|
||||
}
|
||||
|
||||
const pos = selection.getCursorPos() as number
|
||||
const CursorNextNode = getCursorNextNode($li?.getNode() as Node, selectionNode, pos)
|
||||
const todo = createTodo($(CursorNextNode))
|
||||
const $inputcontainer = todo.getInputContainer()
|
||||
const todoLiElem = $inputcontainer.parent().getNode()
|
||||
const $newTodo = todo.getTodo()
|
||||
const contentSection = $inputcontainer.getNode().nextSibling
|
||||
// 处理光标在最前面时回车input不显示的问题
|
||||
if ($li?.text() === '') {
|
||||
$li?.append($(`<br>`))
|
||||
}
|
||||
$newTodo.insertAfter($topSelectElem)
|
||||
// 处理在google中光标在最后面的,input不显示的问题(必须插入之后移动光标)
|
||||
if (!contentSection || contentSection?.textContent === '') {
|
||||
// 防止多个br出现的情况
|
||||
if (contentSection?.nodeName !== 'BR') {
|
||||
const $br = $(`<br>`)
|
||||
$br.insertAfter($inputcontainer)
|
||||
}
|
||||
selection.moveCursor(todoLiElem, 1)
|
||||
} else {
|
||||
selection.moveCursor(todoLiElem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义删除事件,用来处理光标在最前面删除input产生的问题
|
||||
*/
|
||||
function delDown(e: Event) {
|
||||
if (isAllTodo(editor)) {
|
||||
const selection = editor.selection
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes(editor)[0]
|
||||
const $li = $topSelectElem.childNodes()?.getNode()
|
||||
const $p = $(`<p></p>`)
|
||||
const p = $p.getNode()
|
||||
const selectionNode = window.getSelection()?.anchorNode as Node
|
||||
const pos = selection.getCursorPos()
|
||||
const prevNode = selectionNode.previousSibling
|
||||
|
||||
// 处理内容为空的情况
|
||||
if ($topSelectElem.text() === '') {
|
||||
e.preventDefault()
|
||||
const $newP = $(`<p><br></p>`)
|
||||
$newP.insertAfter($topSelectElem)
|
||||
$topSelectElem.remove()
|
||||
selection.moveCursor($newP.getNode(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理有内容时,光标在最前面的情况
|
||||
if (
|
||||
prevNode?.nodeName === 'SPAN' &&
|
||||
prevNode.childNodes[0].nodeName === 'INPUT' &&
|
||||
pos === 0
|
||||
) {
|
||||
e.preventDefault()
|
||||
$li?.childNodes.forEach((v, index) => {
|
||||
if (index === 0) return
|
||||
p.appendChild(v.cloneNode(true))
|
||||
})
|
||||
$p.insertAfter($topSelectElem)
|
||||
|
||||
$topSelectElem.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
editor.txt.eventHooks.enterDownEvents.push(todoEnter)
|
||||
editor.txt.eventHooks.deleteDownEvents.push(delDown)
|
||||
}
|
||||
|
||||
export default bindEvent
|
|
@ -0,0 +1,86 @@
|
|||
import $, { DomElement } from '../../utils/dom-core'
|
||||
import BtnMenu from '../menu-constructors/BtnMenu'
|
||||
import Editor from '../../editor/index'
|
||||
import { MenuActive } from '../menu-constructors/Menu'
|
||||
import { isAllTodo } from './util'
|
||||
import bindEvent from './bind-event'
|
||||
import createTodo from './todo'
|
||||
|
||||
class Todo extends BtnMenu implements MenuActive {
|
||||
constructor(editor: Editor) {
|
||||
const $elem = $(
|
||||
`<div class="w-e-menu">
|
||||
<i class="w-e-icon-checkbox-checked"></i>
|
||||
</div>`
|
||||
)
|
||||
super($elem, editor)
|
||||
bindEvent(editor)
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击事件
|
||||
*/
|
||||
public clickHandler(): void {
|
||||
const editor = this.editor
|
||||
if (!isAllTodo(editor)) {
|
||||
// 设置todolist
|
||||
this.setTodo()
|
||||
} else {
|
||||
// 取消设置todolist
|
||||
this.cancelTodo()
|
||||
this.tryChangeActive()
|
||||
}
|
||||
}
|
||||
tryChangeActive() {
|
||||
if (isAllTodo(this.editor)) {
|
||||
this.active()
|
||||
} else {
|
||||
this.unActive()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置todo
|
||||
*/
|
||||
private setTodo() {
|
||||
const editor = this.editor
|
||||
const topNodeElem: DomElement[] = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
// 增加垫片防止无法删除的情况
|
||||
if (topNodeElem[0].prev().length === 0) {
|
||||
$(`<p style="height:0px;"><br></p>`).insertBefore(topNodeElem[0])
|
||||
}
|
||||
topNodeElem.forEach($node => {
|
||||
const nodeName = $node?.getNodeName()
|
||||
if (nodeName === 'P') {
|
||||
const todo = createTodo($node)
|
||||
const todoNode = todo.getTodo()
|
||||
const child = todoNode.children()?.getNode() as Node
|
||||
todoNode.insertAfter($node)
|
||||
editor.selection.moveCursor(child)
|
||||
$node.remove()
|
||||
}
|
||||
})
|
||||
this.tryChangeActive()
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消设置todo
|
||||
*/
|
||||
private cancelTodo() {
|
||||
const editor = this.editor
|
||||
const $topNodeElems: DomElement[] = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
|
||||
$topNodeElems.forEach($topNodeElem => {
|
||||
let content = $topNodeElem.childNodes()?.childNodes()?.clone(true) as DomElement
|
||||
const $p = $(`<p></p>`)
|
||||
$p.append(content)
|
||||
$p.insertAfter($topNodeElem)
|
||||
// 移除input
|
||||
$p.childNodes()?.get(0).remove()
|
||||
editor.selection.moveCursor($p.getNode())
|
||||
$topNodeElem.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Todo
|
|
@ -0,0 +1,55 @@
|
|||
import $, { DomElement } from '../../utils/dom-core'
|
||||
export class todo {
|
||||
private template: string
|
||||
private checked: boolean
|
||||
private $todo: DomElement
|
||||
private $child: DomElement
|
||||
constructor($orginElem?: DomElement) {
|
||||
this.template = `<ul data-todo-key="w-e-text-todo" style="margin:0 0 0 20px;position:relative;"><li style="list-style:none;"><span style="position: absolute;left: -18px;top: 2px;" contenteditable="false"><input type="checkbox" style="margin-right:3px;"></span></li></ul>`
|
||||
this.checked = false
|
||||
this.$todo = $(this.template)
|
||||
this.$child = $orginElem?.childNodes()?.clone(true) as DomElement
|
||||
}
|
||||
|
||||
public init() {
|
||||
const $input = this.getInput()
|
||||
const $child = this.$child
|
||||
const $inputContainer = this.getInputContainer()
|
||||
|
||||
if ($child) {
|
||||
$child.insertAfter($inputContainer)
|
||||
}
|
||||
|
||||
$input.on('click', () => {
|
||||
if (this.checked) {
|
||||
$input?.removeAttr('checked')
|
||||
} else {
|
||||
$input?.attr('checked', '')
|
||||
}
|
||||
this.checked = !this.checked
|
||||
})
|
||||
}
|
||||
|
||||
public getInput(): DomElement {
|
||||
const $todo = this.$todo
|
||||
const $input = $todo.find('input')
|
||||
return $input
|
||||
}
|
||||
|
||||
public getInputContainer(): DomElement {
|
||||
const $inputContainer = this.getInput().parent()
|
||||
return $inputContainer
|
||||
}
|
||||
|
||||
public getTodo(): DomElement {
|
||||
return this.$todo
|
||||
}
|
||||
}
|
||||
|
||||
function createTodo($orginElem?: DomElement): todo {
|
||||
const t = new todo($orginElem)
|
||||
t.init()
|
||||
return t
|
||||
}
|
||||
|
||||
export default createTodo
|
|
@ -0,0 +1,86 @@
|
|||
import { DomElement } from '../../utils/dom-core'
|
||||
import Editor from '../../editor'
|
||||
|
||||
/**
|
||||
* 判断传入的单行顶级选区选取是不是todo
|
||||
* @param editor 编辑器对象
|
||||
*/
|
||||
function isTodo($topSelectElem: DomElement) {
|
||||
return $topSelectElem.attr('data-todo-key') === 'w-e-text-todo'
|
||||
}
|
||||
/**
|
||||
* 判断选中的内容是不是都是todo
|
||||
* @param editor 编辑器对象
|
||||
*/
|
||||
function isAllTodo(editor: Editor): boolean | undefined {
|
||||
const $topSelectElems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
// 排除为[]的情况
|
||||
if ($topSelectElems.length === 0) return
|
||||
|
||||
return $topSelectElems.every($topSelectElem => {
|
||||
return isTodo($topSelectElem)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据所在的文本节点和光标在文本节点的位置获取截断的后节点内容
|
||||
* @param node 顶级节点
|
||||
* @param textNode 光标所在的文本节点
|
||||
* @param pos 光标在文本节点的位置
|
||||
*/
|
||||
function getCursorNextNode(node: Node, textNode: Node, pos: number): Node | undefined {
|
||||
if (!node.hasChildNodes()) return
|
||||
|
||||
const newNode = node.cloneNode() as ChildNode
|
||||
let end = false
|
||||
if (textNode.nodeValue === '') {
|
||||
end = true
|
||||
}
|
||||
|
||||
let delArr: Node[] = []
|
||||
node.childNodes.forEach(v => {
|
||||
//选中后
|
||||
if (!v.contains(textNode) && end) {
|
||||
newNode.appendChild(v.cloneNode(true))
|
||||
delArr.push(v)
|
||||
}
|
||||
// 选中
|
||||
if (v.contains(textNode)) {
|
||||
if (v.nodeType === 1) {
|
||||
const childNode = getCursorNextNode(v, textNode, pos) as Node
|
||||
if (childNode && childNode.textContent !== '') newNode?.appendChild(childNode)
|
||||
}
|
||||
if (v.nodeType === 3) {
|
||||
if (textNode.isEqualNode(v)) {
|
||||
const textContent = dealTextNode(v, pos)
|
||||
newNode.textContent = textContent
|
||||
} else {
|
||||
newNode.textContent = v.nodeValue
|
||||
}
|
||||
}
|
||||
end = true
|
||||
}
|
||||
})
|
||||
// 删除选中后原来的节点
|
||||
delArr.forEach(v => {
|
||||
const node = v as ChildNode
|
||||
node.remove()
|
||||
})
|
||||
|
||||
return newNode
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新的文本节点
|
||||
* @param node 要处理的文本节点
|
||||
* @param pos 光标在文本节点所在的位置
|
||||
*/
|
||||
function dealTextNode(node: Node, pos: number) {
|
||||
let content = node.nodeValue
|
||||
let oldContent = content?.slice(0, pos) as string
|
||||
content = content?.slice(pos) as string
|
||||
node.nodeValue = oldContent
|
||||
return content
|
||||
}
|
||||
|
||||
export { getCursorNextNode, isTodo, isAllTodo }
|
|
@ -7,11 +7,16 @@ import Editor from '../../editor/index'
|
|||
import { PanelConf } from '../menu-constructors/Panel'
|
||||
import { getRandom } from '../../utils/util'
|
||||
import $ from '../../utils/dom-core'
|
||||
import { videoRegex } from '../../utils/const'
|
||||
|
||||
export default function (editor: Editor, video: string): PanelConf {
|
||||
// panel 中需要用到的id
|
||||
const inputIFrameId = getRandom('input-iframe')
|
||||
const btnOkId = getRandom('btn-ok')
|
||||
const i18nPrefix = 'menus.panelMenus.video.'
|
||||
const t = (text: string, prefix: string = i18nPrefix): string => {
|
||||
return editor.i18next.t(prefix + text)
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入链接
|
||||
|
@ -19,6 +24,44 @@ export default function (editor: Editor, video: string): PanelConf {
|
|||
*/
|
||||
function insertVideo(video: string): void {
|
||||
editor.cmd.do('insertHTML', video + '<p><br></p>')
|
||||
|
||||
// video添加后的回调
|
||||
editor.config.onlineVideoCallback(video)
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验在线视频链接
|
||||
* @param video 在线视频链接
|
||||
*/
|
||||
function checkOnlineVideo(video: string): boolean {
|
||||
// 编辑器进行正常校验,video 合规则使指针为true,不合规为false
|
||||
let flag = true
|
||||
if (!videoRegex.test(video)) {
|
||||
flag = false
|
||||
}
|
||||
|
||||
// 查看开发者自定义配置的返回值
|
||||
const check = editor.config.onlineVideoCheck(video)
|
||||
if (check === undefined) {
|
||||
if (flag === false) console.log(t('您刚才插入的视频链接未通过编辑器校验', 'validate.'))
|
||||
} else if (check === true) {
|
||||
// 用户通过了开发者的校验
|
||||
if (flag === false) {
|
||||
editor.config.customAlert(
|
||||
`${t('您插入的网络视频无法识别', 'validate.')},${t(
|
||||
'请替换为正确的网络视频格式',
|
||||
'validate.'
|
||||
)}:如<iframe src=...></iframe>`,
|
||||
'warning'
|
||||
)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
//用户未能通过开发者的校验,开发者希望我们提示这一字符串
|
||||
editor.config.customAlert(check, 'error')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const conf = {
|
||||
|
@ -39,7 +82,7 @@ export default function (editor: Editor, video: string): PanelConf {
|
|||
placeholder="${editor.i18next.t('如')}:<iframe src=... ></iframe>"/>
|
||||
</td>
|
||||
<div class="w-e-button-container">
|
||||
<button id="${btnOkId}" class="right">
|
||||
<button type="button" id="${btnOkId}" class="right">
|
||||
${editor.i18next.t('插入')}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -57,6 +100,8 @@ export default function (editor: Editor, video: string): PanelConf {
|
|||
|
||||
// 视频为空,则不插入
|
||||
if (!video) return
|
||||
// 对当前用户插入的内容进行判断,插入为空,或者返回false,都停止插入
|
||||
if (!checkOnlineVideo(video)) return
|
||||
|
||||
insertVideo(video)
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ function deleteToKeepP(editor: Editor, deleteUpEvents: Function[], deleteDownEve
|
|||
$textElem.append($p)
|
||||
editor.selection.createRangeByElem($p, false, true)
|
||||
editor.selection.restoreSelection()
|
||||
// 设置折叠后的光标位置,在firebox等浏览器下
|
||||
// 光标设置在end位置会自动换行
|
||||
editor.selection.moveCursor($p.getNode(), 0)
|
||||
}
|
||||
}
|
||||
deleteUpEvents.push(upFn)
|
||||
|
|
|
@ -6,36 +6,37 @@
|
|||
import $, { DomElement } from './../utils/dom-core'
|
||||
import { NodeListType } from './getChildrenJSON'
|
||||
|
||||
function getHtmlByNodeList(nodeList: NodeListType): DomElement {
|
||||
function getHtmlByNodeList(
|
||||
nodeList: NodeListType,
|
||||
parent: Node = document.createElement('div')
|
||||
): DomElement {
|
||||
// 设置一个父节点存储所有子节点
|
||||
let $root = $(`<div></div>`)
|
||||
let root = parent
|
||||
|
||||
// 遍历节点JSON
|
||||
nodeList.forEach(item => {
|
||||
let $elem: DomElement = $('')
|
||||
let elem: Text | Node | undefined
|
||||
|
||||
// 当为文本节点时
|
||||
if (typeof item === 'string') {
|
||||
$elem = $(`<span>${item}</span>`)
|
||||
elem = document.createTextNode(item)
|
||||
}
|
||||
|
||||
// 当为普通节点时
|
||||
if (typeof item === 'object') {
|
||||
$elem = $(`<${item.tag}></${item.tag}>`)
|
||||
elem = document.createElement(item.tag)
|
||||
item.attrs.forEach(attr => {
|
||||
$elem.attr(attr.name, attr.value)
|
||||
$(elem).attr(attr.name, attr.value)
|
||||
})
|
||||
|
||||
// 有子节点时递归将子节点加入当前节点
|
||||
if (item.children && item.children.length > 0) {
|
||||
const $elemChilds = getHtmlByNodeList(item.children).children()
|
||||
$elemChilds && $elem.append($elemChilds)
|
||||
getHtmlByNodeList(item.children, elem.getRootNode())
|
||||
}
|
||||
}
|
||||
|
||||
$root.append($elem)
|
||||
elem && root.appendChild(elem)
|
||||
})
|
||||
return $root
|
||||
return $(root)
|
||||
}
|
||||
|
||||
export default getHtmlByNodeList
|
||||
|
|
|
@ -528,7 +528,7 @@ class Text {
|
|||
const target = e.target as HTMLElement
|
||||
|
||||
//获取最祖父元素
|
||||
$dom = $(target).parentUntil('TABLE', target)
|
||||
$dom = $(target).parentUntilEditor('TABLE', editor, target)
|
||||
|
||||
// 没有table范围内,则返回
|
||||
if (!$dom) return
|
||||
|
|
|
@ -146,11 +146,13 @@ function parseHtml(html: string, filterStyle: boolean = true, ignoreImg: boolean
|
|||
resultArr.push(html)
|
||||
},
|
||||
characters(str: string) {
|
||||
str = str.trim()
|
||||
if (!str) return
|
||||
if (!str) {
|
||||
return
|
||||
}
|
||||
|
||||
// 忽略的标签
|
||||
if (isIgnoreTag(CUR_TAG, ignoreImg)) {
|
||||
// 如果复制拿到的内容是 `<body><html>这种形式无法成功粘贴</html></body>`
|
||||
if (isIgnoreTag(CUR_TAG, ignoreImg) && /^</.test(str)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -9,3 +9,6 @@ export const imgRegex = /\.(gif|jpg|jpeg|png)$/i
|
|||
|
||||
//用于校验是否为url格式字符串
|
||||
export const urlRegex = /^(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-.,@?^=%&:/~+#]*[\w\-@?^=%&/~+#])?/
|
||||
|
||||
//用于校验在线视频是否符合规范
|
||||
export const videoRegex = /<iframe(([\s\S])*?)<\/iframe>/
|
||||
|
|
|
@ -573,8 +573,19 @@ export class DomElement<T extends DomElementSelector = DomElementSelector> {
|
|||
/**
|
||||
* 获取当前元素节点
|
||||
*/
|
||||
getNode(): Node {
|
||||
const elem = this.elems[0]
|
||||
/**
|
||||
* 根据元素位置获取元素节点(默认获取0位置的节点)
|
||||
* @param n 元素节点位置
|
||||
*/
|
||||
getNode(n?: number): Node {
|
||||
let elem: Node
|
||||
|
||||
if (n) {
|
||||
elem = this.elems[n]
|
||||
} else {
|
||||
elem = this.elems[0]
|
||||
}
|
||||
|
||||
return elem
|
||||
}
|
||||
|
||||
|
@ -683,7 +694,7 @@ export class DomElement<T extends DomElementSelector = DomElementSelector> {
|
|||
}
|
||||
|
||||
/**
|
||||
* 查找父元素,知道满足 selector 条件
|
||||
* 查找父元素,直到满足 selector 条件
|
||||
* @param selector css 选择器
|
||||
* @param curElem 从哪个元素开始查找,默认为当前元素
|
||||
*/
|
||||
|
@ -707,6 +718,31 @@ export class DomElement<T extends DomElementSelector = DomElementSelector> {
|
|||
return this.parentUntil(selector, parent)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找父元素,直到满足 selector 条件,或者 到达 编辑区域容器以及菜单栏容器
|
||||
* @param selector css 选择器
|
||||
* @param curElem 从哪个元素开始查找,默认为当前元素
|
||||
*/
|
||||
parentUntilEditor(selector: string, editor: Editor, curElem?: HTMLElement): DomElement | null {
|
||||
const elem = curElem || this.elems[0]
|
||||
if ($(elem).equal(editor.$textContainerElem) || $(elem).equal(editor.$toolbarElem)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const parent = elem.parentElement
|
||||
if (parent === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (parent.matches(selector)) {
|
||||
// 找到,并返回
|
||||
return $(parent)
|
||||
}
|
||||
|
||||
// 继续查找,递归
|
||||
return this.parentUntilEditor(selector, editor, parent)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判读是否相等
|
||||
* @param $elem 元素
|
||||
|
@ -738,7 +774,7 @@ export class DomElement<T extends DomElementSelector = DomElementSelector> {
|
|||
}
|
||||
|
||||
/**
|
||||
* 将该元素插入到某个元素后面
|
||||
* 将该元素插入到selector元素后面
|
||||
* @param selector css 选择器
|
||||
*/
|
||||
insertAfter(selector: string | DomElement): DomElement {
|
||||
|
|
|
@ -220,3 +220,20 @@ export function toArray<T>(data: T) {
|
|||
export function getRandomCode() {
|
||||
return Math.random().toString(36).slice(-5)
|
||||
}
|
||||
|
||||
/**
|
||||
* hex color 转换成 rgb
|
||||
* @param hex string
|
||||
*/
|
||||
export function hexToRgb(hex: string) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
|
||||
if (result == null) return null
|
||||
|
||||
const colors = result.map(i => parseInt(i, 16))
|
||||
const r = colors[1]
|
||||
const g = colors[2]
|
||||
const b = colors[3]
|
||||
|
||||
return `rgb(${r}, ${g}, ${b})`
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* @description video 菜单 test
|
||||
* @author 童汉
|
||||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import Video from '../../src/menus/video/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Panel from '../../src/menus/menu-constructors/Panel'
|
||||
|
||||
let editor: Editor
|
||||
let videoMenu: Video
|
||||
|
||||
test('video 菜单:点击弹出 panel', () => {
|
||||
editor = createEditor(document, 'div1')
|
||||
videoMenu = getMenuInstance(editor, Video) as Video
|
||||
videoMenu.clickHandler()
|
||||
expect(videoMenu.panel).not.toBeNull()
|
||||
})
|
||||
|
||||
test('video 菜单:插入', () => {
|
||||
const panel = videoMenu.panel as Panel
|
||||
const panelElem = panel.$container.elems[0]
|
||||
const $panelElem = $(panelElem) // jquery 对象
|
||||
|
||||
// panel 里的 input 和 button 元素
|
||||
const $btnInsert = $panelElem.find(":button[id^='btn-ok']") // id 以 'btn-ok' 的 button
|
||||
const $videoIFrame = $panelElem.find(":input[id^='input-iframe']")
|
||||
|
||||
// 插入链接
|
||||
mockCmdFn(document)
|
||||
const video =
|
||||
'<iframe height="498" width="510" src="http://player.youku.com/embed/XMjcwMzc3MzM3Mg==" frameborder="0"></iframe>'
|
||||
$videoIFrame.val(video)
|
||||
$btnInsert.click()
|
||||
|
||||
// 此处触发 editor.cmd.do('insertHTML', xx),可以被 jest 成功执行,具体参考 mockCmdFn 的描述
|
||||
expect(editor.$textElem.html().indexOf(video)).toBeGreaterThan(0)
|
||||
})
|
|
@ -0,0 +1 @@
|
|||
import '@testing-library/jest-dom'
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* @description 自定义alert test
|
||||
* @author raosiling
|
||||
*/
|
||||
|
||||
import events from '../../../src/config/events'
|
||||
|
||||
test('customAlert 事件', () => {
|
||||
window.alert = jest.fn()
|
||||
events.customAlert('customAlert', 'success')
|
||||
expect(window.alert).toHaveBeenCalledTimes(1)
|
||||
})
|
|
@ -3,7 +3,7 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
|
||||
test('styleWithCSS 测试', () => {
|
||||
const editor = createEditor(document, 'div1')
|
|
@ -3,7 +3,7 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
|
||||
test('菜单数量', () => {
|
||||
const editor = createEditor(document, 'div1')
|
|
@ -3,7 +3,7 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import $ from 'jquery'
|
||||
|
||||
test('onblur 事件', () => {
|
|
@ -3,8 +3,8 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
|
||||
test.only('onchange 事件', done => {
|
||||
mockCmdFn(document)
|
|
@ -3,7 +3,7 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import $ from 'jquery'
|
||||
|
||||
test('onfocus 事件', () => {
|
|
@ -3,7 +3,7 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
|
||||
test('z-index 测试', () => {
|
||||
const editor = createEditor(document, 'div1')
|
|
@ -3,8 +3,8 @@
|
|||
* @author tonghan
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import $ from '../../src/utils/dom-core'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
|
||||
const TEXT = '我是一行文字'
|
||||
const $SPAN = $(`<span>${TEXT}</span>`)
|
|
@ -3,10 +3,10 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import Editor from '../../src/editor'
|
||||
import $ from '../../src/utils/dom-core'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import Editor from '../../../src/editor'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
|
||||
let editor: Editor
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
|
||||
test('创建一个编辑器实例', () => {
|
||||
const editor = createEditor(document, 'div1')
|
|
@ -0,0 +1,52 @@
|
|||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/editor/index'
|
||||
import disableInit from '../../../src/editor/disable'
|
||||
|
||||
let editor: Editor
|
||||
|
||||
describe('Editor disable', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, 'div1')
|
||||
})
|
||||
|
||||
test('编辑器可以被禁用', () => {
|
||||
const disabledObj = disableInit(editor)
|
||||
|
||||
disabledObj.disable()
|
||||
|
||||
expect(editor.$textElem.elems[0].style.display).toBe('none')
|
||||
})
|
||||
|
||||
test('编辑器禁用后通过js修改内容,change hook监听会触发', done => {
|
||||
expect.assertions(1)
|
||||
|
||||
const changeFn = jest.fn()
|
||||
|
||||
editor.disable()
|
||||
|
||||
editor.txt.eventHooks.changeEvents.push(changeFn)
|
||||
|
||||
editor.txt.html(`<span>123</span>`)
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
expect(changeFn).toBeCalled()
|
||||
done()
|
||||
} catch (err) {
|
||||
done.fail(err)
|
||||
}
|
||||
}, 500)
|
||||
})
|
||||
|
||||
test('编辑器禁用后可以取消禁用', () => {
|
||||
const disabledObj = disableInit(editor)
|
||||
|
||||
disabledObj.disable()
|
||||
|
||||
expect(editor.$textElem.elems[0].style.display).toBe('none')
|
||||
|
||||
disabledObj.enable()
|
||||
|
||||
expect(editor.$textElem.elems[0].style.display).toBe('block')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,52 @@
|
|||
import createEditor from '../../../helpers/create-editor'
|
||||
import Editor from '../../../../src/editor'
|
||||
import compile, {
|
||||
compileType,
|
||||
compileValue,
|
||||
complieNodes,
|
||||
compliePosition,
|
||||
} from '../../../../src/editor/history/data/node/compile'
|
||||
import { Compile } from '../../../../src/editor/history/data/type'
|
||||
|
||||
let editor: Editor
|
||||
|
||||
function generateCompileData(mutationList: MutationRecord[]) {
|
||||
let mockData: Compile[] = []
|
||||
mutationList.forEach(record => {
|
||||
const item: Compile = {
|
||||
type: compileType(record.type),
|
||||
target: record.target,
|
||||
attr: record.attributeName || '',
|
||||
value: compileValue(record) || '',
|
||||
oldValue: record.oldValue || '',
|
||||
nodes: complieNodes(record),
|
||||
position: compliePosition(record),
|
||||
}
|
||||
mockData.push(item)
|
||||
})
|
||||
return mockData
|
||||
}
|
||||
|
||||
describe('Editor history compile', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, 'div1')
|
||||
})
|
||||
|
||||
test('可以将MutationRecord生成Compile数据', done => {
|
||||
expect.assertions(3)
|
||||
|
||||
const observer = new MutationObserver((mutationList: MutationRecord[]) => {
|
||||
const compileData = compile(mutationList)
|
||||
const mockData = generateCompileData(mutationList)
|
||||
expect(compileData instanceof Array).toBeTruthy()
|
||||
expect(compileData.length).toBe(1)
|
||||
expect(compileData).toEqual(mockData)
|
||||
done()
|
||||
})
|
||||
|
||||
const $textEl = editor.$textElem.elems[0]
|
||||
observer.observe($textEl, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
editor.txt.html('<span>123</span>')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,62 @@
|
|||
import createEditor from '../../../helpers/create-editor'
|
||||
import Editor from '../../../../src/editor'
|
||||
import compile from '../../../../src/editor/history/data/node/compile'
|
||||
import { restore, revoke } from '../../../../src/editor/history/data/node/decompilation'
|
||||
|
||||
let editor: Editor
|
||||
|
||||
describe('Editor history decompile', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, 'div1')
|
||||
})
|
||||
|
||||
test('可以通过revoke方法撤销编辑器设置的内容', done => {
|
||||
expect.assertions(3)
|
||||
|
||||
const observer = new MutationObserver((mutationList: MutationRecord[]) => {
|
||||
const compileData = compile(mutationList)
|
||||
|
||||
expect(compileData instanceof Array).toBeTruthy()
|
||||
expect(compileData.length).toBe(1)
|
||||
|
||||
observer.disconnect()
|
||||
|
||||
revoke(compileData)
|
||||
|
||||
expect(editor.$textElem.html()).toEqual('<p><br></p>')
|
||||
done()
|
||||
})
|
||||
|
||||
const $textEl = editor.$textElem.elems[0]
|
||||
observer.observe($textEl, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
editor.txt.html('<span>123</span>')
|
||||
})
|
||||
|
||||
test('可以通过restore方法恢复撤销的内容', done => {
|
||||
expect.assertions(2)
|
||||
|
||||
const testHtml = '<span>123</span>'
|
||||
|
||||
const observer = new MutationObserver((mutationList: MutationRecord[]) => {
|
||||
const compileData = compile(mutationList)
|
||||
|
||||
expect(compileData instanceof Array).toBeTruthy()
|
||||
|
||||
observer.disconnect()
|
||||
|
||||
revoke(compileData)
|
||||
|
||||
restore(compileData)
|
||||
|
||||
expect(editor.$textElem.elems[0]).toContainHTML(testHtml)
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
const $textEl = editor.$textElem.elems[0]
|
||||
observer.observe($textEl, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
editor.txt.html(testHtml)
|
||||
})
|
||||
})
|
|
@ -4,9 +4,9 @@
|
|||
*/
|
||||
|
||||
import i18next from 'i18next'
|
||||
import i18nextInit from '../../src/editor/init-fns/i18next-init'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import Editor from '../../src/editor'
|
||||
import i18nextInit from '../../../src/editor/init-fns/i18next-init'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/editor'
|
||||
|
||||
let editor: Editor
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import Selection from '../../src/editor/selection'
|
||||
import $, { DomElement } from '../../src/utils/dom-core'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import Selection from '../../../src/editor/selection'
|
||||
import $, { DomElement } from '../../../src/utils/dom-core'
|
||||
|
||||
const TEXT = '我是一行文字'
|
||||
const $SPAN = $(`<span>${TEXT}</span>`)
|
|
@ -0,0 +1,67 @@
|
|||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/editor/index'
|
||||
import { setFullScreen, setUnFullScreen } from '../../../src/editor/init-fns/set-full-screen'
|
||||
import $ from 'jquery'
|
||||
|
||||
let editor: Editor
|
||||
const FULLSCREEN_MENU_CLASS_SELECTOR = '.w-e-icon-fullscreen'
|
||||
const EDIT_CONTAINER_FULLSCREEN_CLASS = 'w-e-full-screen-editor'
|
||||
|
||||
describe('设置全屏', () => {
|
||||
beforeEach(() => {
|
||||
editor = createEditor(document, 'div1')
|
||||
})
|
||||
|
||||
test('编辑器默认初始化全屏菜单', () => {
|
||||
const toolbarSelector = editor.$toolbarElem.elems[0].className
|
||||
const fullMenuEl = $(`.${toolbarSelector}`).find(FULLSCREEN_MENU_CLASS_SELECTOR)
|
||||
|
||||
expect(fullMenuEl.length).toBe(1)
|
||||
})
|
||||
|
||||
test('编辑器区和菜单分离的编辑器不初始化全屏菜单', () => {
|
||||
const seprateModeEditor = createEditor(document, 'div1', 'div2')
|
||||
const toolbarSelector = seprateModeEditor.$toolbarElem.selector as string
|
||||
const fullMenuEl = $(toolbarSelector).find(FULLSCREEN_MENU_CLASS_SELECTOR)
|
||||
|
||||
expect(fullMenuEl.length).toBe(0)
|
||||
})
|
||||
|
||||
test('编辑器配置 showFullScreen 为false时不初始化全屏菜单', () => {
|
||||
const seprateModeEditor = createEditor(document, 'div1', '', { showFullScreen: false })
|
||||
const toolbarSelector = seprateModeEditor.$toolbarElem.selector as string
|
||||
const fullMenuEl = $(toolbarSelector).find(FULLSCREEN_MENU_CLASS_SELECTOR)
|
||||
|
||||
expect(fullMenuEl.length).toBe(0)
|
||||
})
|
||||
|
||||
test('调用 setFullScreen 设置编辑器全屏模式', () => {
|
||||
setFullScreen(editor)
|
||||
|
||||
const toolbarSelector = editor.$toolbarElem.elems[0].className
|
||||
const $iconElem = $(`.${toolbarSelector}`).children().last().find('i')
|
||||
const $editorParent = $(`.${toolbarSelector}`).parent().get(0)
|
||||
const $textContainerElem = editor.$textContainerElem
|
||||
|
||||
expect($iconElem.get(0).className).toContain('w-e-icon-fullscreen_exit')
|
||||
expect($editorParent.className).toContain(EDIT_CONTAINER_FULLSCREEN_CLASS)
|
||||
expect(+$editorParent.style.zIndex).toEqual(editor.config.zIndexFullScreen)
|
||||
expect($textContainerElem.elems[0].style.height).toBe('100%')
|
||||
})
|
||||
|
||||
test('调用 setUnFullScreen 取消编辑器全屏模式', () => {
|
||||
setFullScreen(editor)
|
||||
|
||||
setUnFullScreen(editor)
|
||||
|
||||
const toolbarSelector = editor.$toolbarElem.elems[0].className
|
||||
const $iconElem = $(`.${toolbarSelector}`).children().last().find('i')
|
||||
const $editorParent = $(`.${toolbarSelector}`).parent().get(0)
|
||||
const $textContainerElem = editor.$textContainerElem
|
||||
|
||||
expect($iconElem.get(0).className).toContain('w-e-icon-fullscreen')
|
||||
expect($editorParent.className).not.toContain(EDIT_CONTAINER_FULLSCREEN_CLASS)
|
||||
expect($editorParent.style.zIndex).toBe('auto')
|
||||
expect($textContainerElem.elems[0].style.height).toBe(editor.config.height + 'px')
|
||||
})
|
||||
})
|
|
@ -3,11 +3,11 @@
|
|||
* @author lkw
|
||||
*/
|
||||
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import BackColor from '../../src/menus/back-color'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import BackColor from '../../../src/menus/back-color'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let backColorMenu: BackColor
|
|
@ -3,11 +3,11 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import Editor from '../../src/editor'
|
||||
import Bold from '../../src/menus/bold/index'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/editor'
|
||||
import Bold from '../../../src/menus/bold/index'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let boldMenu: Bold
|
|
@ -4,12 +4,12 @@
|
|||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import Code from '../../src/menus/code/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Panel from '../../src/menus/menu-constructors/Panel'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import Code from '../../../src/menus/code/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
import Panel from '../../../src/menus/menu-constructors/Panel'
|
||||
import hljs from 'highlight.js'
|
||||
|
||||
let editor: Editor
|
|
@ -3,8 +3,8 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import Editor from '../../src/wangEditor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/wangEditor'
|
||||
|
||||
const { BtnMenu, DropListMenu, PanelMenu, DropList, Panel, Tooltip } = Editor
|
||||
|
|
@ -2,10 +2,10 @@
|
|||
* @description 表情菜单 单元测试
|
||||
* @author liuwei
|
||||
*/
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import EmoticonMenu from '../../src/menus/emoticon/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import EmoticonMenu from '../../../src/menus/emoticon/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
let editor: Editor
|
||||
let emoticonMenu: EmoticonMenu
|
||||
test('表情 菜单:点击弹出 panel', () => {
|
|
@ -3,11 +3,11 @@
|
|||
* @author lkw
|
||||
*/
|
||||
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import FontColor from '../../src/menus/font-color'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import FontColor from '../../../src/menus/font-color'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let fontColorMenu: FontColor
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
// 暂时用 customFontSize 代替 fontSize ,因此这个测试用例暂时不用,先暂存 - wangfupeng 2020.08.10
|
||||
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import FontSize from '../../src/menus/font-size'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import FontSize from '../../../src/menus/font-size'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let fontSizeMenu: FontSize
|
|
@ -3,11 +3,11 @@
|
|||
* @author dyl
|
||||
*/
|
||||
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import FontStyle from '../../src/menus/font-style'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import FontStyle from '../../../src/menus/font-style'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let fontStyleMenu: FontStyle
|
|
@ -3,11 +3,11 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import Head from '../../src/menus/head/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import Head from '../../../src/menus/head/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let headMenu: Head
|
|
@ -4,12 +4,12 @@
|
|||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import ImgMenu from '../../src/menus/img/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Panel from '../../src/menus/menu-constructors/Panel'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import ImgMenu from '../../../src/menus/img/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
import Panel from '../../../src/menus/menu-constructors/Panel'
|
||||
|
||||
let editor: Editor
|
||||
let imgMenu: ImgMenu
|
|
@ -3,11 +3,11 @@
|
|||
* @author tonghan
|
||||
*/
|
||||
|
||||
import $ from '../../src/utils/dom-core'
|
||||
import Indent from '../../src/menus/indent/index'
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import $ from '../../../src/utils/dom-core'
|
||||
import Indent from '../../../src/menus/indent/index'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let indentMenu: Indent
|
|
@ -3,8 +3,8 @@
|
|||
* @author wangfupeng
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/editor'
|
||||
|
||||
let editor: Editor
|
||||
|
|
@ -3,11 +3,11 @@
|
|||
* @author liuwei
|
||||
*/
|
||||
|
||||
import createEditor from '../fns/create-editor'
|
||||
import Editor from '../../src/editor'
|
||||
import Italic from '../../src/menus/italic/index'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import Editor from '../../../src/editor'
|
||||
import Italic from '../../../src/menus/italic/index'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let italicMenu: Italic
|
|
@ -3,11 +3,11 @@
|
|||
* @author liuwei
|
||||
*/
|
||||
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import justify from '../../src/menus/justify/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import justify from '../../../src/menus/justify/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let justifyMenu: justify
|
|
@ -3,11 +3,11 @@
|
|||
* @author lichunlin
|
||||
*/
|
||||
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import lineHeight from '../../src/menus/lineHeight/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import lineHeight from '../../../src/menus/lineHeight/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
|
||||
let editor: Editor
|
||||
let lineHeightMenu: lineHeight
|
|
@ -4,12 +4,12 @@
|
|||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
import Editor from '../../src/editor'
|
||||
import createEditor from '../fns/create-editor'
|
||||
import mockCmdFn from '../fns/command-mock'
|
||||
import Link from '../../src/menus/link/index'
|
||||
import { getMenuInstance } from '../fns/menus'
|
||||
import Panel from '../../src/menus/menu-constructors/Panel'
|
||||
import Editor from '../../../src/editor'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import Link from '../../../src/menus/link/index'
|
||||
import { getMenuInstance } from '../../helpers/menus'
|
||||
import Panel from '../../../src/menus/menu-constructors/Panel'
|
||||
|
||||
let editor: Editor
|
||||
let linkMenu: Link
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue