hello') 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/blockquote/render-elem.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description blockquote render elem test 3 | * @author wangfupeng 4 | */ 5 | 6 | import createEditor from '../../../../tests/utils/create-editor' 7 | import { renderBlockQuoteConf } from '../../src/modules/blockquote/render-elem' 8 | 9 | describe('blockquote - render elem', () => { 10 | const editor = createEditor() 11 | 12 | it('render blockquote elem', () => { 13 | expect(renderBlockQuoteConf.type).toBe('blockquote') 14 | 15 | const elem = { type: 'blockquote', children: [] } 16 | const vnode = renderBlockQuoteConf.renderElem(elem, null, editor) 17 | expect(vnode.sel).toBe('blockquote') 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/code-block/elem-to-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description code-block elem to html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { codeToHtmlConf, preToHtmlConf } from '../../src/modules/code-block/elem-to-html' 7 | 8 | describe('code-block - elem to html', () => { 9 | it('code to html', () => { 10 | expect(codeToHtmlConf.type).toBe('code') 11 | const elem = { type: 'code', children: [] } 12 | const html = codeToHtmlConf.elemToHtml(elem, 'hello') 13 | expect(html).toBe('
hello
')
14 | })
15 |
16 | it('pre to html', () => {
17 | expect(preToHtmlConf.type).toBe('pre')
18 | const elem = { type: 'pre', children: [] }
19 | const html = preToHtmlConf.elemToHtml(elem, 'hello')
20 | expect(html).toBe('hello') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/code-block/render-elem.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description code-block render elem test 3 | * @author wangfupeng 4 | */ 5 | 6 | import createEditor from '../../../../tests/utils/create-editor' 7 | import { renderPreConf, renderCodeConf } from '../../src/modules/code-block/render-elem' 8 | 9 | describe('code-block render elem', () => { 10 | const editor = createEditor() 11 | 12 | it('render code elem', () => { 13 | expect(renderCodeConf.type).toBe('code') 14 | 15 | const elem = { type: 'code', children: [] } 16 | const vnode = renderCodeConf.renderElem(elem, null, editor) 17 | expect(vnode.sel).toBe('code') 18 | }) 19 | 20 | it('render pre elem', () => { 21 | expect(renderPreConf.type).toBe('pre') 22 | 23 | const elem = { type: 'pre', children: [] } 24 | const vnode = renderPreConf.renderElem(elem, null, editor) 25 | expect(vnode.sel).toBe('pre') 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/color/render-text-style.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @description color - render text style test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { jsx } from 'snabbdom' 7 | import { renderStyle } from '../../src/modules/color/render-style' 8 | 9 | describe('color - render text style', () => { 10 | it('render color style', () => { 11 | const color = 'rgb(51, 51, 51)' 12 | const bgColor = 'rgb(204, 204, 204)' 13 | const textNode = { text: 'hello', color, bgColor } 14 | const vnode = hello 15 | 16 | // @ts-ignore 17 | const newVnode = renderStyle(textNode, vnode) as any 18 | expect(newVnode.sel).toBe('span') 19 | expect(newVnode.data.style.color).toBe(color) 20 | expect(newVnode.data.style.backgroundColor).toBe(bgColor) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/color/text-style-to-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description color - text style to html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { styleToHtml } from '../../src/modules/color/style-to-html' 7 | 8 | describe('color - text style to html', () => { 9 | it('color to html', () => { 10 | const color = 'rgb(51, 51, 51)' 11 | const bgColor = 'rgb(204, 204, 204)' 12 | const textNode = { text: '', color, bgColor } 13 | 14 | const html = styleToHtml(textNode, 'hello') 15 | expect(html).toBe(`hello`) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/divider/elem-to-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description divider - elem to html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { dividerToHtmlConf } from '../../src/modules/divider/elem-to-html' 7 | 8 | describe('divider - elem to html', () => { 9 | it('divider to html', () => { 10 | expect(dividerToHtmlConf.type).toBe('divider') 11 | 12 | const elem = { type: 'divider', children: [{ text: '' }] } 13 | const html = dividerToHtmlConf.elemToHtml(elem, '') 14 | expect(html).toBe('
hello
14 | 15 | // @ts-ignore 16 | const newVnode = renderStyle(elem, vnode) 17 | // @ts-ignore 18 | expect(newVnode.data.style.textIndent).toBe(indent) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/indent/text-style-to-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description indent - text style to html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { styleToHtml } from '../../src/modules/indent/style-to-html' 7 | 8 | describe('indent - text style to html', () => { 9 | it('text style to html', () => { 10 | const indent = '2em' 11 | const elem = { type: 'paragraph', indent, children: [] } 12 | const html = styleToHtml(elem, 'hello
') 13 | expect(html).toBe(`hello
`) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/justify/parse-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description parse html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { $ } from 'dom7' 7 | import createEditor from '../../../../tests/utils/create-editor' 8 | import { parseStyleHtml } from '../../src/modules/justify/parse-style-html' 9 | 10 | describe('text align - parse style', () => { 11 | const editor = createEditor() 12 | 13 | it('parse style', () => { 14 | const $p = $('') 15 | const paragraph = { type: 'paragraph', children: [{ text: 'hello' }] } 16 | 17 | // parse 18 | const res = parseStyleHtml($p[0], paragraph, editor) 19 | expect(res).toEqual({ 20 | type: 'paragraph', 21 | textAlign: 'center', 22 | children: [{ text: 'hello' }], 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/justify/render-text-style.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @description justify - render text style test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { jsx } from 'snabbdom' 7 | import { renderStyle } from '../../src/modules/justify/render-style' 8 | 9 | describe('justify - render text style', () => { 10 | it('render text style', () => { 11 | const elem = { type: 'paragraph', textAlign: 'center', children: [] } 12 | const vnode = hello 13 | // @ts-ignore 忽略 vnode 格式 14 | const newVnode = renderStyle(elem, vnode) 15 | // @ts-ignore 忽略 vnode 格式 16 | expect(newVnode.data.style?.textAlign).toBe('center') 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/justify/text-style-to-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description justify - text style to html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { styleToHtml } from '../../src/modules/justify/style-to-html' 7 | 8 | describe('justify text-style-to-html', () => { 9 | it('text style to html', () => { 10 | const elem = { type: 'paragraph', textAlign: 'center', children: [] } 11 | const html = styleToHtml(elem, 'hello') 12 | expect(html).toBe('hello') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/line-height/parse-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description parse html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { $ } from 'dom7' 7 | import createEditor from '../../../../tests/utils/create-editor' 8 | import { parseStyleHtml } from '../../src/modules/line-height/parse-style-html' 9 | 10 | describe('line height - parse style', () => { 11 | const editor = createEditor() 12 | 13 | it('parse style', () => { 14 | const $p = $('') 15 | const paragraph = { type: 'paragraph', children: [{ text: 'hello' }] } 16 | 17 | // parse 18 | const res = parseStyleHtml($p[0], paragraph, editor) 19 | expect(res).toEqual({ 20 | type: 'paragraph', 21 | lineHeight: '2.5', 22 | children: [{ text: 'hello' }], 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/line-height/render-text-style.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @description line-height render text style test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { jsx } from 'snabbdom' 7 | import { renderStyle } from '../../src/modules/line-height/render-style' 8 | 9 | describe('line-height render-text-style', () => { 10 | it('render text style', () => { 11 | const elem = { type: 'paragraph', lineHeight: '1.5', children: [] } 12 | const vnode = hello 13 | // @ts-ignore 忽略 vnode 格式检查 14 | const newVnode = renderStyle(elem, vnode) 15 | // @ts-ignore 忽略 vnode 格式检查 16 | expect(newVnode.data.style.lineHeight).toBe('1.5') 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description line-height text-style-to-html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { styleToHtml } from '../../src/modules/line-height/style-to-html' 7 | 8 | describe('line-height text-style-to-html', () => { 9 | it('text style to html', () => { 10 | const elem = { type: 'paragraph', lineHeight: '1.5', children: [] } 11 | const html = styleToHtml(elem, 'hello') 12 | expect(html).toBe('hello') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/link/elem-to-html.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description link - elem to html test 3 | * @author wangfupeng 4 | */ 5 | 6 | import { linkToHtmlConf } from '../../src/modules/link/elem-to-html' 7 | 8 | describe('link elem to html', () => { 9 | it('link to html', () => { 10 | expect(linkToHtmlConf.type).toBe('link') 11 | 12 | const url = 'https://www.wangeditor.com/' 13 | const target = '_blank' 14 | const elem = { type: 'link', url, target, children: [] } 15 | 16 | const html = linkToHtmlConf.elemToHtml(elem, 'hello') 17 | expect(html).toBe(`hello`) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/link/render-elem.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description link - render elem test 3 | * @author wangfupeng 4 | */ 5 | 6 | import createEditor from '../../../../tests/utils/create-editor' 7 | import { renderLinkConf } from '../../src/modules/link/render-elem' 8 | 9 | describe('link render elem', () => { 10 | const editor = createEditor() 11 | 12 | it('render elem', () => { 13 | expect(renderLinkConf.type).toBe('link') 14 | 15 | const url = 'https://www.wangeditor.com/' 16 | const target = '_blank' 17 | const elem = { type: 'link', url, target, children: [] } 18 | 19 | const vnode = renderLinkConf.renderElem(elem, null, editor) as any 20 | expect(vnode.sel).toBe('a') 21 | expect(vnode.data.href).toBe(url) 22 | expect(vnode.data.target).toBe(target) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts: -------------------------------------------------------------------------------- 1 | import { html } from 'dom7' 2 | /** 3 | * @description paragraph - elem to html test 4 | * @author wangfupeng 5 | */ 6 | 7 | import { pToHtmlConf } from '../../src/modules/paragraph/elem-to-html' 8 | 9 | describe('paragraph - elem to html', () => { 10 | it('paragraph to html', () => { 11 | expect(pToHtmlConf.type).toBe('paragraph') 12 | 13 | const elem = { type: 'paragraph', children: [] } 14 | const html = pToHtmlConf.elemToHtml(elem, 'hello') 15 | expect(html).toBe('hello
') 16 | }) 17 | 18 | it('paragraph to html with empty children', () => { 19 | expect(pToHtmlConf.type).toBe('paragraph') 20 | 21 | const elem = { type: 'paragraph', children: [] } 22 | const html = pToHtmlConf.elemToHtml(elem, '') 23 | expect(html).toBe('hello
world
${childrenHtml}` 10 | } 11 | 12 | export const quoteToHtmlConf = { 13 | type: 'blockquote', 14 | elemToHtml: quoteToHtml, 15 | } 16 | -------------------------------------------------------------------------------- /packages/basic-modules/src/modules/blockquote/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description blockquote entry 3 | * @author wangfupeng 4 | */ 5 | 6 | import { IModuleConf } from '@wangeditor/core' 7 | import { renderBlockQuoteConf } from './render-elem' 8 | import { quoteToHtmlConf } from './elem-to-html' 9 | import { parseHtmlConf } from './parse-elem-html' 10 | import { blockquoteMenuConf } from './menu/index' 11 | import withBlockquote from './plugin' 12 | 13 | const blockquote: Partial
{children}23 | return vnode 24 | } 25 | 26 | export const renderBlockQuoteConf = { 27 | type: 'blockquote', 28 | renderElem: renderBlockQuote, 29 | } 30 | -------------------------------------------------------------------------------- /packages/basic-modules/src/modules/code-block/custom-types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 自定义 element 3 | * @author wangfupeng 4 | */ 5 | 6 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts 7 | 8 | type PureText = { 9 | text: string 10 | } 11 | 12 | export type PreElement = { 13 | type: 'pre' 14 | children: CodeElement[] 15 | } 16 | 17 | export type CodeElement = { 18 | type: 'code' 19 | language: string 20 | children: PureText[] 21 | } 22 | -------------------------------------------------------------------------------- /packages/basic-modules/src/modules/code-block/elem-to-html.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description to html 3 | * @author wangfupeng 4 | */ 5 | 6 | import { Element } from 'slate' 7 | 8 | function codeToHtml(elem: Element, childrenHtml: string): string { 9 | // 代码高亮 `class="language-xxx"` 在 code-highlight 中实现 10 | return `
${childrenHtml}
`
11 | }
12 |
13 | export const codeToHtmlConf = {
14 | type: 'code',
15 | elemToHtml: codeToHtml,
16 | }
17 |
18 | function preToHtml(elem: Element, childrenHtml: string): string {
19 | return `${childrenHtml}` 20 | } 21 | 22 | export const preToHtmlConf = { 23 | type: 'pre', 24 | elemToHtml: preToHtml, 25 | } 26 | -------------------------------------------------------------------------------- /packages/basic-modules/src/modules/code-block/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description code block module 3 | * @author wangfupeng 4 | */ 5 | 6 | import { IModuleConf } from '@wangeditor/core' 7 | import { codeBlockMenuConf } from './menu/index' 8 | import withCodeBlock from './plugin' 9 | import { renderPreConf, renderCodeConf } from './render-elem' 10 | import { preParseHtmlConf } from './pre-parse-html' 11 | import { parseCodeHtmlConf, parsePreHtmlConf } from './parse-elem-html' 12 | import { codeToHtmlConf, preToHtmlConf } from './elem-to-html' 13 | 14 | const codeBlockModule: Partial
,去掉其中的 (兼容 V4)
11 | * @param codeElem codeElem
12 | */
13 | function preParse(codeElem: DOMElement): DOMElement {
14 | const $code = $(codeElem)
15 | const tagName = getTagName($code)
16 | if (tagName !== 'code') return codeElem
17 |
18 | const $xmp = $code.find('xmp')
19 | if ($xmp.length === 0) return codeElem // 不是 V4 格式
20 |
21 | const codeText = $xmp.text()
22 | $xmp.remove()
23 | $code.text(codeText)
24 |
25 | return $code[0]
26 | }
27 |
28 | export const preParseHtmlConf = {
29 | selector: 'pre>code', // 匹配 下的
30 | preParseHtml: preParse,
31 | }
32 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/code-block/render-elem.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render elem
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element as SlateElement } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { IDomEditor } from '@wangeditor/core'
9 |
10 | function renderPre(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
11 | const vnode = {children}
12 | return vnode
13 | }
14 |
15 | function renderCode(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
16 | // 和 basic/simple-style module 的“行内代码”并不冲突。一个是根据 mark 渲染,一个是根据 node.type 渲染
17 | const vnode = {children}
18 | return vnode
19 | }
20 |
21 | export const renderPreConf = {
22 | type: 'pre',
23 | renderElem: renderPre,
24 | }
25 |
26 | export const renderCodeConf = {
27 | type: 'code',
28 | renderElem: renderCode,
29 | }
30 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | //【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts
7 |
8 | export type ColorText = {
9 | text: string
10 | color?: string
11 | bgColor?: string
12 | }
13 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description color bgColor
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderStyle } from './render-style'
8 | import { styleToHtml } from './style-to-html'
9 | import { preParseHtmlConf } from './pre-parse-html'
10 | import { parseStyleHtml } from './parse-style-html'
11 | import { colorMenuConf, bgColorMenuConf } from './menu/index'
12 |
13 | const color: Partial = {
14 | renderStyle,
15 | styleToHtml,
16 | preParseHtml: [preParseHtmlConf],
17 | parseStyleHtml,
18 | menus: [colorMenuConf, bgColorMenuConf],
19 | }
20 |
21 | export default color
22 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description bg color menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { BG_COLOR_SVG } from '../../../constants/icon-svg'
9 |
10 | class BgColorMenu extends BaseMenu {
11 | readonly title = t('color.bgColor')
12 | readonly iconSvg = BG_COLOR_SVG
13 | readonly mark = 'bgColor'
14 | }
15 |
16 | export default BgColorMenu
17 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/menu/ColorMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description color menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { FONT_COLOR_SVG } from '../../../constants/icon-svg'
9 |
10 | class ColorMenu extends BaseMenu {
11 | readonly title = t('color.color')
12 | readonly iconSvg = FONT_COLOR_SVG
13 | readonly mark = 'color'
14 | }
15 |
16 | export default ColorMenu
17 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import ColorMenu from './ColorMenu'
7 | import BgColorMenu from './BgColorMenu'
8 | import { genColors, genBgColors } from './config'
9 |
10 | export const colorMenuConf = {
11 | key: 'color',
12 | factory() {
13 | return new ColorMenu()
14 | },
15 |
16 | // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
17 | // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
18 | config: {
19 | colors: genColors(),
20 | },
21 | }
22 |
23 | export const bgColorMenuConf = {
24 | key: 'bgColor',
25 | factory() {
26 | return new BgColorMenu()
27 | },
28 | config: {
29 | colors: genBgColors(),
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/parse-style-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse style html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant, Text } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 | import { ColorText } from './custom-types'
9 | import $, { DOMElement, getStyleValue } from '../../utils/dom'
10 |
11 | export function parseStyleHtml(text: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
12 | const $text = $(text)
13 | if (!Text.isText(node)) return node
14 |
15 | const textNode = node as ColorText
16 |
17 | const color = getStyleValue($text, 'color')
18 | if (color) {
19 | textNode.color = color
20 | }
21 |
22 | let bgColor = getStyleValue($text, 'background-color')
23 | if (!bgColor) bgColor = getStyleValue($text, 'background') // word 背景色
24 | if (bgColor) {
25 | textNode.bgColor = bgColor
26 | }
27 |
28 | return textNode
29 | }
30 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/pre-parse-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description pre-parse html
3 | * @author wangfupeng
4 | */
5 |
6 | import $, { DOMElement, getTagName } from '../../utils/dom'
7 |
8 | /**
9 | * pre-prase font ,兼容 V4
10 | * @param fontElem fontElem
11 | */
12 | function preParse(fontElem: DOMElement): DOMElement {
13 | const $font = $(fontElem)
14 | const tagName = getTagName($font)
15 | if (tagName !== 'font') return fontElem
16 |
17 | // 处理 color (V4 使用 xx 格式)
18 | const color = $font.attr('color') || ''
19 | if (color) {
20 | $font.removeAttr('color')
21 | $font.css('color', color)
22 | }
23 |
24 | return $font[0]
25 | }
26 |
27 | export const preParseHtmlConf = {
28 | selector: 'font',
29 | preParseHtml: preParse,
30 | }
31 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/color/render-style.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render color style
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { addVnodeStyle } from '../../utils/vdom'
9 | import { ColorText } from './custom-types'
10 |
11 | /**
12 | * 添加样式
13 | * @param node text node
14 | * @param vnode vnode
15 | * @returns vnode
16 | */
17 | export function renderStyle(node: Descendant, vnode: VNode): VNode {
18 | const { color, bgColor } = node as ColorText
19 | let styleVnode: VNode = vnode
20 |
21 | if (color) {
22 | addVnodeStyle(styleVnode, { color })
23 | }
24 | if (bgColor) {
25 | addVnodeStyle(styleVnode, { backgroundColor: bgColor })
26 | }
27 |
28 | return styleVnode
29 | }
30 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/common/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description common module
3 | * @author wangfupeng
4 | */
5 | import { IModuleConf } from '@wangeditor/core'
6 | import { enterMenuConf } from './menu/index'
7 |
8 | const commonModule: Partial = {
9 | menus: [enterMenuConf],
10 | }
11 |
12 | export default commonModule
13 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/common/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description common menu config
3 | * @author wangfupeng
4 | */
5 |
6 | import EnterMenu from './EnterMenu'
7 |
8 | export const enterMenuConf = {
9 | key: 'enter',
10 | factory() {
11 | return new EnterMenu()
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/divider/README.md:
--------------------------------------------------------------------------------
1 | # 分割线
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/divider/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description divider element
3 | * @author wangfupeng
4 | */
5 |
6 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
7 |
8 | type EmptyText = {
9 | text: ''
10 | }
11 |
12 | export type DividerElement = {
13 | type: 'divider'
14 | children: EmptyText[]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/divider/elem-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element } from 'slate'
7 |
8 | function dividerToHtml(elem: Element, childrenHtml: string): string {
9 | return `
`
10 | }
11 |
12 | export const dividerToHtmlConf = {
13 | type: 'divider',
14 | elemToHtml: dividerToHtml,
15 | }
16 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/divider/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description divider module
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import withDivider from './plugin'
8 | import { renderDividerConf } from './render-elem'
9 | import { dividerToHtmlConf } from './elem-to-html'
10 | import { parseHtmlConf } from './parse-elem-html'
11 | import { insertDividerMenuConf } from './menu/index'
12 |
13 | const image: Partial = {
14 | renderElems: [renderDividerConf],
15 | elemsToHtml: [dividerToHtmlConf],
16 | parseElemsHtml: [parseHtmlConf],
17 | menus: [insertDividerMenuConf],
18 | editorPlugin: withDivider,
19 | }
20 |
21 | export default image
22 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/divider/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description divider menu
3 | * @author wangfupeng
4 | */
5 |
6 | import InsertDividerMenu from './InsertDividerMenu'
7 | // import DeleteDividerMenu from './DeleteDividerMenu.ts'
8 |
9 | export const insertDividerMenuConf = {
10 | key: 'divider',
11 | factory() {
12 | return new InsertDividerMenu()
13 | },
14 | }
15 |
16 | // export const deleteDividerMenuConf = {
17 | // key: 'deleteDivider',
18 | // factory() {
19 | // return new DeleteDividerMenu()
20 | // },
21 | // }
22 | // divider 可用键盘删除了,所以注释掉该菜单 wangfupeng 22.02.23
23 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/divider/parse-elem-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant } from 'slate'
7 | import $, { DOMElement } from '../../utils/dom'
8 | import { IDomEditor } from '@wangeditor/core'
9 | import { DividerElement } from './custom-types'
10 |
11 | function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): DividerElement {
12 | return {
13 | type: 'divider',
14 | children: [{ text: '' }], // void node 有一个空白 text
15 | }
16 | }
17 |
18 | export const parseHtmlConf = {
19 | selector: 'hr:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
20 | parseElemHtml: parseHtml,
21 | }
22 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/emotion/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description emotion entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { emotionMenuConf } from './menu/index'
8 |
9 | const emotion: Partial = {
10 | menus: [emotionMenuConf],
11 | }
12 |
13 | export default emotion
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/emotion/menu/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menu config
3 | * @author wangfupeng
4 | */
5 |
6 | export function genConfig() {
7 | const emotions =
8 | '😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😛 😝 😜 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😢 😭 😤 😠 😡 😳 😱 😨 🤗 🤔 😶 😑 😬 🙄 😯 😴 😷 🤑 😈 🤡 💩 👻 💀 👀 👣 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🖕 ✍️ 🙏'
9 | return emotions.split(' ')
10 | }
11 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/emotion/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description emotion menu
3 | * @author wangfupeng
4 | */
5 |
6 | import EmotionMenu from './EmotionMenu'
7 | import { genConfig } from './config'
8 |
9 | export const emotionMenuConf = {
10 | key: 'emotion',
11 | factory() {
12 | return new EmotionMenu()
13 | },
14 |
15 | // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
16 | // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
17 | config: {
18 | emotions: genConfig(),
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/font-size-family/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | //【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts
7 |
8 | export type FontSizeAndFamilyText = {
9 | text: string
10 | fontSize?: string
11 | fontFamily?: string
12 | }
13 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/font-size-family/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description font-size font-family
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderStyle } from './render-style'
8 | import { styleToHtml } from './style-to-html'
9 | import { preParseHtmlConf } from './pre-parse-html'
10 | import { parseStyleHtml } from './parse-style-html'
11 | import { fontSizeMenuConf, fontFamilyMenuConf } from './menu/index'
12 |
13 | const fontSizeAndFamily: Partial = {
14 | renderStyle,
15 | styleToHtml,
16 | preParseHtml: [preParseHtmlConf],
17 | parseStyleHtml,
18 | menus: [fontSizeMenuConf, fontFamilyMenuConf],
19 | }
20 |
21 | export default fontSizeAndFamily
22 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/font-size-family/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description font-size font-family menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import FontSizeMenu from './FontSizeMenu'
7 | import FontFamilyMenu from './FontFamilyMenu'
8 | import { genFontSizeConfig, getFontFamilyConfig } from './config'
9 |
10 | export const fontSizeMenuConf = {
11 | key: 'fontSize',
12 | factory() {
13 | return new FontSizeMenu()
14 | },
15 |
16 | // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
17 | // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
18 | config: {
19 | fontSizeList: genFontSizeConfig(),
20 | },
21 | }
22 |
23 | export const fontFamilyMenuConf = {
24 | key: 'fontFamily',
25 | factory() {
26 | return new FontFamilyMenu()
27 | },
28 | config: {
29 | fontFamilyList: getFontFamilyConfig(),
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/font-size-family/render-style.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render font-size font-family style
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { addVnodeStyle } from '../../utils/vdom'
9 | import { FontSizeAndFamilyText } from './custom-types'
10 |
11 | /**
12 | * 添加样式
13 | * @param node slate elem
14 | * @param vnode vnode
15 | * @returns vnode
16 | */
17 | export function renderStyle(node: Descendant, vnode: VNode): VNode {
18 | const { fontSize, fontFamily } = node as FontSizeAndFamilyText
19 | let styleVnode: VNode = vnode
20 |
21 | if (fontSize) {
22 | addVnodeStyle(styleVnode, { fontSize })
23 | }
24 | if (fontFamily) {
25 | addVnodeStyle(styleVnode, { fontFamily })
26 | }
27 |
28 | return styleVnode
29 | }
30 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/full-screen/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 全屏
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { fullScreenConf } from './menu/index'
8 |
9 | const fullScreen: Partial = {
10 | menus: [fullScreenConf],
11 | }
12 |
13 | export default fullScreen
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description redo menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
7 | import { FULL_SCREEN_SVG } from '../../../constants/icon-svg'
8 |
9 | class FullScreen implements IButtonMenu {
10 | title = t('fullScreen.title')
11 | iconSvg = FULL_SCREEN_SVG
12 | tag = 'button'
13 | alwaysEnable = true
14 |
15 | getValue(editor: IDomEditor): string | boolean {
16 | return ''
17 | }
18 |
19 | isActive(editor: IDomEditor): boolean {
20 | return editor.isFullScreen
21 | }
22 |
23 | isDisabled(editor: IDomEditor): boolean {
24 | return false
25 | }
26 |
27 | exec(editor: IDomEditor, value: string | boolean) {
28 | if (editor.isFullScreen) {
29 | editor.unFullScreen()
30 | } else {
31 | editor.fullScreen()
32 | }
33 | }
34 | }
35 |
36 | export default FullScreen
37 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/full-screen/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import FullScreen from './FullScreen'
7 |
8 | export const fullScreenConf = {
9 | key: 'fullScreen',
10 | factory() {
11 | return new FullScreen()
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/header/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type Header1Element = {
11 | type: 'header1'
12 | children: Text[]
13 | }
14 |
15 | export type Header2Element = {
16 | type: 'header2'
17 | children: Text[]
18 | }
19 |
20 | export type Header3Element = {
21 | type: 'header3'
22 | children: Text[]
23 | }
24 |
25 | export type Header4Element = {
26 | type: 'header4'
27 | children: Text[]
28 | }
29 |
30 | export type Header5Element = {
31 | type: 'header5'
32 | children: Text[]
33 | }
34 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/header/elem-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element } from 'slate'
7 |
8 | function genToHtmlFn(level: number) {
9 | function headerToHtml(elem: Element, childrenHtml: string): string {
10 | return `${childrenHtml} `
11 | }
12 | return headerToHtml
13 | }
14 |
15 | export const header1ToHtmlConf = {
16 | type: 'header1',
17 | elemToHtml: genToHtmlFn(1),
18 | }
19 |
20 | export const header2ToHtmlConf = {
21 | type: 'header2',
22 | elemToHtml: genToHtmlFn(2),
23 | }
24 |
25 | export const header3ToHtmlConf = {
26 | type: 'header3',
27 | elemToHtml: genToHtmlFn(3),
28 | }
29 |
30 | export const header4ToHtmlConf = {
31 | type: 'header4',
32 | elemToHtml: genToHtmlFn(4),
33 | }
34 |
35 | export const header5ToHtmlConf = {
36 | type: 'header5',
37 | elemToHtml: genToHtmlFn(5),
38 | }
39 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description header1 button menu
3 | * @author wangfupeng
4 | */
5 |
6 | import HeaderButtonMenuBase from './HeaderButtonMenuBase'
7 |
8 | class Header1ButtonMenu extends HeaderButtonMenuBase {
9 | title = 'H1'
10 | type = 'header1'
11 | }
12 |
13 | export default Header1ButtonMenu
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description header2 button menu
3 | * @author wangfupeng
4 | */
5 |
6 | import HeaderButtonMenuBase from './HeaderButtonMenuBase'
7 |
8 | class Header2ButtonMenu extends HeaderButtonMenuBase {
9 | title = 'H2'
10 | type = 'header2'
11 | }
12 |
13 | export default Header2ButtonMenu
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description header3 button menu
3 | * @author wangfupeng
4 | */
5 |
6 | import HeaderButtonMenuBase from './HeaderButtonMenuBase'
7 |
8 | class Header3ButtonMenu extends HeaderButtonMenuBase {
9 | title = 'H3'
10 | type = 'header3'
11 | }
12 |
13 | export default Header3ButtonMenu
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description header4 button menu
3 | * @author wangfupeng
4 | */
5 |
6 | import HeaderButtonMenuBase from './HeaderButtonMenuBase'
7 |
8 | class Header4ButtonMenu extends HeaderButtonMenuBase {
9 | title = 'H4'
10 | type = 'header4'
11 | }
12 |
13 | export default Header4ButtonMenu
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description header5 button menu
3 | * @author wangfupeng
4 | */
5 |
6 | import HeaderButtonMenuBase from './HeaderButtonMenuBase'
7 |
8 | class Header5ButtonMenu extends HeaderButtonMenuBase {
9 | title = 'H5'
10 | type = 'header5'
11 | }
12 |
13 | export default Header5ButtonMenu
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description image element
3 | * @author wangfupeng
4 | */
5 |
6 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
7 |
8 | type EmptyText = {
9 | text: ''
10 | }
11 |
12 | export type ImageStyle = {
13 | width?: string
14 | height?: string
15 | }
16 |
17 | export type ImageElement = {
18 | type: 'image'
19 | src: string
20 | alt?: string
21 | href?: string
22 | style?: ImageStyle
23 | children: EmptyText[]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/elem-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element } from 'slate'
7 | import { ImageElement } from './custom-types'
8 |
9 | function imageToHtml(elemNode: Element, childrenHtml: string): string {
10 | const { src, alt = '', href = '', style = {} } = elemNode as ImageElement
11 | const { width = '', height = '' } = style
12 |
13 | let styleStr = ''
14 | if (width) styleStr += `width: ${width};`
15 | if (height) styleStr += `height: ${height};`
16 | return `
`
17 | }
18 |
19 | export const imageToHtmlConf = {
20 | type: 'image',
21 | elemToHtml: imageToHtml,
22 | }
23 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description image module entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import withImage from './plugin'
8 | import { renderImageConf } from './render-elem'
9 | import { imageToHtmlConf } from './elem-to-html'
10 | import { parseHtmlConf } from './parse-elem-html'
11 | import {
12 | insertImageMenuConf,
13 | deleteImageMenuConf,
14 | editImageMenuConf,
15 | viewImageLinkMenuConf,
16 | imageWidth30MenuConf,
17 | imageWidth50MenuConf,
18 | imageWidth100MenuConf,
19 | } from './menu/index'
20 |
21 | const image: Partial = {
22 | renderElems: [renderImageConf],
23 | elemsToHtml: [imageToHtmlConf],
24 | parseElemsHtml: [parseHtmlConf],
25 | menus: [
26 | insertImageMenuConf,
27 | deleteImageMenuConf,
28 | editImageMenuConf,
29 | viewImageLinkMenuConf,
30 | imageWidth30MenuConf,
31 | imageWidth50MenuConf,
32 | imageWidth100MenuConf,
33 | ],
34 | editorPlugin: withImage,
35 | }
36 |
37 | export default image
38 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/menu/Width100.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description image width 100%
3 | * @author wangfupeng
4 | */
5 |
6 | import ImageWidthBaseClass from './WidthBase'
7 |
8 | class ImageWidth100 extends ImageWidthBaseClass {
9 | readonly title = '100%' // 菜单标题
10 | readonly value = '100%' // css width 的值
11 | }
12 |
13 | export default ImageWidth100
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/menu/Width30.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description image width 30%
3 | * @author wangfupeng
4 | */
5 |
6 | import ImageWidthBaseClass from './WidthBase'
7 |
8 | class ImageWidth30 extends ImageWidthBaseClass {
9 | readonly title = '30%' // 菜单标题
10 | readonly value = '30%' // css width 的值
11 | }
12 |
13 | export default ImageWidth30
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/menu/Width50.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description image width 50%
3 | * @author wangfupeng
4 | */
5 |
6 | import ImageWidthBaseClass from './WidthBase'
7 |
8 | class ImageWidth50 extends ImageWidthBaseClass {
9 | readonly title = '50%' // 菜单标题
10 | readonly value = '50%' // css width 的值
11 | }
12 |
13 | export default ImageWidth50
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/parse-elem-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 | import { ImageElement } from './custom-types'
9 | import $, { DOMElement, getStyleValue } from '../../utils/dom'
10 |
11 | function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): ImageElement {
12 | const $elem = $(elem)
13 | let href = $elem.attr('data-href') || ''
14 | href = decodeURIComponent(href) // 兼容 V4
15 |
16 | return {
17 | type: 'image',
18 | src: $elem.attr('src') || '',
19 | alt: $elem.attr('alt') || '',
20 | href,
21 | style: {
22 | width: getStyleValue($elem, 'width'),
23 | height: getStyleValue($elem, 'height'),
24 | },
25 | children: [{ text: '' }], // void node 有一个空白 text
26 | }
27 | }
28 |
29 | export const parseHtmlConf = {
30 | selector: 'img:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
31 | parseElemHtml: parseHtml,
32 | }
33 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/image/plugin.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description editor 插件,重写 editor API
3 | * @author wangfupeng
4 | */
5 |
6 | // import { Editor, Path, Operation } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 |
9 | function withImage(editor: T): T {
10 | const { isInline, isVoid, insertNode } = editor
11 | const newEditor = editor
12 |
13 | // 重写 isInline
14 | newEditor.isInline = elem => {
15 | const { type } = elem
16 |
17 | if (type === 'image') {
18 | return true
19 | }
20 |
21 | return isInline(elem)
22 | }
23 |
24 | // 重写 isVoid
25 | newEditor.isVoid = elem => {
26 | const { type } = elem
27 |
28 | if (type === 'image') {
29 | return true
30 | }
31 |
32 | return isVoid(elem)
33 | }
34 |
35 | // 返回 editor ,重要!
36 | return newEditor
37 | }
38 |
39 | export default withImage
40 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/indent/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type IndentElement = {
11 | type: string
12 | indent?: string | null
13 | children: Text[]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/indent/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description indent entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderStyle } from './render-style'
8 | import { styleToHtml } from './style-to-html'
9 | import { preParseHtmlConf } from './pre-parse-html'
10 | import { parseStyleHtml } from './parse-style-html'
11 | import { indentMenuConf, delIndentMenuConf } from './menu/index'
12 |
13 | const indent: Partial = {
14 | renderStyle,
15 | styleToHtml,
16 | preParseHtml: [preParseHtmlConf],
17 | parseStyleHtml,
18 | menus: [indentMenuConf, delIndentMenuConf],
19 | }
20 |
21 | export default indent
22 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/indent/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description indent menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import DecreaseIndentMenu from './DecreaseIndentMenu'
7 | import IncreaseIndentMenu from './IncreaseIndentMenu'
8 |
9 | export const indentMenuConf = {
10 | key: 'indent',
11 | factory() {
12 | return new IncreaseIndentMenu()
13 | },
14 | }
15 |
16 | export const delIndentMenuConf = {
17 | key: 'delIndent',
18 | factory() {
19 | return new DecreaseIndentMenu()
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/indent/parse-style-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse style html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant, Element } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 | import { IndentElement } from './custom-types'
9 | import $, { DOMElement, getStyleValue } from '../../utils/dom'
10 |
11 | export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
12 | const $elem = $(elem)
13 | if (!Element.isElement(node)) return node
14 |
15 | const elemNode = node as IndentElement
16 |
17 | const indent = getStyleValue($elem, 'text-indent')
18 | const indentNumber = parseInt(indent, 10)
19 | if (indent && indentNumber > 0) {
20 | elemNode.indent = indent
21 | }
22 |
23 | return elemNode
24 | }
25 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/indent/pre-parse-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description pre-parse html
3 | * @author wangfupeng
4 | */
5 |
6 | import $, { DOMElement, getStyleValue } from '../../utils/dom'
7 |
8 | /**
9 | * pre-prase text-indent 兼容 V4 和 V5 早期格式(都使用 padding-left)
10 | * @param elem elem
11 | */
12 | function preParse(elem: DOMElement): DOMElement {
13 | const $elem = $(elem)
14 | const paddingLeft = getStyleValue($elem, 'padding-left')
15 |
16 | if (/\dem/.test(paddingLeft)) {
17 | // 如 '2em' ,V4 格式
18 | $elem.css('text-indent', '2em')
19 | }
20 |
21 | if (/\dpx/.test(paddingLeft)) {
22 | // px 单位
23 | const num = parseInt(paddingLeft, 10)
24 | if (num % 32 === 0) {
25 | // 如 32px 64px ,V5 早期格式
26 | $elem.css('text-indent', '2em')
27 | }
28 | }
29 |
30 | return $elem[0]
31 | }
32 |
33 | export const preParseHtmlConf = {
34 | selector: 'p,h1,h2,h3,h4,h5',
35 | preParseHtml: preParse,
36 | }
37 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/indent/render-style.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render indent style
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element, Descendant } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { addVnodeStyle } from '../../utils/vdom'
9 | import { IndentElement } from './custom-types'
10 |
11 | /**
12 | * 添加样式
13 | * @param node slate elem
14 | * @param vnode vnode
15 | * @returns vnode
16 | */
17 | export function renderStyle(node: Descendant, vnode: VNode): VNode {
18 | if (!Element.isElement(node)) return vnode
19 |
20 | const { indent } = node as IndentElement // 如 '2em'
21 | let styleVnode: VNode = vnode
22 |
23 | if (indent) {
24 | addVnodeStyle(styleVnode, { textIndent: indent })
25 | }
26 |
27 | return styleVnode
28 | }
29 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/indent/style-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description textStyle to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element, Descendant } from 'slate'
7 | import $, { getOuterHTML } from '../../utils/dom'
8 | import { IndentElement } from './custom-types'
9 |
10 | export function styleToHtml(node: Descendant, elemHtml: string): string {
11 | if (!Element.isElement(node)) return elemHtml
12 |
13 | const { indent } = node as IndentElement // 如 '2em'
14 | if (!indent) return elemHtml
15 |
16 | // 设置样式
17 | const $elem = $(elemHtml)
18 | $elem.css('text-indent', indent)
19 |
20 | // 输出 html
21 | return getOuterHTML($elem)
22 | }
23 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type JustifyElement = {
11 | type: string
12 | textAlign?: string
13 | children: Text[]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description justify module entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderStyle } from './render-style'
8 | import { styleToHtml } from './style-to-html'
9 | import { parseStyleHtml } from './parse-style-html'
10 | import {
11 | justifyLeftMenuConf,
12 | justifyRightMenuConf,
13 | justifyCenterMenuConf,
14 | justifyJustifyMenuConf,
15 | } from './menu/index'
16 |
17 | const justify: Partial = {
18 | renderStyle,
19 | styleToHtml,
20 | parseStyleHtml,
21 | menus: [justifyLeftMenuConf, justifyRightMenuConf, justifyCenterMenuConf, justifyJustifyMenuConf],
22 | }
23 |
24 | export default justify
25 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description justify center menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { Transforms, Element } from 'slate'
7 | import { IDomEditor, t } from '@wangeditor/core'
8 | import BaseMenu from './BaseMenu'
9 | import { JUSTIFY_CENTER_SVG } from '../../../constants/icon-svg'
10 |
11 | class JustifyCenterMenu extends BaseMenu {
12 | readonly title = t('justify.center')
13 | readonly iconSvg = JUSTIFY_CENTER_SVG
14 |
15 | exec(editor: IDomEditor, value: string | boolean): void {
16 | Transforms.setNodes(
17 | editor,
18 | {
19 | textAlign: 'center',
20 | },
21 | { match: n => Element.isElement(n) && !editor.isInline(n) } // inline 元素设置text-align 是没作用的
22 | )
23 | }
24 | }
25 |
26 | export default JustifyCenterMenu
27 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 两端对齐
3 | * @author wangfupeng
4 | */
5 |
6 | import { Transforms, Element } from 'slate'
7 | import { IDomEditor, t } from '@wangeditor/core'
8 | import BaseMenu from './BaseMenu'
9 | import { JUSTIFY_JUSTIFY_SVG } from '../../../constants/icon-svg'
10 |
11 | class JustifyJustifyMenu extends BaseMenu {
12 | readonly title = t('justify.justify')
13 | readonly iconSvg = JUSTIFY_JUSTIFY_SVG
14 |
15 | exec(editor: IDomEditor, value: string | boolean): void {
16 | Transforms.setNodes(
17 | editor,
18 | {
19 | textAlign: 'justify',
20 | },
21 | { match: n => Element.isElement(n) && !editor.isInline(n) }
22 | )
23 | }
24 | }
25 |
26 | export default JustifyJustifyMenu
27 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description justify left menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { Transforms, Element } from 'slate'
7 | import { IDomEditor, t } from '@wangeditor/core'
8 | import BaseMenu from './BaseMenu'
9 | import { JUSTIFY_LEFT_SVG } from '../../../constants/icon-svg'
10 |
11 | class JustifyLeftMenu extends BaseMenu {
12 | readonly title = t('justify.left')
13 | readonly iconSvg = JUSTIFY_LEFT_SVG
14 |
15 | exec(editor: IDomEditor, value: string | boolean): void {
16 | Transforms.setNodes(
17 | editor,
18 | {
19 | textAlign: 'left',
20 | },
21 | { match: n => Element.isElement(n) && !editor.isInline(n) }
22 | )
23 | }
24 | }
25 |
26 | export default JustifyLeftMenu
27 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description justify right menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { Transforms, Element } from 'slate'
7 | import { IDomEditor, t } from '@wangeditor/core'
8 | import BaseMenu from './BaseMenu'
9 | import { JUSTIFY_RIGHT_SVG } from '../../../constants/icon-svg'
10 |
11 | class JustifyRightMenu extends BaseMenu {
12 | readonly title = t('justify.right')
13 | readonly iconSvg = JUSTIFY_RIGHT_SVG
14 |
15 | exec(editor: IDomEditor, value: string | boolean): void {
16 | Transforms.setNodes(
17 | editor,
18 | {
19 | textAlign: 'right',
20 | },
21 | { match: n => Element.isElement(n) && !editor.isInline(n) }
22 | )
23 | }
24 | }
25 |
26 | export default JustifyRightMenu
27 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description justify menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import JustifyLeftMenu from './JustifyLeftMenu'
7 | import JustifyRightMenu from './JustifyRightMenu'
8 | import JustifyCenterMenu from './JustifyCenterMenu'
9 | import JustifyJustifyMenu from './JustifyJustifyMenu'
10 |
11 | export const justifyLeftMenuConf = {
12 | key: 'justifyLeft',
13 | factory() {
14 | return new JustifyLeftMenu()
15 | },
16 | }
17 |
18 | export const justifyRightMenuConf = {
19 | key: 'justifyRight',
20 | factory() {
21 | return new JustifyRightMenu()
22 | },
23 | }
24 |
25 | export const justifyCenterMenuConf = {
26 | key: 'justifyCenter',
27 | factory() {
28 | return new JustifyCenterMenu()
29 | },
30 | }
31 |
32 | export const justifyJustifyMenuConf = {
33 | key: 'justifyJustify',
34 | factory() {
35 | return new JustifyJustifyMenu()
36 | },
37 | }
38 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/parse-style-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse style html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant, Element } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 | import { JustifyElement } from './custom-types'
9 | import $, { DOMElement, getStyleValue } from '../../utils/dom'
10 |
11 | export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
12 | const $elem = $(elem)
13 | if (!Element.isElement(node)) return node
14 |
15 | const elemNode = node as JustifyElement
16 |
17 | const textAlign = getStyleValue($elem, 'text-align')
18 | if (textAlign) {
19 | elemNode.textAlign = textAlign
20 | }
21 |
22 | return elemNode
23 | }
24 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/render-style.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render justify style
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant, Element } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { addVnodeStyle } from '../../utils/vdom'
9 | import { JustifyElement } from './custom-types'
10 |
11 | /**
12 | * 添加样式
13 | * @param node slate elem
14 | * @param vnode vnode
15 | * @returns vnode
16 | */
17 | export function renderStyle(node: Descendant, vnode: VNode): VNode {
18 | if (!Element.isElement(node)) return vnode
19 |
20 | const { textAlign } = node as JustifyElement // 如 'left'/'right'/'center' 等
21 | let styleVnode: VNode = vnode
22 |
23 | if (textAlign) {
24 | addVnodeStyle(styleVnode, { textAlign })
25 | }
26 |
27 | return styleVnode
28 | }
29 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/justify/style-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description textStyle to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element, Descendant } from 'slate'
7 | import $, { getOuterHTML } from '../../utils/dom'
8 | import { JustifyElement } from './custom-types'
9 |
10 | export function styleToHtml(node: Descendant, elemHtml: string): string {
11 | if (!Element.isElement(node)) return elemHtml
12 |
13 | const { textAlign } = node as JustifyElement // 如 'left'/'right'/'center' 等
14 | if (!textAlign) return elemHtml
15 |
16 | // 设置样式
17 | const $elem = $(elemHtml)
18 | $elem.css('text-align', textAlign)
19 |
20 | // 输出 html
21 | const outerHtml = getOuterHTML($elem)
22 | return outerHtml
23 | }
24 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/line-height/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type LineHeightElement = {
11 | type: string
12 | lineHeight?: string
13 | children: Text[]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/line-height/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description line-height module entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderStyle } from './render-style'
8 | import { styleToHtml } from './style-to-html'
9 | import { lineHeightMenuConf } from './menu/index'
10 | import { parseStyleHtml } from './parse-style-html'
11 |
12 | const lineHeight: Partial = {
13 | renderStyle,
14 | styleToHtml,
15 | parseStyleHtml,
16 | menus: [lineHeightMenuConf],
17 | }
18 |
19 | export default lineHeight
20 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/line-height/menu/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description line-height config
3 | * @author wangfupeng
4 | */
5 |
6 | export function genLineHeightConfig() {
7 | return ['1', '1.15', '1.5', '2', '2.5', '3']
8 | }
9 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/line-height/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description line-height menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import LineHeightMenu from './LineHeightMenu'
7 | import { genLineHeightConfig } from './config'
8 |
9 | export const lineHeightMenuConf = {
10 | key: 'lineHeight',
11 | factory() {
12 | return new LineHeightMenu()
13 | },
14 |
15 | // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
16 | // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
17 | config: {
18 | lineHeightList: genLineHeightConfig(),
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/line-height/parse-style-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse style html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant, Element } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 | import { LineHeightElement } from './custom-types'
9 | import $, { DOMElement, getStyleValue } from '../../utils/dom'
10 |
11 | export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant {
12 | const $elem = $(elem)
13 | if (!Element.isElement(node)) return node
14 |
15 | const elemNode = node as LineHeightElement
16 |
17 | const { lineHeightList = [] } = editor.getMenuConfig('lineHeight')
18 | const lineHeight = getStyleValue($elem, 'line-height')
19 | if (lineHeight && lineHeightList.includes(lineHeight)) {
20 | elemNode.lineHeight = lineHeight
21 | }
22 |
23 | return elemNode
24 | }
25 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/line-height/render-style.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render line-height style
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element, Descendant } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { addVnodeStyle } from '../../utils/vdom'
9 | import { LineHeightElement } from './custom-types'
10 |
11 | /**
12 | * 添加样式
13 | * @param node slate elem
14 | * @param vnode vnode
15 | * @returns vnode
16 | */
17 | export function renderStyle(node: Descendant, vnode: VNode): VNode {
18 | if (!Element.isElement(node)) return vnode
19 |
20 | const { lineHeight } = node as LineHeightElement // 如 '1' '1.5'
21 | let styleVnode: VNode = vnode
22 |
23 | if (lineHeight) {
24 | addVnodeStyle(styleVnode, { lineHeight })
25 | }
26 |
27 | return styleVnode
28 | }
29 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/line-height/style-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description textStyle to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element, Descendant } from 'slate'
7 | import $, { getOuterHTML } from '../../utils/dom'
8 | import { LineHeightElement } from './custom-types'
9 |
10 | export function styleToHtml(node: Descendant, elemHtml: string): string {
11 | if (!Element.isElement(node)) return elemHtml
12 |
13 | const { lineHeight } = node as LineHeightElement // 如 '1' '1.5'
14 | if (!lineHeight) return elemHtml
15 |
16 | // 设置样式
17 | const $elem = $(elemHtml)
18 | $elem.css('line-height', lineHeight)
19 |
20 | // 输出 html
21 | return getOuterHTML($elem)
22 | }
23 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/link/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type LinkElement = {
11 | type: 'link'
12 | url: string
13 | target?: string
14 | children: Text[]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/link/elem-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element } from 'slate'
7 | import { LinkElement } from './custom-types'
8 |
9 | function linkToHtml(elem: Element, childrenHtml: string): string {
10 | const { url, target = '_blank' } = elem as LinkElement
11 |
12 | return `${childrenHtml}`
13 | }
14 |
15 | export const linkToHtmlConf = {
16 | type: 'link',
17 | elemToHtml: linkToHtml,
18 | }
19 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/link/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description link entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import withLink from './plugin'
8 | import { renderLinkConf } from './render-elem'
9 | import { linkToHtmlConf } from './elem-to-html'
10 | import { parseHtmlConf } from './parse-elem-html'
11 | import {
12 | insertLinkMenuConf,
13 | editLinkMenuConf,
14 | unLinkMenuConf,
15 | viewLinkMenuConf,
16 | } from './menu/index'
17 |
18 | const link: Partial = {
19 | renderElems: [renderLinkConf],
20 | elemsToHtml: [linkToHtmlConf],
21 | parseElemsHtml: [parseHtmlConf],
22 | menus: [insertLinkMenuConf, editLinkMenuConf, unLinkMenuConf, viewLinkMenuConf],
23 | editorPlugin: withLink,
24 | }
25 |
26 | export default link
27 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/link/menu/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description link menu config
3 | * @author wangfupeng
4 | */
5 |
6 | export function genLinkMenuConfig() {
7 | return {
8 | /**
9 | * 检查链接,支持 async fn
10 | * @param text link text
11 | * @param url link url
12 | */
13 | checkLink(text: string, url: string): boolean | string | undefined {
14 | // 1. 返回 true ,说明检查通过
15 | // 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
16 | // 3. 返回 undefined(即没有任何返回),说明检查未通过,编辑器会阻止插入
17 | return true
18 | },
19 |
20 | /**
21 | * parse link url
22 | * @param url url
23 | * @returns newUrl
24 | */
25 | parseLinkUrl(url: string): string {
26 | return url
27 | },
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/link/parse-elem-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant, Text } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 | import { LinkElement } from './custom-types'
9 | import $, { DOMElement } from '../../utils/dom'
10 |
11 | function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): LinkElement {
12 | const $elem = $(elem)
13 | children = children.filter(child => {
14 | if (Text.isText(child)) return true
15 | if (editor.isInline(child)) return true
16 | return false
17 | })
18 |
19 | // 无 children ,则用纯文本
20 | if (children.length === 0) {
21 | children = [{ text: $elem.text().replace(/\s+/gm, ' ') }]
22 | }
23 |
24 | return {
25 | type: 'link',
26 | url: $elem.attr('href') || '',
27 | target: $elem.attr('target') || '',
28 | // @ts-ignore
29 | children,
30 | }
31 | }
32 |
33 | export const parseHtmlConf = {
34 | selector: 'a:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
35 | parseElemHtml: parseHtml,
36 | }
37 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/link/render-elem.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render link elem
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element as SlateElement } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { IDomEditor } from '@wangeditor/core'
9 | import { LinkElement } from './custom-types'
10 |
11 | /**
12 | * render link elem
13 | * @param elemNode slate elem
14 | * @param children children
15 | * @param editor editor
16 | * @returns vnode
17 | */
18 | function renderLink(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
19 | const { url, target = '_blank' } = elemNode as LinkElement
20 | const vnode = (
21 |
22 | {children}
23 |
24 | )
25 |
26 | return vnode
27 | }
28 |
29 | const renderLinkConf = {
30 | type: 'link', // 和 elemNode.type 一致
31 | renderElem: renderLink,
32 | }
33 |
34 | export { renderLinkConf }
35 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/paragraph/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type ParagraphElement = {
11 | type: 'paragraph'
12 | children: Text[]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/paragraph/elem-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element } from 'slate'
7 |
8 | function pToHtml(elem: Element, childrenHtml: string): string {
9 | if (childrenHtml === '') {
10 | return '
'
11 | }
12 | return `${childrenHtml}
`
13 | }
14 |
15 | export const pToHtmlConf = {
16 | type: 'paragraph',
17 | elemToHtml: pToHtml,
18 | }
19 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/paragraph/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description paragraph entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderParagraphConf } from './render-elem'
8 | import { pToHtmlConf } from './elem-to-html'
9 | import { parseParagraphHtmlConf } from './parse-elem-html'
10 | import withParagraph from './plugin'
11 |
12 | const p: Partial = {
13 | renderElems: [renderParagraphConf],
14 | elemsToHtml: [pToHtmlConf],
15 | parseElemsHtml: [parseParagraphHtmlConf],
16 | editorPlugin: withParagraph,
17 | }
18 |
19 | export default p
20 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Descendant, Text } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 | import { ParagraphElement } from './custom-types'
9 | import $, { DOMElement } from '../../utils/dom'
10 |
11 | function parseParagraphHtml(
12 | elem: DOMElement,
13 | children: Descendant[],
14 | editor: IDomEditor
15 | ): ParagraphElement {
16 | const $elem = $(elem)
17 |
18 | children = children.filter(child => {
19 | if (Text.isText(child)) return true
20 | if (editor.isInline(child)) return true
21 | return false
22 | })
23 |
24 | // 无 children ,则用纯文本
25 | if (children.length === 0) {
26 | children = [{ text: $elem.text().replace(/\s+/gm, ' ') }]
27 | }
28 |
29 | return {
30 | type: 'paragraph',
31 | // @ts-ignore
32 | children,
33 | }
34 | }
35 |
36 | export const parseParagraphHtmlConf = {
37 | selector: 'p:not([data-w-e-type])', // data-w-e-type 属性,留给自定义元素,保证扩展性
38 | parseElemHtml: parseParagraphHtml,
39 | }
40 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/paragraph/render-elem.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render paragraph elem
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element as SlateElement } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { IDomEditor } from '@wangeditor/core'
9 |
10 | /**
11 | * render paragraph elem
12 | * @param elemNode slate elem
13 | * @param children children
14 | * @param editor editor
15 | * @returns vnode
16 | */
17 | function renderParagraph(
18 | elemNode: SlateElement,
19 | children: VNode[] | null,
20 | editor: IDomEditor
21 | ): VNode {
22 | const vnode = {children}
23 | return vnode
24 | }
25 |
26 | export const renderParagraphConf = {
27 | type: 'paragraph',
28 | renderElem: renderParagraph,
29 | }
30 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | //【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts
7 |
8 | export type StyledText = {
9 | text: string
10 | bold?: boolean
11 | code?: boolean
12 | italic?: boolean
13 | through?: boolean
14 | underline?: boolean
15 | sup?: boolean
16 | sub?: boolean
17 | }
18 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/helper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description helper
3 | * @author wangfupeng
4 | */
5 |
6 | import { Editor, Node } from 'slate'
7 | import { IDomEditor, DomEditor } from '@wangeditor/core'
8 |
9 | export function isMenuDisabled(editor: IDomEditor, mark?: string): boolean {
10 | if (editor.selection == null) return true
11 |
12 | const [match] = Editor.nodes(editor, {
13 | match: n => {
14 | const type = DomEditor.getNodeType(n)
15 |
16 | if (type === 'pre') return true // 代码块
17 | if (Editor.isVoid(editor, n)) return true // void node
18 |
19 | return false
20 | },
21 | universal: true,
22 | })
23 |
24 | // 命中,则禁用
25 | if (match) return true
26 | return false
27 | }
28 |
29 | export function removeMarks(editor: IDomEditor, textNode: Node) {
30 | // 遍历 text node 属性,清除样式
31 | const keys = Object.keys(textNode as object)
32 | keys.forEach(key => {
33 | if (key === 'text') {
34 | // 保留 text 属性,text node 必须的
35 | return
36 | }
37 | // 其他属性,全部清除
38 | Editor.removeMark(editor, key)
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description text style entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderStyle } from './render-style'
8 | import { styleToHtml } from './style-to-html'
9 | import { parseStyleHtml } from './parse-style-html'
10 | import {
11 | boldMenuConf,
12 | underlineMenuConf,
13 | italicMenuConf,
14 | throughMenuConf,
15 | codeMenuConf,
16 | subMenuConf,
17 | supMenuConf,
18 | clearStyleMenuConf,
19 | } from './menu/index'
20 |
21 | const textStyle: Partial = {
22 | renderStyle,
23 | menus: [
24 | boldMenuConf,
25 | underlineMenuConf,
26 | italicMenuConf,
27 | throughMenuConf,
28 | codeMenuConf,
29 | subMenuConf,
30 | supMenuConf,
31 | clearStyleMenuConf,
32 | ],
33 | styleToHtml,
34 | parseStyleHtml,
35 | }
36 |
37 | export default textStyle
38 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description bold menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { BOLD_SVG } from '../../../constants/icon-svg'
9 |
10 | class BoldMenu extends BaseMenu {
11 | readonly mark = 'bold'
12 | readonly title = t('textStyle.bold')
13 | readonly iconSvg = BOLD_SVG
14 | readonly hotkey = 'mod+b'
15 | }
16 |
17 | export default BoldMenu
18 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { CODE_SVG } from '../../../constants/icon-svg'
9 |
10 | class CodeMenu extends BaseMenu {
11 | readonly mark = 'code'
12 | readonly title = t('textStyle.code')
13 | readonly iconSvg = CODE_SVG
14 | readonly hotkey = 'mod+e'
15 | }
16 |
17 | export default CodeMenu
18 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description italic menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { ITALIC_SVG } from '../../../constants/icon-svg'
9 |
10 | class ItalicMenu extends BaseMenu {
11 | readonly mark = 'italic'
12 | readonly title = t('textStyle.italic')
13 | readonly iconSvg = ITALIC_SVG
14 | readonly hotkey = 'mod+i'
15 | }
16 |
17 | export default ItalicMenu
18 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description sub menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { SUB_SVG } from '../../../constants/icon-svg'
9 |
10 | class SubMenu extends BaseMenu {
11 | readonly mark = 'sub'
12 | readonly marksNeedToRemove = ['sup'] // sub 和 sup 不能共存
13 | readonly title = t('textStyle.sub')
14 | readonly iconSvg = SUB_SVG
15 | readonly hotkey = ''
16 | }
17 |
18 | export default SubMenu
19 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description sup menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { SUP_SVG } from '../../../constants/icon-svg'
9 |
10 | class SupMenu extends BaseMenu {
11 | readonly mark = 'sup'
12 | readonly marksNeedToRemove = ['sub'] // sup 和 sub 不能共存
13 | readonly title = t('textStyle.sup')
14 | readonly iconSvg = SUP_SVG
15 | readonly hotkey = ''
16 | }
17 |
18 | export default SupMenu
19 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description through menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { THROUGH_SVG } from '../../../constants/icon-svg'
9 |
10 | class ThroughMenu extends BaseMenu {
11 | readonly mark = 'through'
12 | readonly title = t('textStyle.through')
13 | readonly iconSvg = THROUGH_SVG
14 | readonly hotkey = 'mod+shift+x'
15 | }
16 |
17 | export default ThroughMenu
18 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description underline menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { UNDER_LINE_SVG } from '../../../constants/icon-svg'
9 |
10 | class UnderlineMenu extends BaseMenu {
11 | readonly mark = 'underline'
12 | readonly title = t('textStyle.underline')
13 | readonly iconSvg = UNDER_LINE_SVG
14 | readonly hotkey = 'mod+u'
15 | }
16 |
17 | export default UnderlineMenu
18 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/todo/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type TodoElement = {
11 | type: 'todo'
12 | checked: boolean
13 | children: Text[]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/todo/elem-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element } from 'slate'
7 | import { TodoElement } from './custom-types'
8 |
9 | function todoToHtml(elem: Element, childrenHtml: string): string {
10 | const { checked } = elem as TodoElement
11 | const checkedAttr = checked ? 'checked' : ''
12 | return `${childrenHtml}`
13 | }
14 |
15 | export const todoToHtmlConf = {
16 | type: 'todo',
17 | elemToHtml: todoToHtml,
18 | }
19 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/todo/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description todo entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderTodoConf } from './render-elem'
8 | import withTodo from './plugin'
9 | import { todoMenuConf } from './menu/index'
10 | import { todoToHtmlConf } from './elem-to-html'
11 | import { parseHtmlConf } from './parse-elem-html'
12 | import { preParseHtmlConf } from './pre-parse-html'
13 |
14 | const todo: Partial = {
15 | renderElems: [renderTodoConf],
16 | elemsToHtml: [todoToHtmlConf],
17 | preParseHtml: [preParseHtmlConf],
18 | parseElemsHtml: [parseHtmlConf],
19 | menus: [todoMenuConf],
20 | editorPlugin: withTodo,
21 | }
22 |
23 | export default todo
24 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/todo/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description todo menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import TodoMenu from './Todo'
7 |
8 | export const todoMenuConf = {
9 | key: 'todo',
10 | factory() {
11 | return new TodoMenu()
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/todo/plugin.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description editor 插件,重写 editor API
3 | * @author wangfupeng
4 | */
5 |
6 | import { Node, Transforms, Range } from 'slate'
7 | import { DomEditor, IDomEditor } from '@wangeditor/core'
8 |
9 | function withTodo(editor: T): T {
10 | const { deleteBackward } = editor
11 | const newEditor = editor
12 |
13 | /**
14 | * 删除 todo 无内容时,变为 paragraph
15 | */
16 | newEditor.deleteBackward = unit => {
17 | const { selection } = editor
18 |
19 | if (selection && Range.isCollapsed(selection)) {
20 | // 获取选中的 todo
21 | const selectedTodo = DomEditor.getSelectedNodeByType(editor, 'todo')
22 | if (selectedTodo) {
23 | if (Node.string(selectedTodo).length === 0) {
24 | // 当前 todo 已经没有文字,则转换为 paragraph
25 | Transforms.setNodes(editor, { type: 'paragraph' }, { mode: 'highest' })
26 | return
27 | }
28 | }
29 | }
30 |
31 | deleteBackward(unit)
32 | }
33 |
34 | return newEditor
35 | }
36 |
37 | export default withTodo
38 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/undo-redo/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description undo redo
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { redoMenuConf, undoMenuConf } from './menu/index'
8 |
9 | const undoRedo: Partial = {
10 | menus: [redoMenuConf, undoMenuConf],
11 | }
12 |
13 | export default undoRedo
14 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description redo menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
7 | import { REDO_SVG } from '../../../constants/icon-svg'
8 |
9 | class RedoMenu implements IButtonMenu {
10 | title = t('undo.redo')
11 | iconSvg = REDO_SVG
12 | tag = 'button'
13 |
14 | getValue(editor: IDomEditor): string | boolean {
15 | return ''
16 | }
17 |
18 | isActive(editor: IDomEditor): boolean {
19 | return false
20 | }
21 |
22 | isDisabled(editor: IDomEditor): boolean {
23 | if (editor.selection == null) return true
24 | return false
25 | }
26 |
27 | exec(editor: IDomEditor, value: string | boolean) {
28 | if (typeof editor.redo === 'function') {
29 | editor.redo()
30 | }
31 | }
32 | }
33 |
34 | export default RedoMenu
35 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description undo menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { IButtonMenu, IDomEditor, t } from '@wangeditor/core'
7 | import { UNDO_SVG } from '../../../constants/icon-svg'
8 |
9 | class UndoMenu implements IButtonMenu {
10 | title = t('undo.undo')
11 | iconSvg = UNDO_SVG
12 | tag = 'button'
13 |
14 | getValue(editor: IDomEditor): string | boolean {
15 | return ''
16 | }
17 |
18 | isActive(editor: IDomEditor): boolean {
19 | return false
20 | }
21 |
22 | isDisabled(editor: IDomEditor): boolean {
23 | if (editor.selection == null) return true
24 | return false
25 | }
26 |
27 | exec(editor: IDomEditor, value: string | boolean) {
28 | if (typeof editor.undo === 'function') {
29 | editor.undo()
30 | }
31 | }
32 | }
33 |
34 | export default UndoMenu
35 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/modules/undo-redo/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import RedoMenu from './RedoMenu'
7 | import UndoMenu from './UndoMenu'
8 |
9 | export const undoMenuConf = {
10 | key: 'undo',
11 | factory() {
12 | return new UndoMenu()
13 | },
14 | }
15 |
16 | export const redoMenuConf = {
17 | key: 'redo',
18 | factory() {
19 | return new RedoMenu()
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/utils/util.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 工具函数
3 | * @author wangfupeng
4 | */
5 |
6 | import { nanoid } from 'nanoid'
7 |
8 | /**
9 | * 获取随机数字符串
10 | * @param prefix 前缀
11 | * @returns 随机数字符串
12 | */
13 | export function genRandomStr(prefix: string = 'r'): string {
14 | return `${prefix}-${nanoid()}`
15 | }
16 |
17 | export function replaceSymbols(str: string) {
18 | return str.replace(//g, '>')
19 | }
20 |
--------------------------------------------------------------------------------
/packages/basic-modules/src/utils/vdom.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description vdom utils fn
3 | * @author wangfupeng
4 | */
5 |
6 | import { VNode, VNodeStyle, Dataset } from 'snabbdom'
7 |
8 | // /**
9 | // * 给 vnode 添加 dataset
10 | // * @param vnode vnode
11 | // * @param newDataset { key: val }
12 | // */
13 | // export function addVnodeDataset(vnode: VNode, newDataset: Dataset) {
14 | // if (vnode.data == null) vnode.data = {}
15 | // const data = vnode.data
16 | // if (data.dataset == null) data.dataset = {}
17 |
18 | // Object.assign(data.dataset, newDataset)
19 | // }
20 |
21 | /**
22 | * 给 vnode 添加样式
23 | * @param vnode vnode
24 | * @param newStyle { key: val }
25 | */
26 | export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) {
27 | if (vnode.data == null) vnode.data = {}
28 | const data = vnode.data
29 | if (data.style == null) data.style = {}
30 |
31 | Object.assign(data.style, newStyle)
32 | }
33 |
--------------------------------------------------------------------------------
/packages/basic-modules/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {},
3 | "extends": "../../tsconfig.json",
4 | "include": [
5 | "./src/**/*",
6 | "../custom-types.d.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/packages/code-highlight/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor code highlight
2 |
3 | Code highlight module built in [wangEditor](https://www.wangeditor.com/) by default.
4 |
--------------------------------------------------------------------------------
/packages/code-highlight/__tests__/content.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code content
3 | * @author wangfupeng
4 | */
5 |
6 | export const text = 'const a = 100;'
7 |
8 | export const textNode = { text: text }
9 |
10 | export const language = 'javascript'
11 |
12 | export const codeNode = {
13 | type: 'code',
14 | language,
15 | children: [textNode],
16 | }
17 |
18 | export const preNode = {
19 | type: 'pre',
20 | children: [codeNode],
21 | }
22 |
23 | export const content = [{ type: 'paragraph', children: [{ text: 'hello world' }] }, preNode]
24 |
25 | export const textNodePath = [1, 0, 0]
26 |
27 | export const codeLocation = {
28 | anchor: { offset: text.length, path: textNodePath },
29 | focus: { offset: text.length, path: textNodePath },
30 | }
31 |
32 | export const paragraphLocation = {
33 | anchor: { offset: 0, path: [0, 0] },
34 | focus: { offset: 0, path: [0, 0] },
35 | }
36 |
37 | describe('加一个 case 防止报错~', () => {
38 | it('1 + 1 = 2', () => {
39 | expect(1 + 1).toBe(2)
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/packages/code-highlight/__tests__/decorate.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code-highlight decorate test
3 | * @author wangfupeng
4 | */
5 |
6 | import { IDomEditor } from '@wangeditor/core'
7 | import createEditor from '../../../tests/utils/create-editor'
8 | import codeHighLightDecorate from '../src/decorate/index'
9 | import { content, textNode, textNodePath } from './content'
10 |
11 | describe('code-highlight decorate', () => {
12 | let editor: IDomEditor | null = null
13 |
14 | beforeAll(() => {
15 | // 把 content 创建到一个编辑器中
16 | editor = createEditor({
17 | content,
18 | })
19 | })
20 |
21 | afterAll(() => {
22 | // 销毁 editor
23 | if (editor == null) return
24 | editor.destroy()
25 | editor = null
26 | })
27 |
28 | it('code-highlight decorate 拆分代码字符串', () => {
29 | const ranges = codeHighLightDecorate([textNode, textNodePath])
30 | expect(ranges.length).toBe(4) // 把 textNode 内容拆分为 4 段
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/packages/code-highlight/__tests__/elem-to-html.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code-hight elem-to-html
3 | * @author wangfupeng
4 | */
5 |
6 | import { IDomEditor } from '@wangeditor/core'
7 | import createEditor from '../../../tests/utils/create-editor'
8 | import { codeToHtmlConf } from '../src/module/elem-to-html'
9 | import { content, codeNode, language } from './content'
10 |
11 | describe('code-highlight elem to html', () => {
12 | let editor: IDomEditor | null = null
13 |
14 | beforeAll(() => {
15 | // 把 content 创建到一个编辑器中
16 | editor = createEditor({
17 | content,
18 | })
19 | })
20 |
21 | afterAll(() => {
22 | // 销毁 editor
23 | if (editor == null) return
24 | editor.destroy()
25 | editor = null
26 | })
27 |
28 | it('codeNode to html', () => {
29 | expect(codeToHtmlConf.type).toBe('code')
30 |
31 | if (editor == null) throw new Error('editor is null')
32 | const text = 'var n = 100;'
33 | const html = codeToHtmlConf.elemToHtml(codeNode, text)
34 | expect(html).toBe(`${text}
`)
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/packages/code-highlight/__tests__/render-text-style.test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code-highlight render text style test
3 | * @author wangfupeng
4 | */
5 |
6 | import { renderStyle } from '../src/module/render-style'
7 | import { jsx } from 'snabbdom'
8 |
9 | describe('code-highlight render text style', () => {
10 | it('code text style', () => {
11 | const leafNode = { text: 'let', keyword: true } // 定义一个 keyword leaf text node
12 | const vnode = let
13 |
14 | // @ts-ignore 忽略 vnode 格式检查
15 | const newVnode = renderStyle(leafNode, vnode)
16 | expect(newVnode.data?.props?.className).toBe('token keyword')
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/packages/code-highlight/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
2 | import pkg from './package.json'
3 |
4 | const name = 'WangEditorCodeHighLight'
5 |
6 | const configList = []
7 |
8 | // esm
9 | const esmConf = createRollupConfig({
10 | output: {
11 | file: pkg.module,
12 | format: 'esm',
13 | name,
14 | },
15 | })
16 | configList.push(esmConf)
17 |
18 | // umd
19 | const umdConf = createRollupConfig({
20 | output: {
21 | file: pkg.main,
22 | format: 'umd',
23 | name,
24 | },
25 | })
26 | configList.push(umdConf)
27 |
28 | export default configList
29 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/constants/svg.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description icon svg
3 | * @author wangfupeng
4 | */
5 |
6 | /**
7 | * 【注意】svg 字符串的长度 ,否则会导致代码体积过大
8 | * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293
9 | * 找不到再从 iconfont.com 搜索
10 | */
11 |
12 | export const JS_SVG =
13 | ''
14 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | // 拷贝自 basic-modules/src/modules/code-block/custom-types.ts
7 |
8 | type PureText = {
9 | text: string
10 | }
11 |
12 | export type PreElement = {
13 | type: 'pre'
14 | children: CodeElement[]
15 | }
16 |
17 | export type CodeElement = {
18 | type: 'code'
19 | language: string
20 | children: PureText[]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code-highlight
3 | * @author wangfupeng
4 | */
5 |
6 | import './assets/index.less'
7 |
8 | // 配置多语言
9 | import './locale/index'
10 |
11 | import wangEditorCodeHighlightModule from './module/index'
12 | import wangEditorCodeHighLightDecorate from './decorate'
13 |
14 | export { wangEditorCodeHighlightModule, wangEditorCodeHighLightDecorate }
15 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/locale/en.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n en
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | highLightModule: {
8 | selectLang: 'Language',
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/locale/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { i18nAddResources } from '@wangeditor/core'
7 | import enResources from './en'
8 | import zhResources from './zh-CN'
9 |
10 | i18nAddResources('en', enResources)
11 | i18nAddResources('zh-CN', zhResources)
12 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n zh-CN
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | highLightModule: {
8 | selectLang: '选择语言',
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/module/elem-to-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description to html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element } from 'slate'
7 | import { CodeElement } from '../custom-types'
8 |
9 | function codeToHtml(elem: Element, childrenHtml: string): string {
10 | const { language = '' } = elem as CodeElement
11 |
12 | const cssClass = language
13 | ? `class="language-${language}"` // prism.js 根据 language 代码高亮
14 | : ''
15 |
16 | return `${childrenHtml}
`
17 | }
18 |
19 | // 覆盖 basic-module 中的 code to html
20 | export const codeToHtmlConf = {
21 | type: 'code',
22 | elemToHtml: codeToHtml,
23 | }
24 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/module/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code highlight module
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import { renderStyle } from './render-style'
8 | import { parseCodeStyleHtml } from './parse-style-html'
9 | import { selectLangMenuConf } from './menu/index'
10 | import { codeToHtmlConf } from './elem-to-html'
11 |
12 | const codeHighlightModule: Partial = {
13 | renderStyle,
14 | parseStyleHtml: parseCodeStyleHtml,
15 | menus: [selectLangMenuConf],
16 | elemsToHtml: [codeToHtmlConf],
17 | }
18 |
19 | export default codeHighlightModule
20 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/module/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description code-highlight menu
3 | * @author wangfupeng
4 | */
5 |
6 | import SelectLangMenu from './SelectLangMenu'
7 | import { genCodeLangs } from './config'
8 |
9 | export const selectLangMenuConf = {
10 | key: 'codeSelectLang',
11 | factory() {
12 | return new SelectLangMenu()
13 | },
14 | config: {
15 | codeLangs: genCodeLangs(),
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/module/parse-style-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse style html
3 | * @author wangfupeng
4 | */
5 |
6 | import $, { DOMElement } from '../utils/dom'
7 | import { Descendant, Element } from 'slate'
8 | import { DomEditor, IDomEditor } from '@wangeditor/core'
9 | import { CodeElement } from '../custom-types'
10 |
11 | export function parseCodeStyleHtml(
12 | elem: DOMElement,
13 | node: Descendant,
14 | editor: IDomEditor
15 | ): Descendant {
16 | const $elem = $(elem)
17 |
18 | if (!Element.isElement(node)) return node
19 | if (DomEditor.getNodeType(node) !== 'code') return node // 只针对 pre/code 元素
20 |
21 | const elemNode = node as CodeElement
22 |
23 | const langAttr = $elem.attr('class') || ''
24 | if (langAttr.indexOf('language-') === 0) {
25 | // V5 版本,格式如 class="language-javascript"
26 | elemNode.language = langAttr.split('-')[1] || '' // 获取 'javascript'
27 | } else {
28 | // 兼容 V4 版本,格式如 class="Javascript"
29 | elemNode.language = langAttr.toLowerCase()
30 | }
31 |
32 | return elemNode
33 | }
34 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/module/render-style.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render code highlight style
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text as SlateText, Descendant } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { addVnodeClassName } from '../utils/vdom'
9 | import { prismTokenTypes } from '../vendor/prism'
10 |
11 | /**
12 | * 添加样式
13 | * @param node slate text
14 | * @param vnode vnode
15 | * @returns vnode
16 | */
17 | export function renderStyle(node: Descendant, vnode: VNode): VNode {
18 | const leafNode = node as SlateText & { [key: string]: string }
19 | let styleVnode: VNode = vnode
20 |
21 | let className = ''
22 | prismTokenTypes.forEach(type => {
23 | if (leafNode[type]) className = type
24 | })
25 |
26 | if (className) {
27 | className = `token ${className}` // 如 'token keyword' - prismjs 渲染的规则
28 | addVnodeClassName(styleVnode, className)
29 | }
30 |
31 | return styleVnode
32 | }
33 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/utils/dom.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description DOM 操作
3 | * @author wangfupeng
4 | */
5 |
6 | import $, { attr } from 'dom7'
7 |
8 | if (attr) $.fn.attr = attr
9 |
10 | export { Dom7Array } from 'dom7'
11 |
12 | export default $
13 |
14 | // COMPAT: This is required to prevent TypeScript aliases from doing some very
15 | // weird things for Slate's types with the same name as globals. (2019/11/27)
16 | // https://github.com/microsoft/TypeScript/issues/35002
17 | import DOMNode = globalThis.Node
18 | import DOMComment = globalThis.Comment
19 | import DOMElement = globalThis.Element
20 | import DOMText = globalThis.Text
21 | import DOMRange = globalThis.Range
22 | import DOMSelection = globalThis.Selection
23 | import DOMStaticRange = globalThis.StaticRange
24 | export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange }
25 |
--------------------------------------------------------------------------------
/packages/code-highlight/src/utils/vdom.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description vdom utils fn
3 | * @author wangfupeng
4 | */
5 |
6 | import { VNode, VNodeStyle } from 'snabbdom'
7 |
8 | /**
9 | * 给 vnode 添加 className
10 | * @param vnode vnode
11 | * @param className css class
12 | */
13 | export function addVnodeClassName(vnode: VNode, className: string) {
14 | if (vnode.data == null) vnode.data = {}
15 | const data = vnode.data
16 | if (data.props == null) data.props = {}
17 |
18 | Object.assign(data.props, { className })
19 | }
20 |
21 | /**
22 | * 给 vnode 添加样式
23 | * @param vnode vnode
24 | * @param newStyle { key: val }
25 | */
26 | export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) {
27 | if (vnode.data == null) vnode.data = {}
28 | const data = vnode.data
29 | if (data.style == null) data.style = {}
30 |
31 | Object.assign(data.style, newStyle)
32 | }
33 |
--------------------------------------------------------------------------------
/packages/code-highlight/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {},
3 | "extends": "../../tsconfig.json",
4 | "include": [
5 | "./src/**/*",
6 | "../custom-types.d.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor core
2 |
3 | [wangEditor](https://www.wangeditor.com/) core.
4 |
5 | ## Main Functionalities
6 | - View( model -> vdom -> DOM ) + Selection
7 | - Menus + toolbar + hoverbar
8 | - Core editor APIs and events
9 | - Register third-party modules (menus, plugins...)
10 |
11 | ## Main dependencies
12 | - [slate.js](https://docs.slatejs.org/)
13 | - [snabbdom.js](https://github.com/snabbdom/snabbdom)
14 |
--------------------------------------------------------------------------------
/packages/core/__tests__/config/menu-config.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menu config test
3 | * @author wangfupeng
4 | */
5 |
6 | import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor
7 | import { registerGlobalMenuConf } from '../../src/config/register'
8 |
9 | describe('menu config', () => {
10 | it('set and get', () => {
11 | // 先注册一下菜单 key ,再设置配置(专为单元测试,用户使用时不涉及)
12 | registerGlobalMenuConf('bold', {})
13 |
14 | const menuKey = 'bold' // 必须是一个存在的 menu key
15 | const menuConfig = {
16 | x: 100,
17 | }
18 |
19 | const editor = createCoreEditor({
20 | config: {
21 | MENU_CONF: {
22 | [menuKey]: menuConfig,
23 | },
24 | },
25 | })
26 |
27 | expect(editor.getMenuConfig(menuKey)).toEqual(menuConfig)
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/packages/core/__tests__/create-core-editor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description create editor - 用于 packages/core 单元测试
3 | * @author wangfupeng
4 | */
5 |
6 | import createEditor from '../src/create/create-editor'
7 |
8 | export default function (options: any = {}) {
9 | const container = document.createElement('div')
10 | document.body.appendChild(container)
11 |
12 | return createEditor({
13 | selector: container,
14 | ...options,
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/__tests__/i18n/index.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n test
3 | * @author wangfupeng
4 | */
5 |
6 | import i18next, { i18nAddResources, i18nChangeLanguage, t } from '../../src/i18n'
7 |
8 | describe('i18n', () => {
9 | // 添加语言项
10 | i18nAddResources('en', {
11 | module1: {
12 | hello: 'hello',
13 | },
14 | })
15 | i18nAddResources('zh-CN', {
16 | module1: {
17 | hello: '你好',
18 | },
19 | })
20 |
21 | it('default lang', () => {
22 | expect(i18next.language).toBe('zh-CN')
23 | expect(t('module1.hello')).toBe('你好')
24 | })
25 |
26 | it('change lang', () => {
27 | i18nChangeLanguage('en')
28 | expect(i18next.language).toBe('en')
29 | expect(t('module1.hello')).toBe('hello')
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/packages/core/__tests__/menus/README.md:
--------------------------------------------------------------------------------
1 | # menus test
2 |
3 | TODO 各个 modules 中没有这块代码的测试,待编写...
4 |
--------------------------------------------------------------------------------
/packages/core/__tests__/menus/register-menus/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 注册菜单,入口
3 | * @author wangfupeng
4 | */
5 |
6 | import './register-button-menu'
7 | import './register-select-menu'
8 | import './register-modal-menu'
9 |
--------------------------------------------------------------------------------
/packages/core/__tests__/menus/register-menus/register-button-menu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 注册菜单 - button menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { registerMenu, IButtonMenu } from '../../../src/menus/index'
7 | import { IDomEditor } from '../../../src/editor/interface'
8 |
9 | class MyButtonMenu implements IButtonMenu {
10 | readonly title = 'My Button Menu'
11 | readonly tag = 'button'
12 | getValue(editor: IDomEditor) {
13 | return ''
14 | }
15 | isActive(editor: IDomEditor) {
16 | return false
17 | }
18 | isDisabled(editor: IDomEditor) {
19 | return false
20 | }
21 | exec(editor: IDomEditor, value: string | boolean) {
22 | console.log('do..')
23 | }
24 | }
25 |
26 | registerMenu({
27 | key: 'myButtonMenu',
28 | factory() {
29 | return new MyButtonMenu()
30 | },
31 | })
32 |
--------------------------------------------------------------------------------
/packages/core/__tests__/menus/register-menus/register-modal-menu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 注册菜单 - modal menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { registerMenu, IModalMenu } from '../../../src/menus/index'
7 | import { IDomEditor } from '../../../src/editor/interface'
8 |
9 | class MyModalMenu implements IModalMenu {
10 | readonly title = 'My Modal Menu'
11 | readonly tag = 'button'
12 | readonly showModal = true
13 | readonly modalWidth = 300
14 | getValue(editor: IDomEditor) {
15 | return ''
16 | }
17 | isActive(editor: IDomEditor) {
18 | return false
19 | }
20 | isDisabled(editor: IDomEditor) {
21 | return false
22 | }
23 | exec(editor: IDomEditor, value: string | boolean) {
24 | console.log('do..')
25 | }
26 | getModalContentElem(editor: IDomEditor) {
27 | return document.createElement('div')
28 | }
29 | getModalPositionNode(editor: IDomEditor) {
30 | return null
31 | }
32 | }
33 |
34 | registerMenu({
35 | key: 'myModalMenu',
36 | factory() {
37 | return new MyModalMenu()
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/packages/core/__tests__/menus/register-menus/register-select-menu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 注册菜单 - select menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { registerMenu, ISelectMenu, IOption } from '../../../src/menus/index'
7 | import { IDomEditor } from '../../../src/editor/interface'
8 |
9 | class MySelectMenu implements ISelectMenu {
10 | readonly title = 'My Select Menu'
11 | readonly tag = 'select'
12 | getValue(editor: IDomEditor) {
13 | return ''
14 | }
15 | isActive(editor: IDomEditor) {
16 | return false
17 | }
18 | isDisabled(editor: IDomEditor) {
19 | return false
20 | }
21 | exec(editor: IDomEditor, value: string | boolean) {
22 | console.log('do..')
23 | }
24 | getOptions(): IOption[] {
25 | return [
26 | { value: 'a', text: 'a' },
27 | { value: 'b', text: 'b' },
28 | ]
29 | }
30 | }
31 |
32 | registerMenu({
33 | key: 'mySelectMenu',
34 | factory() {
35 | return new MySelectMenu()
36 | },
37 | })
38 |
--------------------------------------------------------------------------------
/packages/core/__tests__/parse-html/README.md:
--------------------------------------------------------------------------------
1 | # parse-html test
2 |
3 | 各个 module `parseHtml` 已经测试了该模块的代码。
4 |
--------------------------------------------------------------------------------
/packages/core/__tests__/render/README.md:
--------------------------------------------------------------------------------
1 | # render test
2 |
3 | 各个 module `renderElem` 已经测试了该模块的代码。
4 |
--------------------------------------------------------------------------------
/packages/core/__tests__/to-html/README.md:
--------------------------------------------------------------------------------
1 | # to-html test
2 |
3 | 各个 module 中的 `editor.getHtml()` API 会测试到这部分代码。
4 |
--------------------------------------------------------------------------------
/packages/core/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
2 | import pkg from './package.json'
3 |
4 | const name = 'WangEditorCore'
5 |
6 | const configList = []
7 |
8 | // esm
9 | const esmConf = createRollupConfig({
10 | output: {
11 | file: pkg.module,
12 | format: 'esm',
13 | name,
14 | },
15 | })
16 | configList.push(esmConf)
17 |
18 | // umd
19 | const umdConf = createRollupConfig({
20 | output: {
21 | file: pkg.main,
22 | format: 'umd',
23 | name,
24 | },
25 | })
26 | configList.push(umdConf)
27 |
28 | export default configList
29 |
--------------------------------------------------------------------------------
/packages/core/src/assets/bar.less:
--------------------------------------------------------------------------------
1 | @import "../../../vars.less"; // var and mixin
2 |
3 | .w-e-bar {
4 | background-color: @toolbar-bg-color;
5 | padding: 0 5px;
6 | font-size: @size;
7 | color: @toolbar-color;
8 |
9 | svg {
10 | width: @size;
11 | height: @size;
12 | fill: @toolbar-color;
13 | }
14 | }
15 | .w-e-bar-show {
16 | display: flex;
17 | }
18 | .w-e-bar-hidden {
19 | display: none;
20 | }
21 |
22 | .w-e-hover-bar {
23 | position: absolute;
24 | .shadowBordered();
25 | }
26 |
27 | .w-e-toolbar {
28 | flex-wrap: wrap;
29 | position: relative;
30 | }
31 |
--------------------------------------------------------------------------------
/packages/core/src/assets/common.less:
--------------------------------------------------------------------------------
1 | .w-e-text-container *,
2 | .w-e-toolbar * {
3 | padding: 0;
4 | margin: 0;
5 | box-sizing: border-box;
6 | outline: none;
7 | }
8 |
9 | .w-e-text-container {
10 | p, li, td, th, blockquote {
11 | line-height: 1.5;
12 | }
13 | }
14 |
15 | .w-e-toolbar * {
16 | line-height: 1.5;
17 | }
--------------------------------------------------------------------------------
/packages/core/src/assets/drop-panel.less:
--------------------------------------------------------------------------------
1 | @import "../../../vars.less"; // var and mixin
2 |
3 | .w-e-drop-panel {
4 | z-index: 1;
5 | background-color: @toolbar-bg-color;
6 | position: absolute;
7 | top: 0;
8 | .shadowBordered(10px);
9 | margin-top: @toolbar-height;
10 | min-width: 200px;
11 | padding: 10px;
12 | }
13 |
14 | // 当 bar 处于页面下方,则 dropPanel 要显示在 bar 上方
15 | .w-e-bar-bottom .w-e-drop-panel {
16 | top: inherit;
17 | bottom: 0;
18 | margin-top: 0;
19 | margin-bottom: @toolbar-height;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/assets/full-screen.less:
--------------------------------------------------------------------------------
1 | .w-e-full-screen-container {
2 | position: fixed;
3 | margin: 0 !important;
4 | padding: 0 !important;
5 | top: 0 !important;
6 | left: 0 !important;
7 | right: 0 !important;
8 | bottom: 0 !important;
9 | height: 100% !important;
10 | width: 100% !important;
11 | display: flex !important;
12 | flex-direction: column !important;
13 |
14 | // [data-w-e-toolbar="true"] {
15 | // }
16 |
17 | [data-w-e-textarea="true"] {
18 | flex: 1 !important;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/assets/index.less:
--------------------------------------------------------------------------------
1 | @import "common.less";
2 | @import "textarea.less";
3 | @import "bar.less";
4 | @import "bar-item.less";
5 | @import "select-list.less";
6 | @import "drop-panel.less";
7 | @import "modal.less";
8 | @import "progress.less";
9 | @import "full-screen.less";
10 |
--------------------------------------------------------------------------------
/packages/core/src/assets/progress.less:
--------------------------------------------------------------------------------
1 | @import "../../../vars.less";
2 |
3 | .w-e-progress-bar {
4 | position: absolute;
5 | width: 0;
6 | height: 1px;
7 | background-color: @textarea-handler-bg-color;
8 | transition: width 0.3s;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/config/register.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description config register
3 | * @author wangfupeng
4 | */
5 |
6 | import { IMenuConfig, ISingleMenuConfig } from '../config/interface'
7 |
8 | // 全局的菜单配置
9 | export const GLOBAL_MENU_CONF: IMenuConfig = {}
10 |
11 | /**
12 | * 注册全局菜单配置
13 | * @param key menu key
14 | * @param config config
15 | */
16 | export function registerGlobalMenuConf(key: string, config?: ISingleMenuConfig) {
17 | if (config == null) return
18 | GLOBAL_MENU_CONF[key] = config
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | export const IGNORE_TAGS = new Set([
2 | 'doctype',
3 | '!doctype',
4 | 'meta',
5 | 'script',
6 | 'style',
7 | 'link',
8 | 'frame',
9 | 'iframe',
10 | 'title',
11 | 'svg', // TODO 暂时忽略
12 | ])
13 |
--------------------------------------------------------------------------------
/packages/core/src/create/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description create entry
3 | * @author wangfupeng
4 | */
5 |
6 | import coreCreateEditor from './create-editor'
7 | import coreCreateToolbar from './create-toolbar'
8 |
9 | export { coreCreateEditor, coreCreateToolbar }
10 |
--------------------------------------------------------------------------------
/packages/core/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n entry
3 | * @author wangfupeng
4 | */
5 |
6 | import i18next from 'i18next'
7 |
8 | // i18n nameSpace
9 | const NS = 'translation'
10 |
11 | i18next.init({
12 | lng: 'zh-CN',
13 | // debug: true,
14 | resources: {}, // 资源为空,随后添加
15 | })
16 |
17 | /**
18 | * 添加多语言配置
19 | * @param lng 语言
20 | * @param resources 多语言配置
21 | */
22 | export function i18nAddResources(lng: string, resources: object) {
23 | i18next.addResourceBundle(lng, NS, resources, true, true)
24 | }
25 |
26 | /**
27 | * 设置语言
28 | * @param lng 语言
29 | */
30 | export function i18nChangeLanguage(lng: string) {
31 | i18next.changeLanguage(lng)
32 | }
33 |
34 | /**
35 | * 获取多语言配置
36 | * @param lng lang
37 | */
38 | export function i18nGetResources(lng: string) {
39 | return i18next.getResourceBundle(lng, NS)
40 | }
41 |
42 | /**
43 | * 翻译
44 | */
45 | export const t = i18next.t.bind(i18next)
46 |
47 | export default i18next
48 |
--------------------------------------------------------------------------------
/packages/core/src/menus/README.md:
--------------------------------------------------------------------------------
1 | # menus
2 |
3 | 统一注册 menu ,menu 支持
4 | - classic toolbar
5 | - hovering toolbar
6 | - tooltip
7 | - contextMenu
8 |
--------------------------------------------------------------------------------
/packages/core/src/menus/bar-item/SimpleButton.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description button class
3 | * @author wangfupeng
4 | */
5 |
6 | import { IButtonMenu } from '../interface'
7 | import BaseButton from './BaseButton'
8 |
9 | class SimpleButton extends BaseButton {
10 | constructor(key: string, menu: IButtonMenu, inGroup = false) {
11 | super(key, menu, inGroup)
12 | }
13 | onButtonClick() {
14 | // menu.exec 已经在 BaseButton 实现了
15 | // 所以,此处不用做任何逻辑
16 | }
17 | }
18 |
19 | export default SimpleButton
20 |
--------------------------------------------------------------------------------
/packages/core/src/menus/bar-item/tooltip.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description tooltip 功能
3 | * @author wangfupeng
4 | */
5 |
6 | import { Dom7Array } from '../../utils/dom'
7 | import { IS_APPLE } from '../../utils/ua'
8 |
9 | export function addTooltip(
10 | $button: Dom7Array,
11 | iconSvg: string,
12 | title: string,
13 | hotkey: string,
14 | inGroup = false
15 | ) {
16 | if (!iconSvg) {
17 | // 没有 icon 直接显示 title ,不用 tooltip
18 | return
19 | }
20 |
21 | if (hotkey) {
22 | const fnKey = IS_APPLE ? 'cmd' : 'ctrl' // mac OS 转换为 cmd ,windows 转换为 ctrl
23 | hotkey = hotkey.replace('mod', fnKey)
24 | }
25 |
26 | if (inGroup) {
27 | // in groupButton ,tooltip 只显示 快捷键
28 | if (hotkey) {
29 | $button.attr('data-tooltip', hotkey)
30 | $button.addClass('w-e-menu-tooltip-v5')
31 | $button.addClass('tooltip-right') // tooltip 显示在右侧
32 | }
33 | } else {
34 | // 非 in groupButton ,正常实现 tooltip
35 | const tooltip = hotkey ? `${title}\n${hotkey}` : title
36 | $button.attr('data-tooltip', tooltip)
37 | $button.addClass('w-e-menu-tooltip-v5')
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/core/src/menus/helpers/helpers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menu helpers
3 | * @author wangfupeng
4 | */
5 |
6 | import $, { Dom7Array } from '../../utils/dom'
7 | import { SVG_DOWN_ARROW } from '../../constants/svg'
8 |
9 | /**
10 | * 清理 svg 的样式
11 | * @param $elem svg elem
12 | */
13 | export function clearSvgStyle($elem: Dom7Array) {
14 | $elem.removeAttr('width')
15 | $elem.removeAttr('height')
16 | $elem.removeAttr('fill')
17 | $elem.removeAttr('class')
18 | $elem.removeAttr('t')
19 | $elem.removeAttr('p-id')
20 |
21 | const children = $elem.children()
22 | if (children.length) {
23 | clearSvgStyle(children)
24 | }
25 | }
26 |
27 | /**
28 | * 向下箭头 icon svg
29 | */
30 | export function gen$downArrow() {
31 | const $downArrow = $(SVG_DOWN_ARROW)
32 | return $downArrow
33 | }
34 |
35 | /**
36 | * bar item 分割线
37 | */
38 | export function gen$barItemDivider() {
39 | return $('')
40 | }
41 |
--------------------------------------------------------------------------------
/packages/core/src/menus/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menus entry
3 | * @author wangfupeng
4 | */
5 |
6 | import Toolbar from './bar/Toolbar'
7 |
8 | // 注册
9 | export { registerMenu } from './register'
10 |
11 | // menu 相关接口
12 | export {
13 | IButtonMenu,
14 | ISelectMenu,
15 | IDropPanelMenu,
16 | IModalMenu,
17 | IRegisterMenuConf,
18 | IOption,
19 | } from './interface'
20 |
21 | // 输出 modal 相关方法
22 | export {
23 | genModalInputElems,
24 | genModalButtonElems,
25 | genModalTextareaElems,
26 | } from './panel-and-modal/Modal'
27 |
28 | export { Toolbar }
29 |
--------------------------------------------------------------------------------
/packages/core/src/menus/panel-and-modal/DropPanel.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description dropPanel class
3 | * @author wangfupeng
4 | */
5 |
6 | import { IDomEditor } from '../../editor/interface'
7 | import $, { Dom7Array } from '../../utils/dom'
8 | import PanelAndModal from './BaseClass'
9 |
10 | class DropPanel extends PanelAndModal {
11 | type = 'dropPanel'
12 | readonly $elem: Dom7Array = $(``)
13 |
14 | constructor(editor: IDomEditor) {
15 | super(editor)
16 | }
17 |
18 | genSelfElem(): Dom7Array | null {
19 | return null
20 | }
21 | }
22 |
23 | export default DropPanel
24 |
--------------------------------------------------------------------------------
/packages/core/src/menus/register.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description register menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { MenuFactoryType, IRegisterMenuConf } from './interface'
7 | import { registerGlobalMenuConf } from '../config/register'
8 |
9 | // menu item 的工厂函数 - 集合
10 | export const MENU_ITEM_FACTORIES: {
11 | [key: string]: MenuFactoryType
12 | } = {}
13 |
14 | /**
15 | * 注册菜单配置
16 | * @param registerMenuConf { key, factory, config } ,各个 menu key 不能重复
17 | * @param customConfig 自定义 menu config
18 | */
19 | export function registerMenu(
20 | registerMenuConf: IRegisterMenuConf,
21 | customConfig?: { [key: string]: any }
22 | ) {
23 | const { key, factory, config } = registerMenuConf
24 |
25 | // 合并 config
26 | const newConfig = { ...config, ...(customConfig || {}) }
27 |
28 | // 注册 menu
29 | if (MENU_ITEM_FACTORIES[key] != null) {
30 | throw new Error(`Duplicated key '${key}' in menu items`)
31 | }
32 | MENU_ITEM_FACTORIES[key] = factory
33 |
34 | // 将 config 保存到全局
35 | registerGlobalMenuConf(key, newConfig)
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/parse-html/README.md:
--------------------------------------------------------------------------------
1 | # parse html
2 |
3 | 把 html 转换为 JSON content
4 |
--------------------------------------------------------------------------------
/packages/core/src/parse-html/helper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description parse-html helper fns
3 | * @author wangfupeng
4 | */
5 |
6 | const REPLACE_SPACE_160_REG = new RegExp(String.fromCharCode(160), 'g')
7 |
8 | /**
9 | * 把 charCode 160 的空格(` ` 转换的),替换为 charCode 32 的空格(JS 默认的)
10 | * @param str str
11 | * @returns str
12 | */
13 | export function replaceSpace160(str: string): string {
14 | const res = str.replace(REPLACE_SPACE_160_REG, ' ')
15 | return res
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/render/README.md:
--------------------------------------------------------------------------------
1 | # render
2 |
3 | 把 JSON content 转换为 vdom
4 |
--------------------------------------------------------------------------------
/packages/core/src/render/element/getRenderElem.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 获取 elem render 函数
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element as SlateElement } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { IDomEditor } from '../../editor/interface'
9 | import { RENDER_ELEM_CONF, RenderElemFnType } from '../index'
10 |
11 | /**
12 | * 默认的 render elem
13 | * @param elemNode elem
14 | * @param editor editor
15 | * @param children children vnode
16 | * @returns vnode
17 | */
18 | function defaultRender(
19 | elemNode: SlateElement,
20 | children: VNode[] | null,
21 | editor: IDomEditor
22 | ): VNode {
23 | const Tag = editor.isInline(elemNode) ? 'span' : 'div'
24 |
25 | const vnode = {children}
26 |
27 | return vnode
28 | }
29 |
30 | /**
31 | * 根据 elemNode.type 获取 renderElement 函数
32 | * @param type elemNode.type
33 | */
34 | function getRenderElem(type: string): RenderElemFnType {
35 | const fn = RENDER_ELEM_CONF[type]
36 | return fn || defaultRender
37 | }
38 |
39 | export default getRenderElem
40 |
--------------------------------------------------------------------------------
/packages/core/src/render/element/renderStyle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 添加文本相关的样式
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element as SlateElement } from 'slate'
7 | import { VNode } from 'snabbdom'
8 | import { RENDER_STYLE_HANDLER_LIST } from '../index'
9 |
10 | /**
11 | * 渲染样式
12 | * @param elem slate elem node
13 | * @param vnode elem Vnode
14 | */
15 | function renderStyle(elem: SlateElement, vnode: VNode): VNode {
16 | let newVnode = vnode
17 |
18 | RENDER_STYLE_HANDLER_LIST.forEach(styleHandler => {
19 | newVnode = styleHandler(elem, vnode)
20 | })
21 |
22 | return newVnode
23 | }
24 |
25 | export default renderStyle
26 |
--------------------------------------------------------------------------------
/packages/core/src/render/helper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description formats helper
3 | * @author wangfupeng
4 | */
5 |
6 | export function genElemId(id: string) {
7 | return `w-e-element-${id}`
8 | }
9 |
10 | export function genTextId(id: string) {
11 | return `w-e-text-${id}`
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/src/render/text/renderStyle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description text 样式
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text as SlateText } from 'slate'
7 | import { VNode } from 'snabbdom'
8 | import { RENDER_STYLE_HANDLER_LIST } from '../index'
9 |
10 | /**
11 | * 给字符串增加样式
12 | * @param leafNode slate text leaf node
13 | * @param textVnode textVnode
14 | */
15 | function addTextVnodeStyle(leafNode: SlateText, textVnode: VNode): VNode {
16 | let newTextVnode = textVnode
17 |
18 | RENDER_STYLE_HANDLER_LIST.forEach(styleHandler => {
19 | newTextVnode = styleHandler(leafNode, newTextVnode)
20 | })
21 |
22 | return newTextVnode
23 | }
24 |
25 | export default addTextVnodeStyle
26 |
--------------------------------------------------------------------------------
/packages/core/src/text-area/event-handlers/copy.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 处理 copy 事件
3 | * @author wangfupeng
4 | */
5 |
6 | import { IDomEditor } from '../../editor/interface'
7 | // import { DomEditor } from '../../editor/dom-editor'
8 | import TextArea from '../TextArea'
9 | import { hasEditableTarget } from '../helpers'
10 |
11 | function handleOnCopy(e: Event, textarea: TextArea, editor: IDomEditor) {
12 | const event = e as ClipboardEvent
13 |
14 | if (!hasEditableTarget(editor, event.target)) return
15 | event.preventDefault()
16 |
17 | const data = event.clipboardData
18 | if (data == null) return
19 | editor.setFragmentData(data)
20 | }
21 |
22 | export default handleOnCopy
23 |
--------------------------------------------------------------------------------
/packages/core/src/text-area/event-handlers/focus.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 处理 onfocus 事件
3 | * @author wangfupeng
4 | */
5 |
6 | import { IDomEditor } from '../../editor/interface'
7 | import { DomEditor } from '../../editor/dom-editor'
8 | import TextArea from '../TextArea'
9 | import { IS_FIREFOX } from '../../utils/ua'
10 | import { IS_FOCUSED } from '../../utils/weak-maps'
11 |
12 | function handleOnFocus(event: Event, textarea: TextArea, editor: IDomEditor) {
13 | const el = DomEditor.toDOMNode(editor, editor)
14 | const root = DomEditor.findDocumentOrShadowRoot(editor)
15 | textarea.latestElement = root.activeElement
16 |
17 | // COMPAT: If the editor has nested editable elements, the focus
18 | // can go to them. In Firefox, this must be prevented because it
19 | // results in issues with keyboard navigation. (2017/03/30)
20 | if (IS_FIREFOX && event.target !== el) {
21 | el.focus()
22 | return
23 | }
24 |
25 | IS_FOCUSED.set(editor, true)
26 | }
27 |
28 | export default handleOnFocus
29 |
--------------------------------------------------------------------------------
/packages/core/src/text-area/event-handlers/keypress.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 监听 keypress 事件
3 | * @author wangfupeng
4 | */
5 |
6 | import { Editor } from 'slate'
7 | import { IDomEditor } from '../../editor/interface'
8 | import TextArea from '../TextArea'
9 | import { HAS_BEFORE_INPUT_SUPPORT } from '../../utils/ua'
10 | import { hasEditableTarget } from '../helpers'
11 |
12 | // 【注意】虽然 keypress 事件已经过时(建议用 keydown 取代),但这里是为了兼容 beforeinput ,所以不会在高级浏览器生效,不用升级 keydown
13 |
14 | function handleKeypress(event: Event, textarea: TextArea, editor: IDomEditor) {
15 | // 这里是兼容不完全支持 beforeInput 的浏览器。对于支持 beforeInput 的浏览器,会用 beforeinput 事件处理
16 | if (HAS_BEFORE_INPUT_SUPPORT) return
17 |
18 | const { readOnly } = editor.getConfig()
19 | if (readOnly) return
20 | if (!hasEditableTarget(editor, event.target)) return
21 |
22 | event.preventDefault()
23 |
24 | const text = (event as any).key as string
25 |
26 | // 这里只兼容 beforeInput 的 insertText 类型,其他的(如删除、换行)使用 keydown 来兼容
27 | Editor.insertText(editor, text)
28 | }
29 |
30 | export default handleKeypress
31 |
--------------------------------------------------------------------------------
/packages/core/src/to-html/README.md:
--------------------------------------------------------------------------------
1 | # to html
2 |
3 | 把 content 为 html
4 |
--------------------------------------------------------------------------------
/packages/core/src/to-html/node2html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description node -> html
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element, Descendant } from 'slate'
7 | import { IDomEditor } from '../editor/interface'
8 | import elemToHtml from './elem2html'
9 | import textToHtml from './text2html'
10 |
11 | function node2html(node: Descendant, editor: IDomEditor): string {
12 | if (Element.isElement(node)) {
13 | // elem node
14 | return elemToHtml(node, editor)
15 | } else {
16 | // text node
17 | return textToHtml(node, editor)
18 | }
19 | }
20 |
21 | export default node2html
22 |
--------------------------------------------------------------------------------
/packages/core/src/upload/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description upload entry
3 | * @author wangfupeng
4 | */
5 |
6 | import createUploader from './createUploader'
7 | import { IUploadConfig } from './interface'
8 |
9 | export { createUploader, IUploadConfig }
10 |
11 | // TODO upload 能力,写到文档中,二次开发使用
12 |
--------------------------------------------------------------------------------
/packages/core/src/upload/interface.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description upload interface
3 | * @author wangfupeng
4 | */
5 |
6 | import { UppyFile } from '@uppy/core'
7 |
8 | type FilesType = { [key: string]: UppyFile<{}, {}> }
9 |
10 | /**
11 | * 配置参考 https://uppy.io/docs/uppy/
12 | */
13 | export interface IUploadConfig {
14 | server: string
15 | fieldName?: string
16 | maxFileSize?: number
17 | maxNumberOfFiles?: number
18 | meta?: Record
19 | metaWithUrl: boolean
20 | headers?:
21 | | Headers
22 | | ((file: UppyFile, Record>) => Headers)
23 | | undefined
24 | withCredentials?: boolean
25 | timeout?: number
26 | onBeforeUpload?: (files: FilesType) => boolean | FilesType
27 | onSuccess: (file: UppyFile<{}, {}>, response: any) => void
28 | onProgress?: (progress: number) => void
29 | onFailed: (file: UppyFile<{}, {}>, response: any) => void
30 | onError: (file: UppyFile<{}, {}>, error: any, res: any) => void
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/src/utils/key.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * An auto-incrementing identifier for keys.
3 | */
4 |
5 | let n = 0
6 |
7 | /**
8 | * A class that keeps track of a key string. We use a full class here because we
9 | * want to be able to use them as keys in `WeakMap` objects.
10 | */
11 | export class Key {
12 | id: string
13 |
14 | constructor() {
15 | this.id = `${n++}`
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {},
3 | "extends": "../../tsconfig.json",
4 | "include": [
5 | "./src/**/*",
6 | "../custom-types.d.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/packages/editor/README-en.md:
--------------------------------------------------------------------------------
1 | # wangEditor editor
2 |
3 | [中文](./README.md)
4 |
5 | Open source web rich text editor, run right out of the box. Support JS Vue React.
6 |
7 | - [Document](https://www.wangeditor.com/en/)
8 | - [Demo](https://www.wangeditor.com/demo/?lang=en)
9 |
10 | 
11 |
12 | You can [commit an issue]((https://github.com/wangeditor-team/wangEditor/issues)) if you have any question.
13 |
--------------------------------------------------------------------------------
/packages/editor/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor editor
2 |
3 | [English](./README-en.md)
4 |
5 | 开源 Web 富文本编辑器,开箱即用,配置简单。支持 JS Vue React 。
6 |
7 | - [文档](https://www.wangeditor.com/)
8 | - [demo](https://www.wangeditor.com/demo/)
9 |
10 | 
11 |
12 | 交流
13 | - [提交问题和建议](https://github.com/wangeditor-team/wangEditor/issues)
14 | - 加入 QQ 群([官网](https://www.wangeditor.com/)有群号)
15 |
--------------------------------------------------------------------------------
/packages/editor/demo/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor demo
2 |
3 | 修改左侧目录,在 demo 目录搜索 `MENU_CONF`
4 |
5 | demo 部署参考 `deploy-demos.yml` 配置
6 |
--------------------------------------------------------------------------------
/packages/editor/demo/css/layout.css:
--------------------------------------------------------------------------------
1 | /* body {
2 | margin: 20px;
3 | } */
4 |
5 | .page-container {
6 | margin-top: 15px;
7 | display: flex;
8 | }
9 |
10 | .page-left {
11 | width: 150px;
12 | padding: 0 10px;
13 | }
14 |
15 | .page-right {
16 | padding: 0 10px;
17 | flex: 1;
18 | width: calc(100vw - 170px);
19 | }
--------------------------------------------------------------------------------
/packages/editor/examples/README.md:
--------------------------------------------------------------------------------
1 | # examples
2 |
3 | - 本地测试
4 | - 提交 `master` 会发布到测试机
5 |
--------------------------------------------------------------------------------
/packages/editor/examples/css/editor.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0 10px;
3 | }
4 |
5 | .editor-toolbar {
6 | border: 1px solid #ccc;
7 | }
8 |
9 | .editor-text-area {
10 | border: 1px solid #ccc;
11 | border-top: 0;
12 | height: 400px;
13 | }
--------------------------------------------------------------------------------
/packages/editor/examples/css/view.css:
--------------------------------------------------------------------------------
1 | .editor-content-view {
2 | border: 1px solid #ccc;
3 | padding: 10px;
4 | margin-top: 30px;
5 | overflow-x: auto;
6 | }
7 |
8 | .editor-content-view p,
9 | .editor-content-view li {
10 | white-space: pre-wrap; /* 保留空格 */
11 | }
12 |
13 | .editor-content-view blockquote {
14 | border-left: 8px solid #d0e5f2;
15 | padding: 10px 10px;
16 | margin: 10px 0;
17 | background-color: #f1f1f1;
18 | }
19 |
20 | .editor-content-view code {
21 | font-family: monospace;
22 | background-color: #eee;
23 | padding: 3px;
24 | border-radius: 3px;
25 | }
26 | .editor-content-view pre>code {
27 | display: block;
28 | padding: 10px;
29 | }
30 |
31 | .editor-content-view table {
32 | border-collapse: collapse;
33 | }
34 | .editor-content-view td,
35 | .editor-content-view th {
36 | border: 1px solid #ccc;
37 | min-width: 50px;
38 | height: 20px;
39 | }
40 | .editor-content-view th {
41 | background-color: #f1f1f1;
42 | }
--------------------------------------------------------------------------------
/packages/editor/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangeditor-team/wangEditor/f35d8a73a0a3dc159134d483afc5963d0256ea18/packages/editor/favicon.ico
--------------------------------------------------------------------------------
/packages/editor/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig, IS_PRD, IS_DEV } from '../../build/create-rollup-config'
2 | import pkg from './package.json'
3 |
4 | const name = 'wangEditor'
5 |
6 | const configList = []
7 |
8 | // umd
9 | const umdConf = createRollupConfig({
10 | output: {
11 | file: pkg.main,
12 | format: 'umd',
13 | name,
14 | },
15 | })
16 | configList.push(umdConf)
17 |
18 | // esm
19 | const esmConf = createRollupConfig({
20 | output: {
21 | file: pkg.module,
22 | format: 'esm',
23 | name,
24 | },
25 | })
26 | configList.push(esmConf)
27 |
28 | export default configList
29 |
--------------------------------------------------------------------------------
/packages/editor/src/assets/index.less:
--------------------------------------------------------------------------------
1 | // 集中定义 css vars ,否则会被重复定义多次
2 | :root, :host {
3 | // textarea - css vars
4 | --w-e-textarea-bg-color: #fff;
5 | --w-e-textarea-color: #333;
6 | --w-e-textarea-border-color: #ccc;
7 | --w-e-textarea-slight-border-color: #e8e8e8;
8 | --w-e-textarea-slight-color: #d4d4d4;
9 | --w-e-textarea-slight-bg-color: #f5f2f0;
10 | --w-e-textarea-selected-border-color: #B4D5FF; // 选中的元素,如选中了分割线
11 | --w-e-textarea-handler-bg-color: #4290f7; // 工具,如图片拖拽按钮
12 |
13 | // toolbar - css vars
14 | --w-e-toolbar-color: #595959;
15 | --w-e-toolbar-bg-color: #fff;
16 | --w-e-toolbar-active-color: #333;
17 | --w-e-toolbar-active-bg-color: #f1f1f1;
18 | --w-e-toolbar-disabled-color: #999;
19 | --w-e-toolbar-border-color: #e8e8e8;
20 |
21 | // modal - css vars
22 | --w-e-modal-button-bg-color: #fafafa;
23 | --w-e-modal-button-border-color: #d9d9d9;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/editor/src/init-default-config/config/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 获取编辑器默认配置
3 | * @author wangfupeng
4 | */
5 |
6 | import { genDefaultToolbarKeys, genSimpleToolbarKeys } from './toolbar'
7 | import { genDefaultHoverbarKeys, genSimpleHoverbarKeys } from './hoverbar'
8 |
9 | export function getDefaultEditorConfig() {
10 | return {
11 | hoverbarKeys: genDefaultHoverbarKeys(),
12 | }
13 | }
14 |
15 | export function getSimpleEditorConfig() {
16 | return {
17 | hoverbarKeys: genSimpleHoverbarKeys(),
18 | }
19 | }
20 |
21 | export function getDefaultToolbarConfig() {
22 | return {
23 | toolbarKeys: genDefaultToolbarKeys(),
24 | }
25 | }
26 |
27 | export function getSimpleToolbarConfig() {
28 | return {
29 | toolbarKeys: genSimpleToolbarKeys(),
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/editor/src/init-default-config/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description set default config
3 | * @author wangfupeng
4 | */
5 |
6 | import Boot from '../Boot'
7 | import {
8 | getDefaultEditorConfig,
9 | getDefaultToolbarConfig,
10 | getSimpleEditorConfig,
11 | getSimpleToolbarConfig,
12 | } from './config'
13 |
14 | import { wangEditorCodeHighLightDecorate } from '@wangeditor/code-highlight'
15 |
16 | const defaultEditorConfig = getDefaultEditorConfig()
17 | Boot.setEditorConfig({
18 | ...defaultEditorConfig,
19 | decorate: wangEditorCodeHighLightDecorate, // 代码高亮
20 | })
21 |
22 | const simpleEditorConfig = getSimpleEditorConfig()
23 | Boot.setSimpleEditorConfig({
24 | ...simpleEditorConfig,
25 | decorate: wangEditorCodeHighLightDecorate, // 代码高亮
26 | })
27 |
28 | const defaultToolbarConfig = getDefaultToolbarConfig()
29 | Boot.setToolbarConfig(defaultToolbarConfig)
30 |
31 | const simpleToolbarConfig = getSimpleToolbarConfig()
32 | Boot.setSimpleToolbarConfig(simpleToolbarConfig)
33 |
--------------------------------------------------------------------------------
/packages/editor/src/locale/en.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n en
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | editor: {
8 | more: 'More',
9 | justify: 'Justify',
10 | indent: 'Indent',
11 | image: 'Image',
12 | video: 'Video',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/packages/editor/src/locale/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { i18nAddResources } from '@wangeditor/core'
7 | import enResources from './en'
8 | import zhResources from './zh-CN'
9 |
10 | i18nAddResources('en', enResources)
11 | i18nAddResources('zh-CN', zhResources)
12 |
--------------------------------------------------------------------------------
/packages/editor/src/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n zh-CN
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | editor: {
8 | more: '更多',
9 | justify: '对齐',
10 | indent: '缩进',
11 | image: '图片',
12 | video: '视频',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/packages/editor/src/utils/dom.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description dom utils
3 | * @author wangfupeng
4 | */
5 |
6 | import DOMElement = globalThis.Element
7 |
8 | export { DOMElement }
9 |
--------------------------------------------------------------------------------
/packages/editor/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": [
4 | "./src/**/*",
5 | "../custom-types.d.ts"
6 | ]
7 | }
--------------------------------------------------------------------------------
/packages/list-module/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [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)
7 |
8 |
9 | ### Bug Fixes
10 |
11 | * list-item - 遇到 style 是 toHtml 出错 ([9854308](https://github.com/wangeditor-team/wangEditor/commit/98543083a1cb09207aceb2a4d8f3c1ce020b106d))
12 |
13 |
14 |
15 |
16 |
17 | ## [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)
18 |
19 | **Note:** Version bump only for package @wangeditor/list-module
20 |
--------------------------------------------------------------------------------
/packages/list-module/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor list-module
2 |
3 | List module built in [wangEditor](https://www.wangeditor.com/) by default.
4 |
--------------------------------------------------------------------------------
/packages/list-module/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
2 | import pkg from './package.json'
3 |
4 | const name = 'WangEditorListModule'
5 |
6 | const configList = []
7 |
8 | // esm - 开发环境不需要 CDN 引入,只需要 npm 引入,所以优先输出 esm
9 | const esmConf = createRollupConfig({
10 | output: {
11 | file: pkg.module,
12 | format: 'esm',
13 | name,
14 | },
15 | })
16 | configList.push(esmConf)
17 |
18 | // umd
19 | const umdConf = createRollupConfig({
20 | output: {
21 | file: pkg.main,
22 | format: 'umd',
23 | name,
24 | },
25 | })
26 | configList.push(umdConf)
27 |
28 | export default configList
29 |
--------------------------------------------------------------------------------
/packages/list-module/src/assets/index.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangeditor-team/wangEditor/f35d8a73a0a3dc159134d483afc5963d0256ea18/packages/list-module/src/assets/index.less
--------------------------------------------------------------------------------
/packages/list-module/src/constants/svg.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description icon svg
3 | * @author wangfupeng
4 | */
5 |
6 | /**
7 | * 【注意】svg 字符串的长度 ,否则会导致代码体积过大
8 | * 尽量选择 https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=20293
9 | * 找不到再从 iconfont.com 搜索
10 | */
11 |
12 | // 无序列表
13 | export const BULLETED_LIST_SVG =
14 | ''
15 |
16 | // 有序列表
17 | export const NUMBERED_LIST_SVG =
18 | ''
19 |
--------------------------------------------------------------------------------
/packages/list-module/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description list module
3 | * @author wangfupeng
4 | */
5 |
6 | import './assets/index.less'
7 |
8 | // 配置多语言
9 | import './locale/index'
10 |
11 | // 导出 module
12 | import wangEditorListModule from './module/index'
13 | export default wangEditorListModule
14 |
--------------------------------------------------------------------------------
/packages/list-module/src/locale/en.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n en
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | listModule: {
8 | unOrderedList: 'Unordered list',
9 | orderedList: 'Ordered list',
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/packages/list-module/src/locale/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { i18nAddResources } from '@wangeditor/core'
7 | import enResources from './en'
8 | import zhResources from './zh-CN'
9 |
10 | i18nAddResources('en', enResources)
11 | i18nAddResources('zh-CN', zhResources)
12 |
--------------------------------------------------------------------------------
/packages/list-module/src/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n zh-CN
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | listModule: {
8 | unOrderedList: '无序列表',
9 | orderedList: '有序列表',
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/packages/list-module/src/module/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description list element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type ListItemElement = {
11 | type: 'list-item'
12 | ordered: boolean // 有序/无序
13 | level: number // 层级:0 1 2 ...
14 | children: Text[]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/list-module/src/module/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description list module entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import renderListItemConf from './render-elem'
8 | import withList from './plugin'
9 | import { bulletedListMenuConf, numberedListMenuConf } from './menu/index'
10 | import listItemToHtmlConf from './elem-to-html'
11 | import { parseItemHtmlConf, parseListHtmlConf } from './parse-elem-html'
12 |
13 | const list: Partial = {
14 | renderElems: [renderListItemConf],
15 | editorPlugin: withList,
16 | menus: [bulletedListMenuConf, numberedListMenuConf],
17 | elemsToHtml: [listItemToHtmlConf],
18 | parseElemsHtml: [parseListHtmlConf, parseItemHtmlConf],
19 | }
20 |
21 | export default list
22 |
--------------------------------------------------------------------------------
/packages/list-module/src/module/menu/BulletedListMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description bulleted list menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { BULLETED_LIST_SVG } from '../../constants/svg'
9 |
10 | class BulletedListMenu extends BaseMenu {
11 | readonly ordered = false
12 | readonly title = t('listModule.unOrderedList')
13 | readonly iconSvg = BULLETED_LIST_SVG
14 | }
15 |
16 | export default BulletedListMenu
17 |
--------------------------------------------------------------------------------
/packages/list-module/src/module/menu/NumberedListMenu.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description numbered list menu
3 | * @author wangfupeng
4 | */
5 |
6 | import { t } from '@wangeditor/core'
7 | import BaseMenu from './BaseMenu'
8 | import { NUMBERED_LIST_SVG } from '../../constants/svg'
9 |
10 | class NumberedListMenu extends BaseMenu {
11 | readonly ordered = true
12 | readonly title = t('listModule.orderedList')
13 | readonly iconSvg = NUMBERED_LIST_SVG
14 | }
15 |
16 | export default NumberedListMenu
17 |
--------------------------------------------------------------------------------
/packages/list-module/src/module/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description menu entry
3 | * @author wangfupeng
4 | */
5 |
6 | import BulletedListMenu from './BulletedListMenu'
7 | import NumberedListMenu from './NumberedListMenu'
8 |
9 | export const bulletedListMenuConf = {
10 | key: 'bulletedList',
11 | factory() {
12 | return new BulletedListMenu()
13 | },
14 | }
15 |
16 | export const numberedListMenuConf = {
17 | key: 'numberedList',
18 | factory() {
19 | return new NumberedListMenu()
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/packages/list-module/src/utils/maps.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description maps
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element as SlateElement } from 'slate'
7 | import { IDomEditor } from '@wangeditor/core'
8 |
9 | export const ELEM_TO_EDITOR = new WeakMap()
10 |
--------------------------------------------------------------------------------
/packages/list-module/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {},
4 | "include": [
5 | "./src/**/*",
6 | "../custom-types.d.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/packages/table-module/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor table-module
2 |
3 | Table module built in [wangEditor](https://www.wangeditor.com/) by default.
4 |
--------------------------------------------------------------------------------
/packages/table-module/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
2 | import pkg from './package.json'
3 |
4 | const name = 'WangEditorTableModule'
5 |
6 | const configList = []
7 |
8 | // esm
9 | const esmConf = createRollupConfig({
10 | output: {
11 | file: pkg.module,
12 | format: 'esm',
13 | name,
14 | },
15 | })
16 | configList.push(esmConf)
17 |
18 | // umd
19 | const umdConf = createRollupConfig({
20 | output: {
21 | file: pkg.main,
22 | format: 'umd',
23 | name,
24 | },
25 | })
26 | configList.push(umdConf)
27 |
28 | export default configList
29 |
--------------------------------------------------------------------------------
/packages/table-module/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description table entry
3 | * @author wangfupeng
4 | */
5 |
6 | import './assets/index.less'
7 |
8 | // 配置多语言
9 | import './locale/index'
10 |
11 | import wangEditorTableModule from './module/index'
12 | export default wangEditorTableModule
13 |
--------------------------------------------------------------------------------
/packages/table-module/src/locale/en.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n en
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | tableModule: {
8 | deleteCol: 'Delete column',
9 | deleteRow: 'Delete row',
10 | deleteTable: 'Delete table',
11 | widthAuto: 'Width auto',
12 | insertCol: 'Insert column',
13 | insertRow: 'Insert row',
14 | insertTable: 'Insert table',
15 | header: 'Header',
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/packages/table-module/src/locale/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { i18nAddResources } from '@wangeditor/core'
7 | import enResources from './en'
8 | import zhResources from './zh-CN'
9 |
10 | i18nAddResources('en', enResources)
11 | i18nAddResources('zh-CN', zhResources)
12 |
--------------------------------------------------------------------------------
/packages/table-module/src/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n zh-CN
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | tableModule: {
8 | deleteCol: '删除列',
9 | deleteRow: '删除行',
10 | deleteTable: '删除表格',
11 | widthAuto: '宽度自适应',
12 | insertCol: '插入列',
13 | insertRow: '插入行',
14 | insertTable: '插入表格',
15 | header: '表头',
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/packages/table-module/src/module/custom-types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 自定义 element
3 | * @author wangfupeng
4 | */
5 |
6 | import { Text } from 'slate'
7 |
8 | //【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts
9 |
10 | export type TableCellElement = {
11 | type: 'table-cell'
12 | isHeader?: boolean // td/th 只作用于第一行
13 | colSpan?: number
14 | rowSpan?: number
15 | width?: string // 只作用于第一行(尚未考虑单元格合并!)
16 | children: Text[]
17 | }
18 |
19 | export type TableRowElement = {
20 | type: 'table-row'
21 | children: TableCellElement[]
22 | }
23 |
24 | export type TableElement = {
25 | type: 'table'
26 | width: string
27 | children: TableRowElement[]
28 | }
29 |
--------------------------------------------------------------------------------
/packages/table-module/src/module/pre-parse-html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description pre parse html
3 | * @author wangfupeng
4 | */
5 |
6 | import $, { getTagName, DOMElement } from '../utils/dom'
7 |
8 | /**
9 | * pre-prase table ,去掉
10 | * @param table table elem
11 | */
12 | function preParse(tableElem: DOMElement): DOMElement {
13 | const $table = $(tableElem)
14 | const tagName = getTagName($table)
15 | if (tagName !== 'table') return tableElem
16 |
17 | // 没有 则直接返回
18 | const $tbody = $table.find('tbody')
19 | if ($tbody.length === 0) return tableElem
20 |
21 | // 去掉 ,把 移动到 下面
22 | const $tr = $table.find('tr')
23 | $table.append($tr)
24 | $tbody.remove()
25 |
26 | return $table[0]
27 | }
28 |
29 | export const preParseTableHtmlConf = {
30 | selector: 'table',
31 | preParseHtml: preParse,
32 | }
33 |
--------------------------------------------------------------------------------
/packages/table-module/src/module/render-elem/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render elem
3 | * @author wangfupeng
4 | */
5 |
6 | import renderTable from './render-table'
7 | import renderTableRow from './render-row'
8 | import renderTableCell from './render-cell'
9 |
10 | export const renderTableConf = {
11 | type: 'table',
12 | renderElem: renderTable,
13 | }
14 |
15 | export const renderTableRowConf = {
16 | type: 'table-row',
17 | renderElem: renderTableRow,
18 | }
19 |
20 | export const renderTableCellConf = {
21 | type: 'table-cell',
22 | renderElem: renderTableCell,
23 | }
24 |
--------------------------------------------------------------------------------
/packages/table-module/src/module/render-elem/render-row.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @description render row
3 | * @author wangfupeng
4 | */
5 |
6 | import { Element as SlateElement } from 'slate'
7 | import { jsx, VNode } from 'snabbdom'
8 | import { IDomEditor } from '@wangeditor/core'
9 |
10 | function renderTableRow(
11 | elemNode: SlateElement,
12 | children: VNode[] | null,
13 | editor: IDomEditor
14 | ): VNode {
15 | const vnode = {children}
16 | return vnode
17 | }
18 |
19 | export default renderTableRow
20 |
--------------------------------------------------------------------------------
/packages/table-module/src/utils/util.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 工具函数
3 | * @author wangfupeng
4 | */
5 |
6 | import { nanoid } from 'nanoid'
7 |
8 | /**
9 | * 获取随机数字符串
10 | * @param prefix 前缀
11 | * @returns 随机数字符串
12 | */
13 | export function genRandomStr(prefix: string = 'r'): string {
14 | return `${prefix}-${nanoid()}`
15 | }
16 |
--------------------------------------------------------------------------------
/packages/table-module/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {},
3 | "extends": "../../tsconfig.json",
4 | "include": [
5 | "./src/**/*",
6 | "../custom-types.d.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/packages/upload-image-module/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor upload-image-module
2 |
3 | Upload image module built in [wangEditor](https://www.wangeditor.com/) by default.
4 |
--------------------------------------------------------------------------------
/packages/upload-image-module/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
2 | import pkg from './package.json'
3 |
4 | const name = 'WangEditorUploadImageModule'
5 |
6 | const configList = []
7 |
8 | // esm
9 | const esmConf = createRollupConfig({
10 | output: {
11 | file: pkg.module,
12 | format: 'esm',
13 | name,
14 | },
15 | })
16 | configList.push(esmConf)
17 |
18 | // umd
19 | const umdConf = createRollupConfig({
20 | output: {
21 | file: pkg.main,
22 | format: 'umd',
23 | name,
24 | },
25 | })
26 | configList.push(umdConf)
27 |
28 | export default configList
29 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/assets/index.less:
--------------------------------------------------------------------------------
1 | // styles
2 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description upload image
3 | * @author wangfupeng
4 | */
5 |
6 | import './assets/index.less'
7 |
8 | // 配置多语言
9 | import './locale/index'
10 |
11 | import wangEditorUploadImageModule from './module/index'
12 | export default wangEditorUploadImageModule
13 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/locale/en.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n en
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | uploadImgModule: {
8 | uploadImage: 'Upload Image',
9 | uploadError: '{{fileName}} upload error',
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/locale/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n entry
3 | * @author wangfupeng
4 | */
5 |
6 | import { i18nAddResources } from '@wangeditor/core'
7 | import enResources from './en'
8 | import zhResources from './zh-CN'
9 |
10 | i18nAddResources('en', enResources)
11 | i18nAddResources('zh-CN', zhResources)
12 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n zh-CN
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | uploadImgModule: {
8 | uploadImage: '上传图片',
9 | uploadError: '{{fileName}} 上传出错',
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/module/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description uploadImage module
3 | * @author wangfupeng
4 | */
5 |
6 | import { IModuleConf } from '@wangeditor/core'
7 | import withUploadImage from './plugin'
8 | import { uploadImageMenuConf } from './menu/index'
9 |
10 | const uploadImage: Partial = {
11 | menus: [uploadImageMenuConf],
12 | editorPlugin: withUploadImage,
13 | }
14 |
15 | export default uploadImage
16 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/module/menu/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description upload image menu
3 | * @author wangfupeng
4 | */
5 |
6 | import UploadImageMenu from './UploadImageMenu'
7 | import { genUploadImageConfig } from './config'
8 |
9 | export const uploadImageMenuConf = {
10 | key: 'uploadImage',
11 | factory() {
12 | return new UploadImageMenu()
13 | },
14 |
15 | // 默认的菜单菜单配置,将存储在 editorConfig.MENU_CONF[key] 中
16 | // 创建编辑器时,可通过 editorConfig.MENU_CONF[key] = {...} 来修改
17 | config: genUploadImageConfig(),
18 | }
19 |
--------------------------------------------------------------------------------
/packages/upload-image-module/src/utils/dom.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description DOM 操作
3 | * @author wangfupeng
4 | */
5 |
6 | import $, { append, on, remove, val, click, hide } from 'dom7'
7 | export { Dom7Array } from 'dom7'
8 |
9 | if (append) $.fn.append = append
10 | if (on) $.fn.on = on
11 | if (remove) $.fn.remove = remove
12 | if (val) $.fn.val = val
13 | if (click) $.fn.click = click
14 | if (hide) $.fn.hide = hide
15 |
16 | export default $
17 |
--------------------------------------------------------------------------------
/packages/upload-image-module/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {},
3 | "extends": "../../tsconfig.json",
4 | "include": [
5 | "./src/**/*",
6 | "../custom-types.d.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/packages/video-module/README.md:
--------------------------------------------------------------------------------
1 | # wangEditor video-module
2 |
3 | Video module built in [wangEditor](https://www.wangeditor.com/) by default.
4 |
--------------------------------------------------------------------------------
/packages/video-module/__tests__/render-elem.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description video render elem test
3 | * @author luochao
4 | */
5 |
6 | import createEditor from '../../../tests/utils/create-editor'
7 | import { renderVideoConf } from '../src/module/render-elem'
8 |
9 | describe('video module - render elem', () => {
10 | const editor = createEditor()
11 |
12 | it('render video elem', () => {
13 | expect(renderVideoConf.type).toBe('video')
14 |
15 | const elem = { type: 'video', src: 'test.mp4', poster: 'xxx.png', children: [] }
16 | const vnode = renderVideoConf.renderElem(elem, null, editor)
17 | expect(vnode.sel).toBe('div')
18 | })
19 |
20 | it('render video with iframe', () => {
21 | expect(renderVideoConf.type).toBe('video')
22 |
23 | const elem = { type: 'video', src: '', children: [] }
24 | const vnode = renderVideoConf.renderElem(elem, null, editor)
25 | expect(vnode.sel).toBe('div')
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/packages/video-module/__tests__/util.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description video menu test
3 | * @author luochao
4 | */
5 |
6 | import { genRandomStr } from '../src/utils/util'
7 |
8 | describe('videoModule util', () => {
9 | describe('utils util', () => {
10 | test('genRandomStr should generate a random string every time', () => {
11 | const str1 = genRandomStr()
12 | const str2 = genRandomStr()
13 |
14 | expect(str1).not.toBe(str2)
15 | })
16 |
17 | test('genRandomStr should generate a random string that specify a prefix string', () => {
18 | const str = genRandomStr('wangeditor')
19 |
20 | expect(str.indexOf('wangeditor-')).toEqual(0)
21 | })
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/packages/video-module/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config'
2 | import pkg from './package.json'
3 |
4 | const name = 'WangEditorVideoModule'
5 |
6 | const configList = []
7 |
8 | // esm
9 | const esmConf = createRollupConfig({
10 | output: {
11 | file: pkg.module,
12 | format: 'esm',
13 | name,
14 | },
15 | })
16 | configList.push(esmConf)
17 |
18 | // umd
19 | const umdConf = createRollupConfig({
20 | output: {
21 | file: pkg.main,
22 | format: 'umd',
23 | name,
24 | },
25 | })
26 | configList.push(umdConf)
27 |
28 | export default configList
29 |
--------------------------------------------------------------------------------
/packages/video-module/src/assets/index.less:
--------------------------------------------------------------------------------
1 | @import "../../../vars.less";
2 |
3 | .w-e-textarea-video-container {
4 | text-align: center;
5 | border: 1px dashed @textarea-border-color;
6 | padding: 10px 0;
7 | margin: 0 auto;
8 | margin-top: 10px;
9 | border-radius: 5px;
10 | background-position: 0px 0px, 10px 10px;
11 | background-size: 20px 20px;
12 | 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%);
13 | }
14 |
--------------------------------------------------------------------------------
/packages/video-module/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description video module
3 | * @author wangfupeng
4 | */
5 |
6 | import './assets/index.less'
7 |
8 | // 配置多语言
9 | import './locale/index'
10 |
11 | import wangEditorVideoModule from './module/index'
12 | export default wangEditorVideoModule
13 |
--------------------------------------------------------------------------------
/packages/video-module/src/locale/en.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description i18n en
3 | * @author wangfupeng
4 | */
5 |
6 | export default {
7 | videoModule: {
8 | delete: 'Delete',
9 | uploadVideo: 'Upload video',
10 | insertVideo: 'Insert video',
11 | videoSrc: 'Video source',
12 | videoSrcPlaceHolder: 'Video file url, or third-party