From cda20505ed20fc43703dbc4369fc5b1792270ccb Mon Sep 17 00:00:00 2001 From: yaolongfei <2991205548@qq.com> Date: Wed, 25 Sep 2024 18:49:06 +0800 Subject: [PATCH] refactor: upgrage ts\jest version --- .husky/pre-commit | 2 +- babel.config.js | 26 + babel.config.json | 26 - build/config/common.js | 4 +- build/config/dev.js | 7 +- build/config/prd.js | 12 +- build/create-rollup-config.js | 6 +- jest.config.cjs | 13 +- package.json | 20 +- .../blockquote/blockquote-menu.test.ts | 3 + .../__tests__/blockquote/elem-to-html.test.ts | 1 + .../__tests__/blockquote/parse-html.test.ts | 7 +- .../__tests__/blockquote/plugin.test.ts | 12 +- .../__tests__/blockquote/render-elem.test.ts | 1 + .../code-block/code-block-menu.test.ts | 7 +- .../__tests__/code-block/elem-to-html.test.ts | 2 + .../__tests__/code-block/parse-html.test.ts | 6 + .../__tests__/code-block/plugin.test.ts | 15 +- .../__tests__/code-block/render-elem.test.ts | 4 +- .../__tests__/color/color-menus.test.ts | 15 +- .../__tests__/color/parse-html.test.ts | 5 +- .../color/render-text-style.test.tsx | 2 + .../color/text-style-to-html.test.ts | 3 +- .../__tests__/common/enter-menu.test.ts | 1 + .../__tests__/divider/elem-to-html.test.ts | 1 + .../divider/insert-divider-menu.test.ts | 3 + .../__tests__/divider/parse-html.test.ts | 2 + .../__tests__/divider/plugin.test.ts | 6 + .../__tests__/divider/render-elem.test.ts | 3 + .../__tests__/emotion/emotion-menu.test.ts | 5 +- .../menu/font-family-menu.test.ts | 3 + .../menu/font-size-menu.test.ts | 3 + .../font-size-family/parse-html.test.ts | 3 + .../render-text-style.test.tsx | 2 + .../text-style-to-html.test.ts | 9 +- .../format-painter-menu.test.ts | 1 + .../__tests__/format-painter/helper.test.ts | 1 + .../__tests__/format-painter/plugin.test.ts | 2 +- .../full-screen/full-screen-menu.test.ts | 4 +- .../__tests__/header/elem-to-html.test.ts | 6 + .../__tests__/header/helper.test.ts | 2 + .../__tests__/header/menu/header-menu.test.ts | 13 + .../header/menu/header-select-menu.test.ts | 4 + .../__tests__/header/parse-html.test.ts | 16 +- .../__tests__/header/plugin.test.ts | 2 + .../__tests__/header/render-elem.test.ts | 5 + .../__tests__/image/elem-to-html.test.ts | 2 +- .../__tests__/image/helper.test.ts | 17 +- .../__tests__/image/menu/del-image.test.ts | 4 + .../image/menu/edit-image-size.test.ts | 9 +- .../__tests__/image/menu/edit-image.test.ts | 13 +- .../__tests__/image/menu/insert-image.test.ts | 11 +- .../image/menu/view-image-link.test.ts | 6 +- .../__tests__/image/menu/width-menus.test.ts | 6 + .../__tests__/image/parse-html.test.ts | 4 +- .../__tests__/image/render-elem.test.ts | 6 +- .../indent/menu/decrease-indent-menu.test.ts | 1 + .../indent/menu/increase-indent-menu.test.ts | 1 + .../__tests__/indent/parse-html.test.ts | 4 + .../indent/render-text-style.test.tsx | 2 + .../indent/text-style-to-html.test.ts | 1 + .../__tests__/justify/menus.test.ts | 5 + .../__tests__/justify/parse-html.test.ts | 2 + .../justify/render-text-style.test.tsx | 2 + .../justify/text-style-to-html.test.ts | 1 + .../line-height/line-height-menu.test.ts | 3 + .../__tests__/line-height/parse-html.test.ts | 2 + .../line-height/render-text-style.test.tsx | 2 + .../line-height/text-style-to-html.test.ts | 1 + .../__tests__/link/elem-to-html.test.ts | 5 +- .../__tests__/link/helper.test.ts | 37 +- .../link/menu/edit-link-menu.test.ts | 7 +- .../link/menu/insert-link-menu.test.ts | 6 +- .../__tests__/link/menu/unlink-menu.test.ts | 2 + .../link/menu/view-link-menu.test.ts | 1 + .../__tests__/link/parse-html.test.ts | 3 + .../__tests__/link/plugin.test.ts | 14 +- .../__tests__/link/render-elem.test.ts | 5 +- .../__tests__/paragraph/elem-to-html.test.ts | 4 +- .../__tests__/paragraph/parse-html.test.ts | 3 + .../__tests__/paragraph/plugin.test.ts | 6 +- .../__tests__/paragraph/render-elem.test.ts | 1 + .../text-style/menu/clear-style-menu.test.ts | 4 +- .../__tests__/text-style/menu/menus.test.ts | 9 +- .../text-style/parse-style-html.test.ts | 14 +- .../__tests__/text-style/text-style.test.tsx | 3 +- .../__tests__/text-style/text-to-html.test.ts | 6 +- .../__tests__/todo/elem-to-html.test.ts | 6 +- .../__tests__/todo/menu/todo-menu.test.ts | 3 + .../__tests__/todo/parse-html.test.ts | 3 + .../__tests__/todo/plugin.test.ts | 6 +- .../__tests__/todo/pre-parse-html.test.ts | 6 +- .../__tests__/todo/render-elem.test.ts | 3 + .../__tests__/undo-redo/redo-menu.test.ts | 4 +- .../__tests__/undo-redo/undo-menu.test.ts | 2 + .../basic-modules/__tests__/utils/dom.test.ts | 4 +- packages/basic-modules/package.json | 2 +- packages/basic-modules/rollup.config.js | 5 +- .../basic-modules/src/constants/icon-svg.ts | 113 +- packages/basic-modules/src/index.ts | 25 +- packages/basic-modules/src/locale/index.ts | 1 + .../src/modules/blockquote/custom-types.ts | 2 +- .../src/modules/blockquote/index.ts | 5 +- .../modules/blockquote/menu/BlockquoteMenu.ts | 16 +- .../src/modules/blockquote/parse-elem-html.ts | 9 +- .../src/modules/blockquote/plugin.ts | 15 +- .../src/modules/blockquote/render-elem.tsx | 5 +- .../src/modules/code-block/custom-types.ts | 2 +- .../src/modules/code-block/index.ts | 7 +- .../modules/code-block/menu/CodeBlockMenu.ts | 47 +- .../src/modules/code-block/parse-elem-html.ts | 5 +- .../src/modules/code-block/plugin.ts | 23 +- .../src/modules/code-block/pre-parse-html.ts | 10 +- .../src/modules/code-block/render-elem.tsx | 4 +- .../src/modules/color/custom-types.ts | 2 +- .../basic-modules/src/modules/color/index.ts | 7 +- .../src/modules/color/menu/BaseMenu.ts | 42 +- .../src/modules/color/menu/BgColorMenu.ts | 5 +- .../src/modules/color/menu/ColorMenu.ts | 5 +- .../src/modules/color/menu/index.ts | 4 +- .../src/modules/color/parse-style-html.ts | 12 +- .../src/modules/color/pre-parse-html.ts | 4 +- .../src/modules/color/render-style.tsx | 3 +- .../src/modules/color/style-to-html.ts | 13 +- .../basic-modules/src/modules/common/index.ts | 1 + .../src/modules/common/menu/EnterMenu.ts | 14 +- .../src/modules/divider/custom-types.ts | 2 +- .../src/modules/divider/elem-to-html.ts | 2 +- .../src/modules/divider/index.ts | 7 +- .../modules/divider/menu/InsertDividerMenu.ts | 20 +- .../src/modules/divider/parse-elem-html.ts | 3 +- .../src/modules/divider/plugin.ts | 6 +- .../src/modules/divider/render-elem.tsx | 6 +- .../src/modules/emotion/index.ts | 1 + .../src/modules/emotion/menu/EmotionMenu.ts | 28 +- .../src/modules/emotion/menu/config.ts | 4 +- .../src/modules/emotion/menu/index.ts | 2 +- .../modules/font-size-family/custom-types.ts | 2 +- .../src/modules/font-size-family/index.ts | 7 +- .../modules/font-size-family/menu/BaseMenu.ts | 21 +- .../font-size-family/menu/FontFamilyMenu.ts | 8 +- .../font-size-family/menu/FontSizeMenu.ts | 9 +- .../modules/font-size-family/menu/config.ts | 2 +- .../modules/font-size-family/menu/index.ts | 4 +- .../font-size-family/parse-style-html.ts | 18 +- .../font-size-family/pre-parse-html.ts | 19 +- .../modules/font-size-family/render-style.tsx | 3 +- .../modules/font-size-family/style-to-html.ts | 13 +- .../src/modules/format-painter/index.ts | 1 + .../format-painter/menu/FormatPainter.ts | 10 +- .../src/modules/format-painter/plugin.ts | 1 + .../src/modules/full-screen/index.ts | 1 + .../modules/full-screen/menu/FullScreen.ts | 12 +- .../src/modules/header/custom-types.ts | 2 +- .../src/modules/header/helper.ts | 15 +- .../basic-modules/src/modules/header/index.ts | 33 +- .../modules/header/menu/Header1ButtonMenu.ts | 1 + .../modules/header/menu/Header2ButtonMenu.ts | 1 + .../modules/header/menu/Header3ButtonMenu.ts | 1 + .../modules/header/menu/Header4ButtonMenu.ts | 1 + .../modules/header/menu/Header5ButtonMenu.ts | 1 + .../modules/header/menu/Header6ButtonMenu.ts | 1 + .../header/menu/HeaderButtonMenuBase.ts | 4 + .../modules/header/menu/HeaderSelectMenu.ts | 11 +- .../src/modules/header/menu/index.ts | 2 +- .../src/modules/header/parse-elem-html.ts | 8 +- .../src/modules/header/plugin.ts | 4 +- .../src/modules/header/render-elem.tsx | 5 +- .../src/modules/image/custom-types.ts | 2 +- .../src/modules/image/elem-to-html.ts | 10 +- .../basic-modules/src/modules/image/helper.ts | 66 +- .../basic-modules/src/modules/image/index.ts | 13 +- .../src/modules/image/menu/DeleteImage.ts | 12 +- .../src/modules/image/menu/EditImage.ts | 47 +- .../modules/image/menu/EditImageSizeMenu.ts | 35 +- .../src/modules/image/menu/InsertImage.ts | 32 +- .../src/modules/image/menu/ViewImageLink.ts | 13 +- .../src/modules/image/menu/Width100.ts | 1 + .../src/modules/image/menu/Width30.ts | 1 + .../src/modules/image/menu/Width50.ts | 1 + .../src/modules/image/menu/WidthBase.ts | 18 +- .../src/modules/image/menu/config.ts | 4 +- .../src/modules/image/menu/index.ts | 6 +- .../src/modules/image/parse-elem-html.ts | 6 +- .../src/modules/image/render-elem.tsx | 49 +- .../src/modules/indent/custom-types.ts | 2 +- .../basic-modules/src/modules/indent/index.ts | 7 +- .../src/modules/indent/menu/BaseMenu.ts | 13 +- .../modules/indent/menu/DecreaseIndentMenu.ts | 12 +- .../modules/indent/menu/IncreaseIndentMenu.ts | 23 +- .../src/modules/indent/parse-style-html.ts | 9 +- .../src/modules/indent/pre-parse-html.ts | 1 + .../src/modules/indent/render-style.tsx | 7 +- .../src/modules/indent/style-to-html.ts | 9 +- .../src/modules/justify/custom-types.ts | 2 +- .../src/modules/justify/index.ts | 11 +- .../src/modules/justify/menu/BaseMenu.ts | 17 +- .../modules/justify/menu/JustifyCenterMenu.ts | 8 +- .../justify/menu/JustifyJustifyMenu.ts | 8 +- .../modules/justify/menu/JustifyLeftMenu.ts | 8 +- .../modules/justify/menu/JustifyRightMenu.ts | 8 +- .../src/modules/justify/menu/index.ts | 4 +- .../src/modules/justify/parse-style-html.ts | 9 +- .../src/modules/justify/render-style.tsx | 5 +- .../src/modules/justify/style-to-html.ts | 10 +- .../src/modules/line-height/custom-types.ts | 2 +- .../src/modules/line-height/index.ts | 5 +- .../line-height/menu/LineHeightMenu.ts | 29 +- .../src/modules/line-height/menu/index.ts | 2 +- .../modules/line-height/parse-style-html.ts | 9 +- .../src/modules/line-height/render-style.tsx | 7 +- .../src/modules/line-height/style-to-html.ts | 9 +- .../src/modules/link/custom-types.ts | 2 +- .../src/modules/link/elem-to-html.ts | 1 + .../basic-modules/src/modules/link/helper.ts | 46 +- .../basic-modules/src/modules/link/index.ts | 9 +- .../src/modules/link/menu/EditLink.ts | 30 +- .../src/modules/link/menu/InsertLink.ts | 24 +- .../src/modules/link/menu/UnLink.ts | 12 +- .../src/modules/link/menu/ViewLink.ts | 16 +- .../src/modules/link/menu/index.ts | 8 +- .../src/modules/link/parse-elem-html.ts | 10 +- .../basic-modules/src/modules/link/plugin.ts | 18 +- .../src/modules/link/render-elem.tsx | 3 +- .../src/modules/paragraph/custom-types.ts | 2 +- .../src/modules/paragraph/index.ts | 3 +- .../src/modules/paragraph/parse-elem-html.ts | 11 +- .../src/modules/paragraph/plugin.ts | 27 +- .../src/modules/paragraph/render-elem.tsx | 5 +- .../src/modules/text-style/custom-types.ts | 2 +- .../src/modules/text-style/helper.ts | 11 +- .../src/modules/text-style/index.ts | 15 +- .../src/modules/text-style/menu/BaseMenu.ts | 23 +- .../src/modules/text-style/menu/BoldMenu.ts | 6 +- .../modules/text-style/menu/ClearStyleMenu.ts | 7 +- .../src/modules/text-style/menu/CodeMenu.ts | 6 +- .../src/modules/text-style/menu/ItalicMenu.ts | 6 +- .../src/modules/text-style/menu/SubMenu.ts | 7 +- .../src/modules/text-style/menu/SupMenu.ts | 7 +- .../modules/text-style/menu/ThroughMenu.ts | 6 +- .../modules/text-style/menu/UnderlineMenu.ts | 6 +- .../src/modules/text-style/menu/index.ts | 6 +- .../modules/text-style/parse-style-html.ts | 15 +- .../src/modules/text-style/render-style.tsx | 5 +- .../src/modules/text-style/style-to-html.ts | 29 +- .../src/modules/todo/custom-types.ts | 2 +- .../src/modules/todo/elem-to-html.ts | 2 + .../basic-modules/src/modules/todo/index.ts | 7 +- .../src/modules/todo/menu/Todo.ts | 18 +- .../src/modules/todo/parse-elem-html.ts | 10 +- .../basic-modules/src/modules/todo/plugin.ts | 3 +- .../src/modules/todo/pre-parse-html.ts | 2 + .../src/modules/todo/render-elem.tsx | 7 +- .../src/modules/undo-redo/index.ts | 1 + .../src/modules/undo-redo/menu/RedoMenu.ts | 5 +- .../src/modules/undo-redo/menu/UndoMenu.ts | 5 +- packages/basic-modules/src/utils/dom.ts | 125 +- packages/basic-modules/src/utils/util.ts | 2 +- packages/basic-modules/src/utils/vdom.ts | 7 +- packages/code-highlight/__tests__/content.ts | 2 +- .../code-highlight/__tests__/decorate.test.ts | 7 +- .../__tests__/elem-to-html.test.ts | 10 +- .../__tests__/parse-html.test.ts | 5 +- .../__tests__/render-text-style.test.tsx | 4 +- .../__tests__/select-lang-menu.test.ts | 26 +- packages/code-highlight/rollup.config.js | 5 +- packages/code-highlight/src/constants/svg.ts | 3 +- packages/code-highlight/src/decorate/index.ts | 18 +- packages/code-highlight/src/index.ts | 5 +- packages/code-highlight/src/locale/index.ts | 1 + .../code-highlight/src/module/elem-to-html.ts | 1 + packages/code-highlight/src/module/index.ts | 7 +- .../src/module/menu/SelectLangMenu.ts | 37 +- .../code-highlight/src/module/menu/index.ts | 2 +- .../src/module/parse-style-html.ts | 12 +- .../src/module/render-style.tsx | 8 +- packages/code-highlight/src/utils/dom.ts | 16 +- packages/code-highlight/src/utils/vdom.ts | 5 +- packages/code-highlight/src/vendor/prism.ts | 27 +- .../__tests__/config/editor-config.test.ts | 7 + .../core/__tests__/config/menu-config.test.ts | 2 +- .../__tests__/config/toolbar-config.test.ts | 17 +- packages/core/__tests__/create-core-editor.ts | 2 + .../__tests__/create/content-to-html.test.ts | 11 +- .../core/__tests__/editor/dom-editor.test.ts | 15 +- .../editor/plugins/with-config.test.ts | 5 +- .../editor/plugins/with-content.test.ts | 58 +- .../__tests__/editor/plugins/with-dom.test.ts | 15 +- .../editor/plugins/with-emitter.test.ts | 2 +- .../editor/plugins/with-selection.test.ts | 4 +- .../register-menus/register-button-menu.ts | 7 +- .../register-menus/register-modal-menu.ts | 11 +- .../register-menus/register-select-menu.ts | 8 +- .../core/__tests__/upload/uploader.test.ts | 10 +- packages/core/__tests__/utils/util.test.ts | 10 +- packages/core/__tests__/utils/vdom.test.ts | 34 +- packages/core/package.json | 2 +- packages/core/rollup.config.js | 5 +- packages/core/src/config/index.ts | 4 +- packages/core/src/config/interface.ts | 5 +- packages/core/src/config/register.ts | 2 +- packages/core/src/constants/svg.ts | 9 +- .../core/src/create/bind-node-relation.ts | 8 +- packages/core/src/create/create-editor.ts | 46 +- packages/core/src/create/create-toolbar.ts | 11 +- packages/core/src/create/helper.ts | 13 +- packages/core/src/editor/dom-editor.ts | 166 +- packages/core/src/editor/interface.ts | 7 +- .../core/src/editor/plugins/with-config.ts | 15 +- .../core/src/editor/plugins/with-content.ts | 96 +- packages/core/src/editor/plugins/with-dom.ts | 54 +- .../core/src/editor/plugins/with-emitter.ts | 13 +- .../src/editor/plugins/with-event-data.ts | 20 +- .../src/editor/plugins/with-max-length.ts | 29 +- .../core/src/editor/plugins/with-selection.ts | 19 +- packages/core/src/index.ts | 10 +- .../core/src/menus/bar-item/BaseButton.ts | 33 +- .../src/menus/bar-item/DropPanelButton.ts | 13 +- .../core/src/menus/bar-item/GroupButton.ts | 23 +- .../core/src/menus/bar-item/ModalButton.ts | 20 +- packages/core/src/menus/bar-item/Select.ts | 39 +- .../core/src/menus/bar-item/SimpleButton.ts | 1 + packages/core/src/menus/bar-item/index.ts | 22 +- packages/core/src/menus/bar-item/tooltip.ts | 6 +- packages/core/src/menus/bar/HoverBar.ts | 74 +- packages/core/src/menus/bar/Toolbar.ts | 56 +- packages/core/src/menus/helpers/helpers.ts | 6 +- packages/core/src/menus/helpers/position.ts | 98 +- packages/core/src/menus/index.ts | 6 +- packages/core/src/menus/interface.ts | 1 + .../src/menus/panel-and-modal/BaseClass.ts | 24 +- .../src/menus/panel-and-modal/DropPanel.ts | 3 +- .../core/src/menus/panel-and-modal/Modal.ts | 32 +- .../src/menus/panel-and-modal/SelectList.ts | 16 +- packages/core/src/menus/register.ts | 4 +- packages/core/src/parse-html/helper.ts | 1 + packages/core/src/parse-html/index.ts | 6 +- .../src/parse-html/parse-common-elem-html.ts | 28 +- .../core/src/parse-html/parse-elem-html.ts | 40 +- .../src/parse-html/parse-text-elem-html.ts | 5 +- .../core/src/render/element/getRenderElem.tsx | 4 +- .../core/src/render/element/renderElement.tsx | 21 +- .../core/src/render/element/renderStyle.ts | 1 + packages/core/src/render/index.ts | 3 +- packages/core/src/render/node2Vnode.ts | 8 +- packages/core/src/render/text/genVnode.tsx | 19 +- packages/core/src/render/text/renderStyle.ts | 1 + packages/core/src/render/text/renderText.tsx | 21 +- packages/core/src/text-area/TextArea.ts | 57 +- .../text-area/event-handlers/beforeInput.ts | 17 +- .../core/src/text-area/event-handlers/blur.ts | 23 +- .../src/text-area/event-handlers/click.ts | 19 +- .../text-area/event-handlers/composition.ts | 47 +- .../core/src/text-area/event-handlers/copy.ts | 7 +- .../core/src/text-area/event-handlers/cut.ts | 15 +- .../core/src/text-area/event-handlers/drag.ts | 22 +- .../core/src/text-area/event-handlers/drop.ts | 16 +- .../src/text-area/event-handlers/focus.ts | 5 +- .../src/text-area/event-handlers/index.ts | 12 +- .../src/text-area/event-handlers/keydown.ts | 62 +- .../src/text-area/event-handlers/keypress.ts | 10 +- .../src/text-area/event-handlers/paste.ts | 17 +- packages/core/src/text-area/helpers.ts | 29 +- packages/core/src/text-area/place-holder.ts | 14 +- packages/core/src/text-area/syncSelection.ts | 50 +- packages/core/src/text-area/update-view.ts | 33 +- packages/core/src/to-html/elem2html.ts | 17 +- packages/core/src/to-html/index.ts | 3 +- packages/core/src/to-html/node2html.ts | 9 +- packages/core/src/to-html/text2html.ts | 10 +- packages/core/src/upload/createUploader.ts | 7 +- packages/core/src/utils/dom.ts | 192 +- packages/core/src/utils/hotkeys.ts | 7 +- packages/core/src/utils/line.ts | 7 +- packages/core/src/utils/ua.ts | 44 +- packages/core/src/utils/util.ts | 7 +- packages/core/src/utils/vdom.ts | 36 +- packages/core/src/utils/weak-maps.ts | 15 +- packages/custom-types.d.ts | 48 +- packages/editor/__tests__/create.test.ts | 18 +- packages/editor/demo/js/custom-elem.js | 32 +- packages/editor/demo/js/huge-content.js | 11 +- packages/editor/examples/js/huge-content.js | 11 +- packages/editor/rollup.config.js | 5 +- packages/editor/src/Boot.ts | 43 +- packages/editor/src/constants/svg.ts | 15 +- packages/editor/src/create.ts | 28 +- packages/editor/src/index.ts | 46 +- .../src/init-default-config/config/index.ts | 2 +- .../src/init-default-config/config/toolbar.ts | 3 +- .../editor/src/init-default-config/index.ts | 8 +- packages/editor/src/locale/index.ts | 1 + .../src/register-builtin-modules/index.ts | 16 +- .../src/register-builtin-modules/register.ts | 3 +- packages/editor/src/utils/browser-polyfill.ts | 2 + packages/editor/src/utils/node-polyfill.ts | 2 +- .../__tests__/elem-to-html.test.ts | 28 +- .../__tests__/menu/bulleted-list-menu.test.ts | 4 +- .../__tests__/menu/numbered-list-menu.test.ts | 4 +- .../list-module/__tests__/parse-html.test.ts | 12 + packages/list-module/__tests__/plugin.test.ts | 34 +- .../list-module/__tests__/render-elem.test.ts | 11 + packages/list-module/rollup.config.js | 5 +- packages/list-module/src/constants/svg.ts | 6 +- packages/list-module/src/index.ts | 2 +- packages/list-module/src/locale/index.ts | 1 + .../list-module/src/module/custom-types.ts | 2 +- .../list-module/src/module/elem-to-html.ts | 50 +- packages/list-module/src/module/helpers.ts | 8 +- packages/list-module/src/module/index.ts | 7 +- .../list-module/src/module/menu/BaseMenu.ts | 29 +- .../src/module/menu/BulletedListMenu.ts | 5 +- .../src/module/menu/NumberedListMenu.ts | 5 +- .../list-module/src/module/parse-elem-html.ts | 14 +- packages/list-module/src/module/plugin.ts | 35 +- .../list-module/src/module/render-elem.tsx | 28 +- packages/list-module/src/utils/dom.ts | 34 +- packages/list-module/src/utils/maps.ts | 2 +- packages/list-module/src/utils/util.ts | 12 +- .../__tests__/elem-to-html.test.ts | 19 +- .../__tests__/menu/delete-col.test.ts | 39 +- .../__tests__/menu/delete-row.test.ts | 39 +- .../__tests__/menu/delete-table.test.ts | 16 +- .../__tests__/menu/full-width.test.ts | 20 +- .../__tests__/menu/insert-col.test.ts | 41 +- .../__tests__/menu/insert-row.test.ts | 21 +- .../__tests__/menu/insert-table.test.ts | 25 +- .../__tests__/menu/table-header.test.ts | 16 +- .../table-module/__tests__/parse-html.test.ts | 8 +- .../table-module/__tests__/plugin.test.ts | 8 +- .../__tests__/render-elem.test.ts | 8 +- packages/table-module/rollup.config.js | 5 +- packages/table-module/src/constants/svg.ts | 51 +- packages/table-module/src/index.ts | 2 +- packages/table-module/src/locale/index.ts | 1 + .../table-module/src/module/column-resize.ts | 70 +- .../table-module/src/module/custom-types.ts | 2 +- .../table-module/src/module/elem-to-html.ts | 8 +- packages/table-module/src/module/helpers.ts | 15 +- packages/table-module/src/module/index.ts | 31 +- .../src/module/menu/CellProperty.ts | 8 +- .../table-module/src/module/menu/DeleteCol.ts | 32 +- .../table-module/src/module/menu/DeleteRow.ts | 28 +- .../src/module/menu/DeleteTable.ts | 12 +- .../table-module/src/module/menu/FullWidth.ts | 21 +- .../table-module/src/module/menu/InsertCol.ts | 36 +- .../table-module/src/module/menu/InsertRow.ts | 33 +- .../src/module/menu/InsertTable.ts | 57 +- .../table-module/src/module/menu/MergeCell.ts | 22 +- .../table-module/src/module/menu/SplitCell.ts | 18 +- .../src/module/menu/TableHeader.ts | 41 +- .../src/module/menu/TableProperty.ts | 54 +- .../table-module/src/module/menu/index.ts | 14 +- .../src/module/parse-elem-html.ts | 27 +- .../src/module/parse-style-html.ts | 13 +- packages/table-module/src/module/plugin.ts | 44 +- .../table-module/src/module/pre-parse-html.ts | 9 +- .../src/module/render-elem/index.ts | 4 +- .../src/module/render-elem/render-cell.tsx | 6 +- .../src/module/render-elem/render-row.tsx | 5 +- .../src/module/render-elem/render-table.tsx | 58 +- .../table-module/src/module/render-style.ts | 22 +- .../table-module/src/module/style-to-html.ts | 9 +- .../table-module/src/module/table-cursor.ts | 7 +- packages/table-module/src/module/weak-maps.ts | 1 + .../table-module/src/module/with-selection.ts | 16 +- packages/table-module/src/utils/dom.ts | 82 +- packages/table-module/src/utils/has-common.ts | 3 +- packages/table-module/src/utils/is-of-type.ts | 11 +- packages/table-module/src/utils/matrices.ts | 7 +- packages/table-module/src/utils/point.ts | 1 + packages/table-module/src/utils/types.ts | 2 +- packages/table-module/src/utils/util.ts | 2 +- packages/table-module/src/utils/vdom.ts | 7 +- .../__tests__/plugin.test.ts | 15 +- .../__tests__/upload-files.test.ts | 9 +- .../__tests__/upload-image-menu.test.ts | 3 +- packages/upload-image-module/rollup.config.js | 5 +- .../upload-image-module/src/constants/svg.ts | 3 +- packages/upload-image-module/src/index.ts | 2 +- .../upload-image-module/src/locale/index.ts | 1 + .../upload-image-module/src/module/index.ts | 3 +- .../src/module/menu/UploadImageMenu.ts | 10 +- .../src/module/menu/index.ts | 2 +- .../upload-image-module/src/module/plugin.ts | 8 +- .../src/module/upload-images.ts | 26 +- packages/upload-image-module/src/utils/dom.ts | 17 +- .../__tests__/elem-to-html.test.ts | 6 +- .../video-module/__tests__/helpler.test.ts | 15 +- .../__tests__/menu/insert-video-menu.test.ts | 7 +- .../__tests__/menu/upload-video-menu.test.ts | 7 +- .../video-module/__tests__/parse-html.test.ts | 14 +- .../video-module/__tests__/plugin.test.ts | 5 +- .../__tests__/render-elem.test.ts | 6 +- packages/video-module/rollup.config.js | 5 +- packages/video-module/src/constants/svg.ts | 9 +- packages/video-module/src/index.ts | 2 +- packages/video-module/src/locale/index.ts | 1 + .../video-module/src/module/custom-types.ts | 2 +- .../video-module/src/module/elem-to-html.ts | 9 +- .../src/module/helper/insert-video.ts | 8 +- .../src/module/helper/upload-videos.ts | 19 +- packages/video-module/src/module/index.ts | 13 +- .../src/module/menu/EditVideoSizeMenu.ts | 36 +- .../src/module/menu/EditVideoSrcMenu.ts | 35 +- .../src/module/menu/InsertVideoMenu.ts | 40 +- .../src/module/menu/UploadVideoMenu.ts | 29 +- .../video-module/src/module/menu/config.ts | 1 + .../video-module/src/module/menu/index.ts | 6 +- .../src/module/parse-elem-html.ts | 12 +- packages/video-module/src/module/plugin.ts | 4 +- .../video-module/src/module/pre-parse-html.ts | 14 +- .../video-module/src/module/render-elem.tsx | 15 +- packages/video-module/src/utils/dom.ts | 63 +- packages/video-module/src/utils/util.ts | 3 +- packages/yjs-for-react/rollup.config.js | 5 +- .../src/hooks/use-editor-static.tsx | 5 +- .../src/hooks/useRemoteCursorEditor.ts | 6 +- .../hooks/useRemoteCursorOverlayPositions.tsx | 39 +- .../src/hooks/useRemoteCursorStateStore.ts | 12 +- .../src/hooks/useRemoteCursorStates.ts | 9 +- packages/yjs-for-react/src/hooks/utils.ts | 15 +- packages/yjs-for-react/src/index.ts | 9 +- .../yjs-for-react/src/utils/getCursorRange.ts | 4 +- .../src/utils/getOverlayPosition.ts | 13 +- .../utils/react-editor-to-dom-range-safe.ts | 2 +- packages/yjs/examples/frontend/src/index.tsx | 14 +- .../src/pages/RemoteCursorOverlay/Overlay.tsx | 3 +- .../src/pages/RemoteCursorOverlay/index.tsx | 20 +- .../examples/frontend/src/pages/Simple.tsx | 20 +- packages/yjs/examples/frontend/src/utils.ts | 2 + packages/yjs/examples/frontend/vite.config.ts | 2 +- packages/yjs/rollup.config.js | 5 +- packages/yjs/src/applyToSlate/index.ts | 5 +- packages/yjs/src/applyToSlate/textEvent.ts | 42 +- packages/yjs/src/applyToYjs/index.ts | 2 + packages/yjs/src/applyToYjs/node/index.ts | 1 + .../yjs/src/applyToYjs/node/insertNode.ts | 1 + packages/yjs/src/applyToYjs/node/mergeNode.ts | 12 +- packages/yjs/src/applyToYjs/node/moveNode.ts | 10 +- .../yjs/src/applyToYjs/node/removeNode.ts | 2 + packages/yjs/src/applyToYjs/node/setNode.ts | 1 + packages/yjs/src/applyToYjs/node/splitNode.ts | 15 +- packages/yjs/src/applyToYjs/text/index.ts | 1 + .../yjs/src/applyToYjs/text/insertText.ts | 4 +- .../yjs/src/applyToYjs/text/removeText.ts | 4 +- packages/yjs/src/index.ts | 28 +- packages/yjs/src/module/custom-types.ts | 6 +- packages/yjs/src/plugins/index.ts | 4 +- packages/yjs/src/plugins/withCursors.ts | 47 +- packages/yjs/src/plugins/withYHistory.ts | 20 +- packages/yjs/src/plugins/withYjs.ts | 38 +- packages/yjs/src/utils/clone.ts | 2 + packages/yjs/src/utils/convert.ts | 1 + packages/yjs/src/utils/delta.ts | 9 +- packages/yjs/src/utils/location.ts | 17 +- packages/yjs/src/utils/object.ts | 18 +- packages/yjs/src/utils/position.ts | 60 +- packages/yjs/src/utils/slate.ts | 3 +- packages/yjs/src/utils/yjs.ts | 2 +- scripts/release-tag.js | 3 + tests/setup/index.ts | 27 +- tests/utils/create-editor.ts | 1 + tests/utils/create-toolbar.ts | 1 + tsconfig.json | 9 +- yarn.lock | 1589 ++++++++--------- 566 files changed, 5748 insertions(+), 3508 deletions(-) create mode 100644 babel.config.js delete mode 100644 babel.config.json diff --git a/.husky/pre-commit b/.husky/pre-commit index b29c21d1e..2f392dfec 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn lint:staged \ No newline at end of file +# yarn lint:staged \ No newline at end of file diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 000000000..11a122020 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,26 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + modules: false, + useBuiltIns: 'usage', + corejs: 3, + targets: 'ie 11', + }, + ], + '@babel/preset-typescript', + ], + plugins: [ + [ + '@babel/plugin-transform-runtime', + { + absoluteRuntime: false, + corejs: 3, + helpers: true, + regenerator: true, + useESModules: false, + }, + ], + ], +} diff --git a/babel.config.json b/babel.config.json deleted file mode 100644 index ad09d556f..000000000 --- a/babel.config.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "modules": false, - "useBuiltIns": "usage", - "corejs": 3, - "targets": "ie 11" - } - ], - "@babel/preset-typescript" - ], - "plugins": [ - [ - "@babel/plugin-transform-runtime", - { - "absoluteRuntime": false, - "corejs": 3, - "helpers": true, - "regenerator": true, - "useESModules": false - } - ] - ] -} \ No newline at end of file diff --git a/build/config/common.js b/build/config/common.js index dfef3f956..f30db73c1 100644 --- a/build/config/common.js +++ b/build/config/common.js @@ -3,13 +3,13 @@ * @author wangfupeng */ -import path from 'path' import commonjs from '@rollup/plugin-commonjs' import json from '@rollup/plugin-json' import nodeResolve from '@rollup/plugin-node-resolve' -import typescript from 'rollup-plugin-typescript2' import replace from '@rollup/plugin-replace' +import path from 'path' import peerDepsExternal from 'rollup-plugin-peer-deps-external' +import typescript from 'rollup-plugin-typescript2' // import del from 'rollup-plugin-delete' export const extensions = ['.js', '.jsx', '.ts', '.tsx'] diff --git a/build/config/dev.js b/build/config/dev.js index 97101aae2..e43dd9ecc 100644 --- a/build/config/dev.js +++ b/build/config/dev.js @@ -3,8 +3,9 @@ * @author wangfupeng */ -import postcss from 'rollup-plugin-postcss' import autoprefixer from 'autoprefixer' +import postcss from 'rollup-plugin-postcss' + import genCommonConf from './common' /** @@ -12,7 +13,9 @@ import genCommonConf from './common' * @param {string} format 'umd' 'esm' */ function genDevConf(format) { - const { input, output = {}, plugins = [], external } = genCommonConf(format) + const { + input, output = {}, plugins = [], external, + } = genCommonConf(format) return { input, diff --git a/build/config/prd.js b/build/config/prd.js index 71f735f50..a2d0168ab 100644 --- a/build/config/prd.js +++ b/build/config/prd.js @@ -4,20 +4,22 @@ */ import babel from '@rollup/plugin-babel' -import postcss from 'rollup-plugin-postcss' +import terser from '@rollup/plugin-terser' import autoprefixer from 'autoprefixer' import cssnano from 'cssnano' -import terser from '@rollup/plugin-terser' import cleanup from 'rollup-plugin-cleanup' -import genCommonConf from './common' -import { extensions } from './common' +import postcss from 'rollup-plugin-postcss' + +import genCommonConf, { extensions } from './common' /** * 生成 prd config * @param {string} format 'umd' 'esm' */ function genPrdConf(format) { - const { input, output = {}, plugins = [], external } = genCommonConf(format) + const { + input, output = {}, plugins = [], external, + } = genCommonConf(format) const finalPlugins = [ ...plugins, diff --git a/build/create-rollup-config.js b/build/create-rollup-config.js index f037e81d5..c644f0c71 100644 --- a/build/create-rollup-config.js +++ b/build/create-rollup-config.js @@ -5,12 +5,14 @@ import { merge } from 'lodash' import { visualizer } from 'rollup-plugin-visualizer' + import genDevConf from './config/dev' import genPrdConf from './config/prd' // 环境变量 const ENV = process.env.NODE_ENV || 'production' const IS_SIZE_STATS = ENV.indexOf('size_stats') >= 0 // 分析包体积 + export const IS_DEV = ENV.indexOf('development') >= 0 export const IS_PRD = ENV.indexOf('production') >= 0 @@ -23,6 +25,7 @@ export function createRollupConfig(customConfig = {}) { const { format } = output let baseConfig + if (IS_PRD) { baseConfig = genPrdConf(format) } else { @@ -35,11 +38,12 @@ export function createRollupConfig(customConfig = {}) { } const config = { - input: input ? input : baseConfig.input, + input: input || baseConfig.input, output, plugins, } const res = merge({}, baseConfig, config) + return res } diff --git a/jest.config.cjs b/jest.config.cjs index f62306441..7c2d3ac07 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -3,8 +3,13 @@ module.exports = { testEnvironment: 'jsdom', testMatch: ['**/(*.)+(spec|test).+(ts|js|tsx)'], transform: { - '^.+\\.tsx?$': 'ts-jest', - '^.+\\.js$': 'ts-jest', + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + }, + ], + '^.+\\.js$': 'babel-jest', }, globals: { 'ts-jest': { @@ -15,7 +20,7 @@ module.exports = { moduleNameMapper: { '^.+\\.(css|less)$': '/tests/utils/stylesMock.js', }, - transformIgnorePatterns: ['node_modules/(?!(html-void-elements)/)'], + transformIgnorePatterns: ['node_modules/(?!html-void-elements)'], setupFilesAfterEnv: ['/tests/setup/index.ts'], // just collect basic module collectCoverageFrom: [ @@ -28,5 +33,7 @@ module.exports = { 'config.ts', 'browser-polyfill.ts', 'node-polyfill.ts', + '/node_modules/', + '\\.d\\.ts$', ], } diff --git a/package.json b/package.json index 82e2d2c4a..45cee04c1 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,11 @@ "name": "@wangeditor-next/wangeditor", "private": true, "packageManager": "yarn@1.22.22", + "engines": { + "node": ">=18.12.0", + "npm": "please-use-yarn", + "yarn": ">=1.22.0" + }, "browserslist": [ "defaults", "not IE 11" @@ -50,13 +55,13 @@ "@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-terser": "^0.4.0", "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^5.14.1", - "@types/jest": "^25.2.1", + "@testing-library/jest-dom": "6.5.0", + "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "autoprefixer": "^10.2.5", "babel-core": "^7.0.0-bridge.0", - "babel-jest": "^27.0.6", + "babel-jest": "^29.7.0", "babel-plugin-istanbul": "^6.0.0", "concurrently": "^6.2.0", "core-js": "3", @@ -77,7 +82,8 @@ "eslint-plugin-vue": "^9.27.0", "http-server": "^0.13.0", "husky": "^9.1.6", - "jest": "^27.0.6", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "less": "^3.11.1", "lint-staged": "^15.2.10", "lodash": "^4.17.21", @@ -96,10 +102,10 @@ "rollup-plugin-serve": "^1.1.0", "rollup-plugin-typescript2": "^0.36.0", "rollup-plugin-visualizer": "^5.5.0", - "ts-jest": "^27.0.4", + "ts-jest": "^29.2.5", "tslib": "^2.3.0", "turbo": "2.0.6", - "typescript": "4.3.2" + "typescript": "^5.2.0" }, "commitlint": { "extends": [ @@ -120,4 +126,4 @@ "dependencies": { "@babel/runtime": "^7.14.6" } -} +} \ No newline at end of file diff --git a/packages/basic-modules/__tests__/blockquote/blockquote-menu.test.ts b/packages/basic-modules/__tests__/blockquote/blockquote-menu.test.ts index 70cfaa408..2de9b04d0 100644 --- a/packages/basic-modules/__tests__/blockquote/blockquote-menu.test.ts +++ b/packages/basic-modules/__tests__/blockquote/blockquote-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import BlockquoteMenu from '../../src/modules/blockquote/menu/BlockquoteMenu' @@ -46,11 +47,13 @@ describe('blockquote menu', () => { menu.exec(editor, '') // 转换为 blockquote const blockquotes1 = editor.getElemsByTypePrefix('blockquote') + expect(blockquotes1.length).toBe(1) expect(menu.isActive(editor)).toBeTruthy() menu.exec(editor, '') // 取消 blockquote const blockquotes2 = editor.getElemsByTypePrefix('blockquote') + expect(blockquotes2.length).toBe(0) expect(menu.isActive(editor)).toBeFalsy() }) diff --git a/packages/basic-modules/__tests__/blockquote/elem-to-html.test.ts b/packages/basic-modules/__tests__/blockquote/elem-to-html.test.ts index db49ca18e..bbea641e3 100644 --- a/packages/basic-modules/__tests__/blockquote/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/blockquote/elem-to-html.test.ts @@ -11,6 +11,7 @@ describe('blockquote elem to html', () => { const elem = { type: 'blockquote', children: [] } const html = quoteToHtmlConf.elemToHtml(elem, 'hello') + expect(html).toBe('
hello
') }) }) diff --git a/packages/basic-modules/__tests__/blockquote/parse-html.test.ts b/packages/basic-modules/__tests__/blockquote/parse-html.test.ts index a85bf80d3..0b2217b86 100644 --- a/packages/basic-modules/__tests__/blockquote/parse-html.test.ts +++ b/packages/basic-modules/__tests__/blockquote/parse-html.test.ts @@ -5,6 +5,7 @@ import { $ } from 'dom7' import { BaseElement } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { parseHtmlConf } from '../../src/modules/blockquote/parse-elem-html' @@ -19,6 +20,7 @@ describe('blockquote - parse html', () => { // parse const res = parseHtmlConf.parseElemHtml($elem[0], [], editor) + expect(res).toEqual({ type: 'blockquote', children: [{ text: 'hello world' }], @@ -31,6 +33,7 @@ describe('blockquote - parse html', () => { // parse const res = parseHtmlConf.parseElemHtml($elem[0], children, editor) + expect(res).toEqual({ type: 'blockquote', children: [{ text: 'hello ' }, { text: 'world', bold: true }], @@ -46,13 +49,15 @@ describe('blockquote - parse html', () => { ] const isInline = editor.isInline + editor.isInline = (element: any) => { - if (element.type === 'link') return true + if (element.type === 'link') { return true } return isInline(element) } // parse const res = parseHtmlConf.parseElemHtml($elem[0], children, editor) + expect(res).toEqual({ type: 'blockquote', children: [{ text: 'hello ' }, { type: 'link', url: 'http://wangeditor.com' }], diff --git a/packages/basic-modules/__tests__/blockquote/plugin.test.ts b/packages/basic-modules/__tests__/blockquote/plugin.test.ts index e1c1870e4..37c4c703c 100644 --- a/packages/basic-modules/__tests__/blockquote/plugin.test.ts +++ b/packages/basic-modules/__tests__/blockquote/plugin.test.ts @@ -4,9 +4,10 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' -import withBlockquote from '../../src/modules/blockquote/plugin' import { BlockQuoteElement } from '../../src/modules/blockquote/custom-types' +import withBlockquote from '../../src/modules/blockquote/plugin' describe('blockquote plugin', () => { let editor: any = withBlockquote(createEditor()) @@ -16,7 +17,7 @@ describe('blockquote plugin', () => { editor = withBlockquote( createEditor({ content: [{ type: 'blockquote', children: [{ text: 'hello\n' }] }], - }) + }), ) startLocation = Editor.start(editor, []) }) @@ -30,12 +31,14 @@ describe('blockquote plugin', () => { editor.deselect() editor.insertBreak() let pList = editor.getElemsByType('paragraph') + expect(pList.length).toBe(0) editor.select({ path: [0, 0], offset: 5 }) editor.insertBreak() let bqList = editor.getElemsByType('blockquote') as unknown as BlockQuoteElement[] let text = bqList[0].children[0].text + expect(text).toBe('hello\n\n') editor.select({ path: [0, 0], offset: 6 }) @@ -52,8 +55,9 @@ describe('blockquote plugin', () => { it('insert break new line', () => { editor.select({ path: [0, 0], offset: 6 }) editor.insertBreak() - let bqList = editor.getElemsByType('blockquote') as unknown as BlockQuoteElement[] - let text = bqList[0].children[0].text + const bqList = editor.getElemsByType('blockquote') as unknown as BlockQuoteElement[] + const text = bqList[0].children[0].text + expect(text).toBe('hello') }) }) diff --git a/packages/basic-modules/__tests__/blockquote/render-elem.test.ts b/packages/basic-modules/__tests__/blockquote/render-elem.test.ts index 2b169dc2b..ef15840be 100644 --- a/packages/basic-modules/__tests__/blockquote/render-elem.test.ts +++ b/packages/basic-modules/__tests__/blockquote/render-elem.test.ts @@ -14,6 +14,7 @@ describe('blockquote - render elem', () => { const elem = { type: 'blockquote', children: [] } const vnode = renderBlockQuoteConf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('blockquote') }) }) diff --git a/packages/basic-modules/__tests__/code-block/code-block-menu.test.ts b/packages/basic-modules/__tests__/code-block/code-block-menu.test.ts index ffb33e79c..0c5ac3087 100644 --- a/packages/basic-modules/__tests__/code-block/code-block-menu.test.ts +++ b/packages/basic-modules/__tests__/code-block/code-block-menu.test.ts @@ -3,7 +3,8 @@ * @author wangfupeng */ -import { Editor, Transforms, Element } from 'slate' +import { Editor, Element, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import CodeBlockMenu from '../../src/modules/code-block/menu/CodeBlockMenu' @@ -66,8 +67,10 @@ describe('code-block menu', () => { menu.exec(editor, 'javascript') // 生成 code-block const preList = editor.getElemsByTypePrefix('pre') + expect(preList.length).toBe(1) const codeLis = editor.getElemsByTypePrefix('code') + expect(codeLis.length).toBe(1) }) @@ -81,8 +84,10 @@ describe('code-block menu', () => { menu.exec(editor, '') // 取消 code-block const preList = editor.getElemsByTypePrefix('pre') + expect(preList.length).toBe(0) const codeLis = editor.getElemsByTypePrefix('code') + expect(codeLis.length).toBe(0) }) }) diff --git a/packages/basic-modules/__tests__/code-block/elem-to-html.test.ts b/packages/basic-modules/__tests__/code-block/elem-to-html.test.ts index 64cec9299..8f5c6fa9e 100644 --- a/packages/basic-modules/__tests__/code-block/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/code-block/elem-to-html.test.ts @@ -10,6 +10,7 @@ describe('code-block - elem to html', () => { expect(codeToHtmlConf.type).toBe('code') const elem = { type: 'code', children: [] } const html = codeToHtmlConf.elemToHtml(elem, 'hello') + expect(html).toBe('hello') }) @@ -17,6 +18,7 @@ describe('code-block - elem to html', () => { expect(preToHtmlConf.type).toBe('pre') const elem = { type: 'pre', children: [] } const html = preToHtmlConf.elemToHtml(elem, 'hello') + expect(html).toBe('
hello
') }) }) diff --git a/packages/basic-modules/__tests__/code-block/parse-html.test.ts b/packages/basic-modules/__tests__/code-block/parse-html.test.ts index ac81711ed..0722ca2ed 100644 --- a/packages/basic-modules/__tests__/code-block/parse-html.test.ts +++ b/packages/basic-modules/__tests__/code-block/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseCodeHtmlConf, parsePreHtmlConf } from '../../src/modules/code-block/parse-elem-html' import { preParseHtmlConf } from '../../src/modules/code-block/pre-parse-html' @@ -12,6 +13,7 @@ describe('code block - pre parse html', () => { it('pre parse html', () => { const $pre = $('
')
     const $code = $('var a = 100;')
+
     $pre.append($code)
 
     // match selector
@@ -19,6 +21,7 @@ describe('code block - pre parse html', () => {
 
     // pre parse
     const res = preParseHtmlConf.preParseHtml($code[0])
+
     expect(res.innerHTML).toBe('var a = 100;')
   })
 })
@@ -29,6 +32,7 @@ describe('code block - parse html', () => {
   it('parse code html', () => {
     const $pre = $('
')
     const $code = $('var a = 100;')
+
     $pre.append($code)
 
     // match selector
@@ -36,6 +40,7 @@ describe('code block - parse html', () => {
 
     // parse
     const res = parseCodeHtmlConf.parseElemHtml($code[0], [], editor)
+
     expect(res).toEqual({
       type: 'code',
       language: '',
@@ -57,6 +62,7 @@ describe('code block - parse html', () => {
 
     // parse
     let res = parsePreHtmlConf.parseElemHtml($pre[0], children, editor)
+
     expect(res).toEqual({
       type: 'pre',
       children: [
diff --git a/packages/basic-modules/__tests__/code-block/plugin.test.ts b/packages/basic-modules/__tests__/code-block/plugin.test.ts
index 560be47cf..aeeda9681 100644
--- a/packages/basic-modules/__tests__/code-block/plugin.test.ts
+++ b/packages/basic-modules/__tests__/code-block/plugin.test.ts
@@ -4,16 +4,19 @@
  */
 
 import { Editor, Transforms } from 'slate'
+
 import createEditor from '../../../../tests/utils/create-editor'
-import withCodeBlock from '../../src/modules/code-block/plugin'
 import { isDataTransfer } from '../../../core/src/utils/dom'
+import withCodeBlock from '../../src/modules/code-block/plugin'
 
 // 模拟 DataTransfer
 class MyDataTransfer {
   private values: object = {}
+
   setData(type: string, value: string) {
     this.values[type] = value
   }
+
   getData(type: string): string {
     return this.values[type]
   }
@@ -52,6 +55,7 @@ describe('code-block plugin', () => {
 
     // code-block 前后会自动生成两个 p
     const pList1 = editor.getElemsByTypePrefix('paragraph')
+
     expect(pList1.length).toBe(2)
 
     editor.select({
@@ -66,6 +70,7 @@ describe('code-block plugin', () => {
 
     // 不会再生成新的 p
     const pList2 = editor.getElemsByTypePrefix('paragraph')
+
     expect(pList2.length).toBe(2)
   })
 
@@ -81,11 +86,13 @@ describe('code-block plugin', () => {
       type: 'pre',
       children: [codeElem],
     }
+
     editor.select(startLocation)
     editor.insertNode(preElem) // 插入 code-block
 
     // code-block 前后会自动生成两个 p
     const pList1 = editor.getElemsByTypePrefix('paragraph')
+
     expect(pList1.length).toBe(2)
 
     editor.select({
@@ -100,6 +107,7 @@ describe('code-block plugin', () => {
 
   it('insert data', () => {
     const data = new MyDataTransfer()
+
     data.setData('text/plain', ' hello')
 
     // 无 codeNode 换行
@@ -122,6 +130,7 @@ describe('code-block plugin', () => {
     editor.insertNode(codeElem)
 
     const pList = editor.getElemsByTypePrefix('paragraph')
+
     expect(pList.length).toBe(2)
   })
 
@@ -130,9 +139,11 @@ describe('code-block plugin', () => {
     editor.insertNode({ type: 'pre', children: [{ type: 'code', children: [{ text: 'var' }] }] })
 
     const pList = editor.getElemsByTypePrefix('paragraph')
+
     expect(pList.length).toBe(2)
 
     const preList = editor.getElemsByTypePrefix('pre')
+
     expect(preList.length).toBe(1)
   })
 
@@ -141,9 +152,11 @@ describe('code-block plugin', () => {
     editor.insertNode({ type: 'pre', children: [{ type: 'text', children: [{ text: 'var' }] }] })
 
     const pList = editor.getElemsByTypePrefix('paragraph')
+
     expect(pList.length).toBe(2)
 
     const preList = editor.getElemsByTypePrefix('pre')
+
     expect(preList.length).toBe(0)
   })
 })
diff --git a/packages/basic-modules/__tests__/code-block/render-elem.test.ts b/packages/basic-modules/__tests__/code-block/render-elem.test.ts
index 2f2b7e21b..0e3f5648c 100644
--- a/packages/basic-modules/__tests__/code-block/render-elem.test.ts
+++ b/packages/basic-modules/__tests__/code-block/render-elem.test.ts
@@ -4,7 +4,7 @@
  */
 
 import createEditor from '../../../../tests/utils/create-editor'
-import { renderPreConf, renderCodeConf } from '../../src/modules/code-block/render-elem'
+import { renderCodeConf, renderPreConf } from '../../src/modules/code-block/render-elem'
 
 describe('code-block render elem', () => {
   const editor = createEditor()
@@ -14,6 +14,7 @@ describe('code-block render elem', () => {
 
     const elem = { type: 'code', children: [] }
     const vnode = renderCodeConf.renderElem(elem, null, editor)
+
     expect(vnode.sel).toBe('code')
   })
 
@@ -22,6 +23,7 @@ describe('code-block render elem', () => {
 
     const elem = { type: 'pre', children: [] }
     const vnode = renderPreConf.renderElem(elem, null, editor)
+
     expect(vnode.sel).toBe('pre')
   })
 })
diff --git a/packages/basic-modules/__tests__/color/color-menus.test.ts b/packages/basic-modules/__tests__/color/color-menus.test.ts
index 80daaebd7..6b89f9558 100644
--- a/packages/basic-modules/__tests__/color/color-menus.test.ts
+++ b/packages/basic-modules/__tests__/color/color-menus.test.ts
@@ -4,10 +4,11 @@
  */
 
 import { Editor, Transforms } from 'slate'
+
+import { isHTMLElememt } from '../../../../packages/core/src/utils/dom'
 import createEditor from '../../../../tests/utils/create-editor'
-import ColorMenu from '../../src/modules/color/menu/ColorMenu'
 import BgColorMenu from '../../src/modules/color/menu/BgColorMenu'
-import { isHTMLElememt } from '../../../../packages/core/src/utils/dom'
+import ColorMenu from '../../src/modules/color/menu/ColorMenu'
 
 describe('color menus', () => {
   let editor: any
@@ -69,6 +70,7 @@ describe('color menus', () => {
   it('get panel content elem', () => {
     menus.forEach(({ menu }) => {
       const elem = menu.getPanelContentElem(editor)
+
       expect(isHTMLElememt(elem)).toBeTruthy()
     })
   })
@@ -78,14 +80,17 @@ describe('color menus', () => {
       content: [{ type: 'paragraph', children: [{ text: 'hello', color: '#000' }] }],
     })
     const panelContent = menu.getPanelContentElem(textEditor)
+
     document.body.appendChild(panelContent)
 
     const li = panelContent.querySelector('li[data-value="rgb(120, 6, 80)"]') as HTMLLIElement
+
     textEditor.select([])
     li.click()
 
     const text: any = textEditor.children[0]
-    var color = text.children[0].color
+    const color = text.children[0].color
+
     expect(color).toBe('rgb(120, 6, 80)')
   })
 
@@ -98,6 +103,7 @@ describe('color menus', () => {
     textEditor.select([])
     editor.addMark('color', 'rgb(120, 6, 80)')
     const panelContent = menu.getPanelContentElem(textEditor)
+
     document.body.appendChild(panelContent)
 
     const li = panelContent.querySelector('li[data-value="0"]') as HTMLLIElement
@@ -106,7 +112,8 @@ describe('color menus', () => {
     li.click()
 
     const text: any = textEditor.children[0]
-    var color = text.children[0].color
+    const color = text.children[0].color
+
     expect(color).toBeUndefined()
   })
 })
diff --git a/packages/basic-modules/__tests__/color/parse-html.test.ts b/packages/basic-modules/__tests__/color/parse-html.test.ts
index 3f8e20832..b45ef4905 100644
--- a/packages/basic-modules/__tests__/color/parse-html.test.ts
+++ b/packages/basic-modules/__tests__/color/parse-html.test.ts
@@ -4,6 +4,7 @@
  */
 
 import { $ } from 'dom7'
+
 import createEditor from '../../../../tests/utils/create-editor'
 import { parseStyleHtml } from '../../src/modules/color/parse-style-html'
 import { preParseHtmlConf } from '../../src/modules/color/pre-parse-html'
@@ -17,6 +18,7 @@ describe('color - pre parse html', () => {
 
     // pre parse
     const res = preParseHtmlConf.preParseHtml($font[0])
+
     expect(res.outerHTML).toBe('hello')
   })
 })
@@ -26,12 +28,13 @@ describe('color - parse style html', () => {
 
   it('parse style html', () => {
     const $span = $(
-      ''
+      '',
     )
     const textNode = { text: 'hello' }
 
     // parse style
     const res = parseStyleHtml($span[0], textNode, editor)
+
     expect(res).toEqual({
       text: 'hello',
       color: 'rgb(235, 144, 58)',
diff --git a/packages/basic-modules/__tests__/color/render-text-style.test.tsx b/packages/basic-modules/__tests__/color/render-text-style.test.tsx
index a85a39cbd..7c805aa0a 100644
--- a/packages/basic-modules/__tests__/color/render-text-style.test.tsx
+++ b/packages/basic-modules/__tests__/color/render-text-style.test.tsx
@@ -4,6 +4,7 @@
  */
 
 import { jsx } from 'snabbdom'
+
 import { renderStyle } from '../../src/modules/color/render-style'
 
 describe('color - render text style', () => {
@@ -15,6 +16,7 @@ describe('color - render text style', () => {
 
     // @ts-ignore
     const newVnode = renderStyle(textNode, vnode) as any
+
     expect(newVnode.sel).toBe('span')
     expect(newVnode.data.style.color).toBe(color)
     expect(newVnode.data.style.backgroundColor).toBe(bgColor)
diff --git a/packages/basic-modules/__tests__/color/text-style-to-html.test.ts b/packages/basic-modules/__tests__/color/text-style-to-html.test.ts
index a0d949fdd..b01731c67 100644
--- a/packages/basic-modules/__tests__/color/text-style-to-html.test.ts
+++ b/packages/basic-modules/__tests__/color/text-style-to-html.test.ts
@@ -12,6 +12,7 @@ describe('color - text style to html', () => {
     const textNode = { text: '', color, bgColor }
 
     let html = styleToHtml(textNode, 'hello')
+
     expect(html).toBe(`hello`)
 
     // 测试纯文本
@@ -21,7 +22,7 @@ describe('color - text style to html', () => {
     // 测试 非 span 标签
     html = styleToHtml(textNode, '

hello

') expect(html).toBe( - `

hello

` + `

hello

`, ) }) }) diff --git a/packages/basic-modules/__tests__/common/enter-menu.test.ts b/packages/basic-modules/__tests__/common/enter-menu.test.ts index c54662405..07f9f076b 100644 --- a/packages/basic-modules/__tests__/common/enter-menu.test.ts +++ b/packages/basic-modules/__tests__/common/enter-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import EnterMenu from '../../src/modules/common/menu/EnterMenu' diff --git a/packages/basic-modules/__tests__/divider/elem-to-html.test.ts b/packages/basic-modules/__tests__/divider/elem-to-html.test.ts index c27b52212..74d42908e 100644 --- a/packages/basic-modules/__tests__/divider/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/divider/elem-to-html.test.ts @@ -11,6 +11,7 @@ describe('divider - elem to html', () => { const elem = { type: 'divider', children: [{ text: '' }] } const html = dividerToHtmlConf.elemToHtml(elem, '') + expect(html).toBe('
') }) }) diff --git a/packages/basic-modules/__tests__/divider/insert-divider-menu.test.ts b/packages/basic-modules/__tests__/divider/insert-divider-menu.test.ts index f0bd0b8c3..a9a14b3a0 100644 --- a/packages/basic-modules/__tests__/divider/insert-divider-menu.test.ts +++ b/packages/basic-modules/__tests__/divider/insert-divider-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import InsertDividerMenu from '../../src/modules/divider/menu/InsertDividerMenu' @@ -38,6 +39,7 @@ describe('divider plugin', () => { expect(menu.isDisabled(editor)).toBeFalsy() const elem = { type: 'divider', children: [{ text: '' }] } + editor.insertNode(elem) // 插入 divider editor.select({ path: [1, 0], // 选中 divider @@ -51,6 +53,7 @@ describe('divider plugin', () => { menu.exec(editor, '') const dividers = editor.getElemsByTypePrefix('divider') + expect(dividers.length).toBe(1) }) }) diff --git a/packages/basic-modules/__tests__/divider/parse-html.test.ts b/packages/basic-modules/__tests__/divider/parse-html.test.ts index 2e0bfb28f..52fcf88f3 100644 --- a/packages/basic-modules/__tests__/divider/parse-html.test.ts +++ b/packages/basic-modules/__tests__/divider/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseHtmlConf } from '../../src/modules/divider/parse-elem-html' @@ -18,6 +19,7 @@ describe('divider - parse html', () => { // parse const res = parseHtmlConf.parseElemHtml($hr[0], [], editor) + expect(res).toEqual({ type: 'divider', children: [{ text: '' }], // void node 有一个空白 text diff --git a/packages/basic-modules/__tests__/divider/plugin.test.ts b/packages/basic-modules/__tests__/divider/plugin.test.ts index 6d0c3e127..3c2f38d3c 100644 --- a/packages/basic-modules/__tests__/divider/plugin.test.ts +++ b/packages/basic-modules/__tests__/divider/plugin.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import withDivider from '../../src/modules/divider/plugin' @@ -23,20 +24,25 @@ describe('divider plugin', () => { it('divider is void node', () => { const elem = { type: 'divider', children: [{ text: '' }] } + expect(editor.isVoid(elem)).toBeTruthy() }) it('normalizeNode - divider 不能是最后一个元素,否则后面追加 p', () => { const elem = { type: 'divider', children: [{ text: '' }] } + editor.select(startLocation) editor.insertNode(elem) // 插入 divider const length = editor.children.length + expect(length).toBe(3) // 3 个顶级节点:p, divider, p const divider = editor.children[1] // 第 2 个节点应该是 divider + expect(divider.type).toBe('divider') const p = editor.children[2] // 第 3 个节点应该是 p + expect(p.type).toBe('paragraph') }) }) diff --git a/packages/basic-modules/__tests__/divider/render-elem.test.ts b/packages/basic-modules/__tests__/divider/render-elem.test.ts index fe8d1887b..da6f0dfcf 100644 --- a/packages/basic-modules/__tests__/divider/render-elem.test.ts +++ b/packages/basic-modules/__tests__/divider/render-elem.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { renderDividerConf } from '../../src/modules/divider/render-elem' @@ -16,6 +17,7 @@ describe('divider - render elem test', () => { const elem = { type: 'divider', children: [{ text: '' }] } const vnode1 = renderDividerConf.renderElem(elem, null, editor) as any + expect(vnode1.sel).toBe('div') expect(vnode1.data.props.className).toBe('w-e-textarea-divider') expect(vnode1.data.dataset.selected).toBe('') // 未选中 @@ -28,6 +30,7 @@ describe('divider - render elem test', () => { offset: 0, }) const vnode2 = renderDividerConf.renderElem(elem, null, editor) as any + expect(vnode2.data.dataset.selected).toBe('true') // 选中 }) }) diff --git a/packages/basic-modules/__tests__/emotion/emotion-menu.test.ts b/packages/basic-modules/__tests__/emotion/emotion-menu.test.ts index bcd987479..7d9735947 100644 --- a/packages/basic-modules/__tests__/emotion/emotion-menu.test.ts +++ b/packages/basic-modules/__tests__/emotion/emotion-menu.test.ts @@ -4,9 +4,10 @@ */ import { Editor, Transforms } from 'slate' + +import { isHTMLElememt } from '../../../../packages/core/src/utils/dom' import createEditor from '../../../../tests/utils/create-editor' import EmotionMenu from '../../src/modules/emotion/menu/EmotionMenu' -import { isHTMLElememt } from '../../../../packages/core/src/utils/dom' describe('font family menu', () => { const menu = new EmotionMenu() @@ -42,10 +43,12 @@ describe('font family menu', () => { it('get panel content elem', () => { const elem = menu.getPanelContentElem(editor) + expect(isHTMLElememt(elem)).toBeTruthy() document.body.appendChild(elem) const li = elem.querySelector('li') as HTMLLIElement + editor.select([]) li.click() diff --git a/packages/basic-modules/__tests__/font-size-family/menu/font-family-menu.test.ts b/packages/basic-modules/__tests__/font-size-family/menu/font-family-menu.test.ts index b7d40e073..021826bb3 100644 --- a/packages/basic-modules/__tests__/font-size-family/menu/font-family-menu.test.ts +++ b/packages/basic-modules/__tests__/font-size-family/menu/font-family-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import FontFamilyMenu from '../../../src/modules/font-size-family/menu/FontFamilyMenu' @@ -26,6 +27,7 @@ describe('font family menu', () => { editor.select(startLocation) const options1 = menu.getOptions(editor) const selectedDefault = options1.some(opt => opt.selected && opt.value === '') + expect(selectedDefault).toBeTruthy() // 空白 p ,选中“默认” editor.insertText('hello') @@ -33,6 +35,7 @@ describe('font family menu', () => { editor.addMark('fontFamily', '黑体') // 设置字体 const options2 = menu.getOptions(editor) const selectedHeiti = options2.some(opt => opt.selected && opt.value === '黑体') + expect(selectedHeiti).toBeTruthy() }) diff --git a/packages/basic-modules/__tests__/font-size-family/menu/font-size-menu.test.ts b/packages/basic-modules/__tests__/font-size-family/menu/font-size-menu.test.ts index 6e3f80f39..665cf92c5 100644 --- a/packages/basic-modules/__tests__/font-size-family/menu/font-size-menu.test.ts +++ b/packages/basic-modules/__tests__/font-size-family/menu/font-size-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import FontSizeMenu from '../../../src/modules/font-size-family/menu/FontSizeMenu' @@ -26,6 +27,7 @@ describe('font family menu', () => { editor.select(startLocation) const options1 = menu.getOptions(editor) const selectedDefault = options1.some(opt => opt.selected && opt.value === '') + expect(selectedDefault).toBeTruthy() // 空白 p ,选中“默认” editor.insertText('hello') @@ -33,6 +35,7 @@ describe('font family menu', () => { editor.addMark('fontSize', '40px') // 设置字号 const options2 = menu.getOptions(editor) const selected = options2.some(opt => opt.selected && opt.value === '40px') + expect(selected).toBeTruthy() }) diff --git a/packages/basic-modules/__tests__/font-size-family/parse-html.test.ts b/packages/basic-modules/__tests__/font-size-family/parse-html.test.ts index 6007f15b0..771f927a4 100644 --- a/packages/basic-modules/__tests__/font-size-family/parse-html.test.ts +++ b/packages/basic-modules/__tests__/font-size-family/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseStyleHtml } from '../../src/modules/font-size-family/parse-style-html' import { preParseHtmlConf } from '../../src/modules/font-size-family/pre-parse-html' @@ -17,6 +18,7 @@ describe('font size family - pre parse html', () => { // pre parse const res = preParseHtmlConf.preParseHtml($font[0]) + expect(res.outerHTML).toBe('hello') }) }) @@ -30,6 +32,7 @@ describe('font size family - parse style html', () => { // parse style const res = parseStyleHtml($span[0], textNode, editor) + expect(res).toEqual({ text: 'hello', fontSize: '12px', diff --git a/packages/basic-modules/__tests__/font-size-family/render-text-style.test.tsx b/packages/basic-modules/__tests__/font-size-family/render-text-style.test.tsx index 69ff804ed..ac4094a84 100644 --- a/packages/basic-modules/__tests__/font-size-family/render-text-style.test.tsx +++ b/packages/basic-modules/__tests__/font-size-family/render-text-style.test.tsx @@ -4,6 +4,7 @@ */ import { jsx } from 'snabbdom' + import { renderStyle } from '../../src/modules/font-size-family/render-style' describe('font size and family - render text style', () => { @@ -15,6 +16,7 @@ describe('font size and family - render text style', () => { // @ts-ignore 忽略 vnode 格式检查 const newVnode = renderStyle(textNode, vnode) as any + expect(newVnode.data.style.fontSize).toBe(fontSize) expect(newVnode.data.style.fontFamily).toBe(fontFamily) }) diff --git a/packages/basic-modules/__tests__/font-size-family/text-style-to-html.test.ts b/packages/basic-modules/__tests__/font-size-family/text-style-to-html.test.ts index 0bc93f2e1..4ed536e26 100644 --- a/packages/basic-modules/__tests__/font-size-family/text-style-to-html.test.ts +++ b/packages/basic-modules/__tests__/font-size-family/text-style-to-html.test.ts @@ -12,17 +12,20 @@ describe('font size and family - text style to html', () => { const textNode = { text: '', fontSize, fontFamily } const html = styleToHtml(textNode, 'hello') + expect(html).toBe( - `hello` + `hello`, ) const textHtml = styleToHtml(textNode, 'hello') + expect(textHtml).toBe( - `hello` + `hello`, ) const pHtml = styleToHtml(textNode, '

hello

') + expect(pHtml).toBe( - `

hello

` + `

hello

`, ) }) }) diff --git a/packages/basic-modules/__tests__/format-painter/format-painter-menu.test.ts b/packages/basic-modules/__tests__/format-painter/format-painter-menu.test.ts index c960879d1..cbc90f765 100644 --- a/packages/basic-modules/__tests__/format-painter/format-painter-menu.test.ts +++ b/packages/basic-modules/__tests__/format-painter/format-painter-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import FormatPainter from '../../src/modules/format-painter/menu/FormatPainter' diff --git a/packages/basic-modules/__tests__/format-painter/helper.test.ts b/packages/basic-modules/__tests__/format-painter/helper.test.ts index 1d2bb3652..4e9a56964 100644 --- a/packages/basic-modules/__tests__/format-painter/helper.test.ts +++ b/packages/basic-modules/__tests__/format-painter/helper.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { clearAllMarks } from '../../src/modules/format-painter/helper' diff --git a/packages/basic-modules/__tests__/format-painter/plugin.test.ts b/packages/basic-modules/__tests__/format-painter/plugin.test.ts index 57968491b..13bf779cd 100644 --- a/packages/basic-modules/__tests__/format-painter/plugin.test.ts +++ b/packages/basic-modules/__tests__/format-painter/plugin.test.ts @@ -1,6 +1,6 @@ import createEditor from '../../../../tests/utils/create-editor' -import withFormatPainter from '../../src/modules/format-painter/plugin' import FormatPainter from '../../src/modules/format-painter/menu/FormatPainter' +import withFormatPainter from '../../src/modules/format-painter/plugin' describe('format painter plugin', () => { let editor: any diff --git a/packages/basic-modules/__tests__/full-screen/full-screen-menu.test.ts b/packages/basic-modules/__tests__/full-screen/full-screen-menu.test.ts index 94d86dccf..0279876c2 100644 --- a/packages/basic-modules/__tests__/full-screen/full-screen-menu.test.ts +++ b/packages/basic-modules/__tests__/full-screen/full-screen-menu.test.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { t } from '@wangeditor-next/core' + import createEditor from '../../../../tests/utils/create-editor' import { CANCEL_FULL_SCREEN_SVG, FULL_SCREEN_SVG } from '../../src/constants/icon-svg' import FullScreen from '../../src/modules/full-screen/menu/FullScreen' -import { t } from '@wangeditor-next/core' describe('full screen menu', () => { const editor = createEditor() @@ -39,6 +40,7 @@ describe('full screen menu', () => { it('get icon', done => { let svg = menu.getIcon(editor) + expect(svg).toBe(FULL_SCREEN_SVG) menu.exec(editor, '') setTimeout(() => { diff --git a/packages/basic-modules/__tests__/header/elem-to-html.test.ts b/packages/basic-modules/__tests__/header/elem-to-html.test.ts index bf2e6a959..cd0360fa7 100644 --- a/packages/basic-modules/__tests__/header/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/header/elem-to-html.test.ts @@ -13,33 +13,39 @@ import { describe('header - elem to html', () => { const elem = { type: 'header1', children: [{ text: '' }] } + it('header1 to html', () => { expect(header1ToHtmlConf.type).toBe('header1') const html = header1ToHtmlConf.elemToHtml(elem, 'hello') + expect(html).toBe('

hello

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

hello

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

hello

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

hello

') }) it('header5 to html', () => { expect(header5ToHtmlConf.type).toBe('header5') const html = header5ToHtmlConf.elemToHtml(elem, 'hello') + expect(html).toBe('
hello
') }) }) diff --git a/packages/basic-modules/__tests__/header/helper.test.ts b/packages/basic-modules/__tests__/header/helper.test.ts index f0e8d7fac..7c4fdff4c 100644 --- a/packages/basic-modules/__tests__/header/helper.test.ts +++ b/packages/basic-modules/__tests__/header/helper.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { getHeaderType, isMenuDisabled, setHeaderType } from '../../src/modules/header/helper' @@ -46,6 +47,7 @@ describe('header helper', () => { setHeaderType(editor, 'header1') const headers = editor.getElemsByTypePrefix('header1') + expect(headers.length).toBe(1) }) }) diff --git a/packages/basic-modules/__tests__/header/menu/header-menu.test.ts b/packages/basic-modules/__tests__/header/menu/header-menu.test.ts index bb15eac74..f52af6218 100644 --- a/packages/basic-modules/__tests__/header/menu/header-menu.test.ts +++ b/packages/basic-modules/__tests__/header/menu/header-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import Header1ButtonMenu from '../../../src/modules/header/menu/Header1ButtonMenu' import Header2ButtonMenu from '../../../src/modules/header/menu/Header2ButtonMenu' @@ -37,10 +38,12 @@ describe('header menu', () => { menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) const headers1 = editor.getElemsByTypePrefix('header1') + expect(headers1.length).toBe(1) menu.exec(editor, 'header1') // 取消 header( header1 是当前选中的 node type ) const headers2 = editor.getElemsByTypePrefix('header1') + expect(headers2.length).toBe(0) }) }) @@ -53,10 +56,12 @@ describe('header menu', () => { menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) const headers1 = editor.getElemsByTypePrefix('header2') + expect(headers1.length).toBe(1) menu.exec(editor, 'header2') // 取消 header( header2 是当前选中的 node type ) const headers2 = editor.getElemsByTypePrefix('header2') + expect(headers2.length).toBe(0) }) }) @@ -69,10 +74,12 @@ describe('header menu', () => { menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) const headers1 = editor.getElemsByTypePrefix('header3') + expect(headers1.length).toBe(1) menu.exec(editor, 'header3') // 取消 header( header3 是当前选中的 node type ) const headers2 = editor.getElemsByTypePrefix('header3') + expect(headers2.length).toBe(0) }) }) @@ -85,10 +92,12 @@ describe('header menu', () => { menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) const headers1 = editor.getElemsByTypePrefix('header4') + expect(headers1.length).toBe(1) menu.exec(editor, 'header4') // 取消 header( header4 是当前选中的 node type ) const headers2 = editor.getElemsByTypePrefix('header4') + expect(headers2.length).toBe(0) }) }) @@ -101,10 +110,12 @@ describe('header menu', () => { menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) const headers1 = editor.getElemsByTypePrefix('header5') + expect(headers1.length).toBe(1) menu.exec(editor, 'header5') // 取消 header( header5 是当前选中的 node type ) const headers2 = editor.getElemsByTypePrefix('header5') + expect(headers2.length).toBe(0) }) }) @@ -116,10 +127,12 @@ describe('header menu', () => { menu.exec(editor, 'paragraph') // 设置 header ( paragraph 是当前选中的 node type ) const headers1 = editor.getElemsByTypePrefix('header6') + expect(headers1.length).toBe(1) menu.exec(editor, 'header6') // 取消 header( header6 是当前选中的 node type ) const headers2 = editor.getElemsByTypePrefix('header6') + expect(headers2.length).toBe(0) }) }) diff --git a/packages/basic-modules/__tests__/header/menu/header-select-menu.test.ts b/packages/basic-modules/__tests__/header/menu/header-select-menu.test.ts index 5255e1103..0eb7239a6 100644 --- a/packages/basic-modules/__tests__/header/menu/header-select-menu.test.ts +++ b/packages/basic-modules/__tests__/header/menu/header-select-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import HeaderSelectMenu from '../../../src/modules/header/menu/HeaderSelectMenu' @@ -16,11 +17,13 @@ describe('header select menu', () => { editor.select(startLocation) const options1 = menu.getOptions(editor) const selectedP = options1.some(opt => opt.selected && opt.value === 'paragraph') // 选中“文本” + expect(selectedP).toBeTruthy() Transforms.setNodes(editor, { type: 'header1' }) const options2 = menu.getOptions(editor) const selectedHeader = options2.some(opt => opt.selected && opt.value === 'header1') // 选中“h1” + expect(selectedHeader).toBeTruthy() }) @@ -38,6 +41,7 @@ describe('header select menu', () => { menu.exec(editor, 'header1') const headers1 = editor.getElemsByTypePrefix('header1') + expect(headers1.length).toBe(1) }) }) diff --git a/packages/basic-modules/__tests__/header/parse-html.test.ts b/packages/basic-modules/__tests__/header/parse-html.test.ts index 439ba36c2..f935abcdf 100644 --- a/packages/basic-modules/__tests__/header/parse-html.test.ts +++ b/packages/basic-modules/__tests__/header/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseHeader1HtmlConf } from '../../src/modules/header/parse-elem-html' @@ -11,21 +12,23 @@ describe('header - parse html', () => { const editor = createEditor() it('with children', () => { - const $h1 = $(`

`) + const $h1 = $('

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

hello world

`) + const $h1 = $('

hello world

') const image = [{ type: 'image', children: [{ text: '' }] }] const table = [{ type: 'table-cell', children: [{ text: 'hello world' }] }] @@ -34,18 +37,19 @@ describe('header - parse html', () => { // parse html let res = parseHeader1HtmlConf.parseElemHtml($h1[0], [], editor) + expect(res).toEqual({ - type: `header1`, + type: 'header1', children: [{ text: 'hello world' }], }) res = parseHeader1HtmlConf.parseElemHtml($h1[0], image, editor) expect(res).toEqual({ - type: `header1`, + type: 'header1', children: [{ type: 'image', children: [{ text: '' }] }], }) res = parseHeader1HtmlConf.parseElemHtml($h1[0], table, editor) expect(res).toEqual({ - type: `header1`, + type: 'header1', children: [{ text: 'hello world' }], }) }) diff --git a/packages/basic-modules/__tests__/header/plugin.test.ts b/packages/basic-modules/__tests__/header/plugin.test.ts index 9165f3245..765cf5bb8 100644 --- a/packages/basic-modules/__tests__/header/plugin.test.ts +++ b/packages/basic-modules/__tests__/header/plugin.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import withHeader from '../../src/modules/header/plugin' @@ -24,6 +25,7 @@ describe('header plugin', () => { editor.insertBreak() const paragraphs = editor.getElemsByTypePrefix('paragraph') + expect(paragraphs.length).toBe(2) }) }) diff --git a/packages/basic-modules/__tests__/header/render-elem.test.ts b/packages/basic-modules/__tests__/header/render-elem.test.ts index f3482bd1d..1ecbc60cc 100644 --- a/packages/basic-modules/__tests__/header/render-elem.test.ts +++ b/packages/basic-modules/__tests__/header/render-elem.test.ts @@ -20,6 +20,7 @@ describe('render header elem', () => { const elem = { type: 'header1', children: [] } const vnode = renderHeader1Conf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('h1') }) @@ -28,6 +29,7 @@ describe('render header elem', () => { const elem = { type: 'header2', children: [] } const vnode = renderHeader2Conf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('h2') }) @@ -36,6 +38,7 @@ describe('render header elem', () => { const elem = { type: 'header3', children: [] } const vnode = renderHeader3Conf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('h3') }) @@ -44,6 +47,7 @@ describe('render header elem', () => { const elem = { type: 'header4', children: [] } const vnode = renderHeader4Conf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('h4') }) @@ -52,6 +56,7 @@ describe('render header elem', () => { const elem = { type: 'header5', children: [] } const vnode = renderHeader5Conf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('h5') }) }) diff --git a/packages/basic-modules/__tests__/image/elem-to-html.test.ts b/packages/basic-modules/__tests__/image/elem-to-html.test.ts index a704eecba..b092fa0e4 100644 --- a/packages/basic-modules/__tests__/image/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/image/elem-to-html.test.ts @@ -22,7 +22,7 @@ describe('image to html', () => { const html = imageToHtmlConf.elemToHtml(elem, '') expect(html).toBe( - `logo` + `logo`, ) }) }) diff --git a/packages/basic-modules/__tests__/image/helper.test.ts b/packages/basic-modules/__tests__/image/helper.test.ts index 30e6c9494..be2a07059 100644 --- a/packages/basic-modules/__tests__/image/helper.test.ts +++ b/packages/basic-modules/__tests__/image/helper.test.ts @@ -3,13 +3,14 @@ * @author wangfupeng */ -import { Editor, Transforms } from 'slate' import { DomEditor } from '@wangeditor-next/core' +import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { insertImageNode, - updateImageNode, isInsertImageMenuDisabled, + updateImageNode, } from '../../src/modules/image/helper' describe('image helper', () => { @@ -34,7 +35,8 @@ describe('image helper', () => { } const editorConfig = { MENU_CONF: {} } - editorConfig.MENU_CONF['insertImage'] = { + + editorConfig.MENU_CONF.insertImage = { checkImage: customCheckImageFn, } @@ -57,20 +59,24 @@ describe('image helper', () => { await insertImageNode(editor, emptySrc, alt, href) await insertImageNode(editor, inValidSrc, alt, href) const images = editor.getElemsByTypePrefix('image') + expect(images.length).toBe(2) }) it('parse image src', async () => { const editorConfig = { MENU_CONF: {} } - editorConfig.MENU_CONF['insertImage'] = { + + editorConfig.MENU_CONF.insertImage = { parseImageSrc: false, } const editor = createEditor({ config: editorConfig, }) + editor.select(startLocation) await insertImageNode(editor, src, alt, href) const images = editor.getElemsByTypePrefix('image') + expect(images.length).toBe(1) }) @@ -85,6 +91,7 @@ describe('image helper', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -94,9 +101,11 @@ describe('image helper', () => { const newSrc = 'https://www.baidu.com/logo.png' const newAlt = 'baidu' const newHref = 'https://www.baidu.com/' + await updateImageNode(editor, newSrc, newAlt, newHref, {}) // 更新图片信息 const imageNode = DomEditor.getSelectedNodeByType(editor, 'image') + expect(imageNode).not.toBeNull() }) diff --git a/packages/basic-modules/__tests__/image/menu/del-image.test.ts b/packages/basic-modules/__tests__/image/menu/del-image.test.ts index 8919d17bc..a383c5b52 100644 --- a/packages/basic-modules/__tests__/image/menu/del-image.test.ts +++ b/packages/basic-modules/__tests__/image/menu/del-image.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import DeleteImage from '../../../src/modules/image/menu/DeleteImage' @@ -48,6 +49,7 @@ describe('delete image menu', () => { href, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -65,6 +67,7 @@ describe('delete image menu', () => { href, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -73,6 +76,7 @@ describe('delete image menu', () => { menu.exec(editor, '') const images = editor.getElemsByTypePrefix('image') + expect(images.length).toBe(0) }) }) diff --git a/packages/basic-modules/__tests__/image/menu/edit-image-size.test.ts b/packages/basic-modules/__tests__/image/menu/edit-image-size.test.ts index 18cdfb3cd..dfb904607 100644 --- a/packages/basic-modules/__tests__/image/menu/edit-image-size.test.ts +++ b/packages/basic-modules/__tests__/image/menu/edit-image-size.test.ts @@ -3,10 +3,11 @@ * @author cycleccc */ +import { fireEvent, waitFor } from '@testing-library/dom' import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import EditImageSize from '../../../src/modules/image/menu/EditImageSizeMenu' -import { fireEvent, waitFor } from '@testing-library/dom' describe('edit image size menu', () => { const menu = new EditImageSize() @@ -54,6 +55,7 @@ describe('edit image size menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -74,12 +76,14 @@ describe('edit image size menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 offset: 0, }) const imageNode = menu.getModalPositionNode(editor) + expect((imageNode as any).src).toBe(src) }) @@ -102,6 +106,7 @@ describe('edit image size menu', () => { const spy = jest.spyOn(editor, 'hidePanelOrModal') const elem = menu.getModalContentElem(editor) + document.body.appendChild(elem) // 使用类型断言访问私有属性 @@ -139,6 +144,7 @@ describe('edit image size menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], } + editor.select(startLocation) editor.insertNode(imageElem) editor.select({ @@ -148,6 +154,7 @@ describe('edit image size menu', () => { menu.getModalContentElem(editor) const inputSrc = document.getElementById((menu as any).widthInputId) as HTMLInputElement + jest.spyOn(inputSrc, 'focus') await waitFor(() => { diff --git a/packages/basic-modules/__tests__/image/menu/edit-image.test.ts b/packages/basic-modules/__tests__/image/menu/edit-image.test.ts index 90f054b68..5a5aee63e 100644 --- a/packages/basic-modules/__tests__/image/menu/edit-image.test.ts +++ b/packages/basic-modules/__tests__/image/menu/edit-image.test.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { fireEvent, waitFor } from '@testing-library/dom' import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import EditImage from '../../../src/modules/image/menu/EditImage' -import { fireEvent, waitFor } from '@testing-library/dom' describe('edit image menu', () => { const menu = new EditImage() @@ -50,6 +51,7 @@ describe('edit image menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -70,12 +72,14 @@ describe('edit image menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 offset: 0, }) const imageNode = menu.getModalPositionNode(editor) + expect((imageNode as any).src).toBe(src) }) @@ -88,6 +92,7 @@ describe('edit image menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + expect(() => menu.getModalContentElem(editor)).toThrow('Not found selected image node') editor.select(startLocation) editor.insertNode(imageElem) // 插入图片 @@ -97,6 +102,7 @@ describe('edit image menu', () => { }) const elem = menu.getModalContentElem(editor) + expect(elem.tagName).toBe('DIV') // updateImage 在 helper.test.ts 中测试 @@ -120,6 +126,7 @@ describe('edit image menu', () => { }) const elem = menu.getModalContentElem(editor) + document.body.appendChild(elem) // 使用类型断言访问私有属性 @@ -145,7 +152,7 @@ describe('edit image menu', () => { expect.any(Object), // editor 对象 'https://example.com/new-image.jpg', 'new alt text', - 'https://example.com/new-link' + 'https://example.com/new-link', ) editor.select(startLocation) @@ -161,6 +168,7 @@ describe('edit image menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], } + editor.select(startLocation) editor.insertNode(imageElem) editor.select({ @@ -170,6 +178,7 @@ describe('edit image menu', () => { menu.getModalContentElem(editor) const inputSrc = document.getElementById((menu as any).srcInputId) as HTMLInputElement + jest.spyOn(inputSrc, 'focus') await waitFor(() => { diff --git a/packages/basic-modules/__tests__/image/menu/insert-image.test.ts b/packages/basic-modules/__tests__/image/menu/insert-image.test.ts index 084e7ca6d..367a54324 100644 --- a/packages/basic-modules/__tests__/image/menu/insert-image.test.ts +++ b/packages/basic-modules/__tests__/image/menu/insert-image.test.ts @@ -3,11 +3,12 @@ * @author wangfupeng */ +import { waitFor } from '@testing-library/dom' import { Editor, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' -import InsertImage from '../../../src/modules/image/menu/InsertImage' -import { waitFor } from '@testing-library/dom' import * as helper from '../../../src/modules/image/helper' +import InsertImage from '../../../src/modules/image/menu/InsertImage' // 在测试文件中 // beforeEach(() => { @@ -66,16 +67,19 @@ describe('insert image menu', () => { // Generate modal content and simulate button click const elem = menu.getModalContentElem(editor) + expect(elem).not.toBeNull() // Non-null assertion for button const button = elem.querySelector(`#${(menu as any).buttonId}`) as HTMLButtonElement + expect(button).not.toBeNull() // Non-null assertion for input fields const srcInput = elem.querySelector(`#${(menu as any).srcInputId}`) as HTMLInputElement const altInput = elem.querySelector(`#${(menu as any).altInputId}`) as HTMLInputElement const hrefInput = elem.querySelector(`#${(menu as any).hrefInputId}`) as HTMLInputElement + expect(srcInput).not.toBeNull() expect(altInput).not.toBeNull() expect(hrefInput).not.toBeNull() @@ -96,7 +100,7 @@ describe('insert image menu', () => { expect.any(Object), 'https://example.com/new-image.jpg', 'new alt text', - 'https://example.com' + 'https://example.com', ) editor.select(startLocation) @@ -106,6 +110,7 @@ describe('insert image menu', () => { it('focus input asynchronously', async () => { menu.getModalContentElem(editor) const inputSrc = document.getElementById((menu as any).srcInputId) as HTMLInputElement + jest.spyOn(inputSrc, 'focus') await waitFor(() => { diff --git a/packages/basic-modules/__tests__/image/menu/view-image-link.test.ts b/packages/basic-modules/__tests__/image/menu/view-image-link.test.ts index 5964b0343..2bf51328f 100644 --- a/packages/basic-modules/__tests__/image/menu/view-image-link.test.ts +++ b/packages/basic-modules/__tests__/image/menu/view-image-link.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import ViewImageLink from '../../../src/modules/image/menu/ViewImageLink' @@ -39,6 +40,7 @@ describe('view image link menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -56,6 +58,7 @@ describe('view image link menu', () => { editor.select(startLocation) const value = '' const url = 'https://github.com/cycleccc/wangEditor-next' + expect(menu.exec(editor, value)).toBeUndefined() const elem = { type: 'image', @@ -65,13 +68,14 @@ describe('view image link menu', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 offset: 0, }) expect(() => menu.exec(editor, value)).toThrow( - `View image link failed, image.href is '${value}'` + `View image link failed, image.href is '${value}'`, ) menu.exec(editor, url) }) diff --git a/packages/basic-modules/__tests__/image/menu/width-menus.test.ts b/packages/basic-modules/__tests__/image/menu/width-menus.test.ts index 1abb6c694..0090cc528 100644 --- a/packages/basic-modules/__tests__/image/menu/width-menus.test.ts +++ b/packages/basic-modules/__tests__/image/menu/width-menus.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import Width30 from '../../../src/modules/image/menu/Width30' import Width50 from '../../../src/modules/image/menu/Width50' @@ -58,6 +59,7 @@ describe('image width menus', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -78,6 +80,7 @@ describe('image width menus', () => { style: { width: '100', height: '80' }, children: [{ text: '' }], // void node 必须包含一个空 text } + editor.insertNode(elem) // 插入图片 editor.select({ path: [0, 1, 0], // 选中图片 @@ -86,16 +89,19 @@ describe('image width menus', () => { width30Menu.exec(editor, '') const image1 = editor.getElemsByTypePrefix('image')[0] + expect(image1.style.width).toBe('30%') expect(image1.style.height).toBe('') width50Menu.exec(editor, '') const image2 = editor.getElemsByTypePrefix('image')[0] + expect(image2.style.width).toBe('50%') expect(image2.style.height).toBe('') width100Menu.exec(editor, '') const image3 = editor.getElemsByTypePrefix('image')[0] + expect(image3.style.width).toBe('100%') expect(image3.style.height).toBe('') }) diff --git a/packages/basic-modules/__tests__/image/parse-html.test.ts b/packages/basic-modules/__tests__/image/parse-html.test.ts index 109a46131..3524a2e5e 100644 --- a/packages/basic-modules/__tests__/image/parse-html.test.ts +++ b/packages/basic-modules/__tests__/image/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseHtmlConf } from '../../src/modules/image/parse-elem-html' @@ -12,7 +13,7 @@ describe('image - parse html', () => { it('parse html', () => { const $img = $( - 'hello' + 'hello', ) // match selector @@ -20,6 +21,7 @@ describe('image - parse html', () => { // parse const res = parseHtmlConf.parseElemHtml($img[0], [], editor) + expect(res).toEqual({ type: 'image', src: 'hello.png', diff --git a/packages/basic-modules/__tests__/image/render-elem.test.ts b/packages/basic-modules/__tests__/image/render-elem.test.ts index 86ea2d4dd..0ec36e7ac 100644 --- a/packages/basic-modules/__tests__/image/render-elem.test.ts +++ b/packages/basic-modules/__tests__/image/render-elem.test.ts @@ -4,8 +4,9 @@ */ import { Editor } from 'slate' -import { renderImageConf } from '../../src/modules/image/render-elem' + import createEditor from '../../../../tests/utils/create-editor' +import { renderImageConf } from '../../src/modules/image/render-elem' describe('image render elem', () => { let editor: any @@ -38,12 +39,14 @@ describe('image render elem', () => { } const containerVnode = renderImageConf.renderElem(elem, null, editor) as any + expect(containerVnode.sel).toBe('div') expect(containerVnode.data.className).toBe('w-e-image-container') expect(containerVnode.data.style.width).toBe('100') // expect(containerVnode.data.style.height).toBe('80') const imageVnode = containerVnode.children[0] as any + expect(imageVnode.sel).toBe('img') expect(imageVnode.data.src).toBe(src) expect(imageVnode.data['data-href']).toBe(href) @@ -69,6 +72,7 @@ describe('image render elem', () => { }) const containerVnode = renderImageConf.renderElem(elem, null, editor) as any + expect(containerVnode.sel).toBe('div') expect(containerVnode.data.className.indexOf('w-e-selected-image-container')).toBeGreaterThan(0) expect(containerVnode.children.length).toBe(5) // image + 4 个拖拽触手 diff --git a/packages/basic-modules/__tests__/indent/menu/decrease-indent-menu.test.ts b/packages/basic-modules/__tests__/indent/menu/decrease-indent-menu.test.ts index db38baf90..ec9c52c6d 100644 --- a/packages/basic-modules/__tests__/indent/menu/decrease-indent-menu.test.ts +++ b/packages/basic-modules/__tests__/indent/menu/decrease-indent-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import DecreaseIndentMenu from '../../../src/modules/indent/menu/DecreaseIndentMenu' diff --git a/packages/basic-modules/__tests__/indent/menu/increase-indent-menu.test.ts b/packages/basic-modules/__tests__/indent/menu/increase-indent-menu.test.ts index 611b01385..acb18c5f8 100644 --- a/packages/basic-modules/__tests__/indent/menu/increase-indent-menu.test.ts +++ b/packages/basic-modules/__tests__/indent/menu/increase-indent-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import IncreaseIndentMenu from '../../../src/modules/indent/menu/IncreaseIndentMenu' diff --git a/packages/basic-modules/__tests__/indent/parse-html.test.ts b/packages/basic-modules/__tests__/indent/parse-html.test.ts index fe1354a30..00afe90c3 100644 --- a/packages/basic-modules/__tests__/indent/parse-html.test.ts +++ b/packages/basic-modules/__tests__/indent/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseStyleHtml } from '../../src/modules/indent/parse-style-html' import { preParseHtmlConf } from '../../src/modules/indent/pre-parse-html' @@ -17,6 +18,7 @@ describe('indent - parse style', () => { // parse const res = parseStyleHtml($p[0], paragraph, editor) + expect(res).toEqual({ type: 'paragraph', indent: '2em', @@ -33,6 +35,7 @@ describe('indent - pre parse html', () => { // parse const res = preParseHtmlConf.preParseHtml($p[0]) + expect((res as HTMLParagraphElement).style.textIndent).toBe('2em') }) @@ -43,6 +46,7 @@ describe('indent - pre parse html', () => { // parse const res = preParseHtmlConf.preParseHtml($p[0]) + expect((res as HTMLParagraphElement).style.textIndent).toBe('2em') }) }) diff --git a/packages/basic-modules/__tests__/indent/render-text-style.test.tsx b/packages/basic-modules/__tests__/indent/render-text-style.test.tsx index 910ff58f3..272b44a17 100644 --- a/packages/basic-modules/__tests__/indent/render-text-style.test.tsx +++ b/packages/basic-modules/__tests__/indent/render-text-style.test.tsx @@ -4,6 +4,7 @@ */ import { jsx } from 'snabbdom' + import { renderStyle } from '../../src/modules/indent/render-style' describe('indent - render text style', () => { @@ -15,6 +16,7 @@ describe('indent - render text style', () => { // @ts-ignore const newVnode = renderStyle(elem, vnode) // @ts-ignore + expect(newVnode.data.style.textIndent).toBe(indent) }) }) diff --git a/packages/basic-modules/__tests__/indent/text-style-to-html.test.ts b/packages/basic-modules/__tests__/indent/text-style-to-html.test.ts index 2c40a0e17..a48f0c63d 100644 --- a/packages/basic-modules/__tests__/indent/text-style-to-html.test.ts +++ b/packages/basic-modules/__tests__/indent/text-style-to-html.test.ts @@ -10,6 +10,7 @@ describe('indent - text style to html', () => { const indent = '2em' const elem = { type: 'paragraph', indent, children: [] } const html = styleToHtml(elem, '

hello

') + expect(html).toBe(`

hello

`) }) }) diff --git a/packages/basic-modules/__tests__/justify/menus.test.ts b/packages/basic-modules/__tests__/justify/menus.test.ts index e007baf5f..0e90c2ea5 100644 --- a/packages/basic-modules/__tests__/justify/menus.test.ts +++ b/packages/basic-modules/__tests__/justify/menus.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import JustifyCenterMenu from '../../src/modules/justify/menu/JustifyCenterMenu' import JustifyJustifyMenu from '../../src/modules/justify/menu/JustifyJustifyMenu' @@ -57,18 +58,22 @@ describe('justify menus', () => { centerMenu.exec(editor, '') const p1 = editor.getElemsByTypePrefix('paragraph')[0] + expect(p1.textAlign).toBe('center') justifyMenu.exec(editor, '') const p2 = editor.getElemsByTypePrefix('paragraph')[0] + expect(p2.textAlign).toBe('justify') leftMenu.exec(editor, '') const p3 = editor.getElemsByTypePrefix('paragraph')[0] + expect(p3.textAlign).toBe('left') rightMenu.exec(editor, '') const p4 = editor.getElemsByTypePrefix('paragraph')[0] + expect(p4.textAlign).toBe('right') }) }) diff --git a/packages/basic-modules/__tests__/justify/parse-html.test.ts b/packages/basic-modules/__tests__/justify/parse-html.test.ts index 56f398983..827948324 100644 --- a/packages/basic-modules/__tests__/justify/parse-html.test.ts +++ b/packages/basic-modules/__tests__/justify/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseStyleHtml } from '../../src/modules/justify/parse-style-html' @@ -16,6 +17,7 @@ describe('text align - parse style', () => { // parse const res = parseStyleHtml($p[0], paragraph, editor) + expect(res).toEqual({ type: 'paragraph', textAlign: 'center', diff --git a/packages/basic-modules/__tests__/justify/render-text-style.test.tsx b/packages/basic-modules/__tests__/justify/render-text-style.test.tsx index b9e03b994..2a3e373ff 100644 --- a/packages/basic-modules/__tests__/justify/render-text-style.test.tsx +++ b/packages/basic-modules/__tests__/justify/render-text-style.test.tsx @@ -4,6 +4,7 @@ */ import { jsx } from 'snabbdom' + import { renderStyle } from '../../src/modules/justify/render-style' describe('justify - render text style', () => { @@ -13,6 +14,7 @@ describe('justify - render text style', () => { // @ts-ignore 忽略 vnode 格式 const newVnode = renderStyle(elem, vnode) // @ts-ignore 忽略 vnode 格式 + expect(newVnode.data.style?.textAlign).toBe('center') }) }) diff --git a/packages/basic-modules/__tests__/justify/text-style-to-html.test.ts b/packages/basic-modules/__tests__/justify/text-style-to-html.test.ts index 53037f411..9563666c8 100644 --- a/packages/basic-modules/__tests__/justify/text-style-to-html.test.ts +++ b/packages/basic-modules/__tests__/justify/text-style-to-html.test.ts @@ -9,6 +9,7 @@ describe('justify text-style-to-html', () => { it('text style to html', () => { const elem = { type: 'paragraph', textAlign: 'center', children: [] } const html = styleToHtml(elem, 'hello') + expect(html).toBe('hello') }) }) diff --git a/packages/basic-modules/__tests__/line-height/line-height-menu.test.ts b/packages/basic-modules/__tests__/line-height/line-height-menu.test.ts index 45c2a7f8e..ffd94353e 100644 --- a/packages/basic-modules/__tests__/line-height/line-height-menu.test.ts +++ b/packages/basic-modules/__tests__/line-height/line-height-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import LineHeightMenu from '../../src/modules/line-height/menu/LineHeightMenu' @@ -26,10 +27,12 @@ describe('line-height menu', () => { editor.select(startLocation) const options = menu.getOptions(editor) + expect(options.length).toBeGreaterThan(0) // 默认选中 空 const selectedEmptyOne = options.some(opt => opt.value === '' && opt.selected) + expect(selectedEmptyOne).toBe(true) }) diff --git a/packages/basic-modules/__tests__/line-height/parse-html.test.ts b/packages/basic-modules/__tests__/line-height/parse-html.test.ts index 70c1f4c7f..e2c04d5c8 100644 --- a/packages/basic-modules/__tests__/line-height/parse-html.test.ts +++ b/packages/basic-modules/__tests__/line-height/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseStyleHtml } from '../../src/modules/line-height/parse-style-html' @@ -16,6 +17,7 @@ describe('line height - parse style', () => { // parse const res = parseStyleHtml($p[0], paragraph, editor) + expect(res).toEqual({ type: 'paragraph', lineHeight: '2.5', diff --git a/packages/basic-modules/__tests__/line-height/render-text-style.test.tsx b/packages/basic-modules/__tests__/line-height/render-text-style.test.tsx index cf11d1acb..7de85b5cb 100644 --- a/packages/basic-modules/__tests__/line-height/render-text-style.test.tsx +++ b/packages/basic-modules/__tests__/line-height/render-text-style.test.tsx @@ -4,6 +4,7 @@ */ import { jsx } from 'snabbdom' + import { renderStyle } from '../../src/modules/line-height/render-style' describe('line-height render-text-style', () => { @@ -13,6 +14,7 @@ describe('line-height render-text-style', () => { // @ts-ignore 忽略 vnode 格式检查 const newVnode = renderStyle(elem, vnode) // @ts-ignore 忽略 vnode 格式检查 + expect(newVnode.data.style.lineHeight).toBe('1.5') }) }) diff --git a/packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts b/packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts index dee81e0b8..cc8a51817 100644 --- a/packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts +++ b/packages/basic-modules/__tests__/line-height/text-style-to-html.test.ts @@ -9,6 +9,7 @@ describe('line-height text-style-to-html', () => { it('text style to html', () => { const elem = { type: 'paragraph', lineHeight: '1.5', children: [] } const html = styleToHtml(elem, 'hello') + expect(html).toBe('hello') }) }) diff --git a/packages/basic-modules/__tests__/link/elem-to-html.test.ts b/packages/basic-modules/__tests__/link/elem-to-html.test.ts index 9574de78c..6aa5feeb3 100644 --- a/packages/basic-modules/__tests__/link/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/link/elem-to-html.test.ts @@ -11,9 +11,12 @@ describe('link elem to html', () => { const url = 'https://www.wangeditor.com/' const target = '_blank' - const elem = { type: 'link', url, target, children: [] } + const elem = { + type: 'link', url, target, children: [], + } const html = linkToHtmlConf.elemToHtml(elem, 'hello') + expect(html).toBe(`hello`) }) }) diff --git a/packages/basic-modules/__tests__/link/helper.test.ts b/packages/basic-modules/__tests__/link/helper.test.ts index 7b5a47896..e8969e55c 100644 --- a/packages/basic-modules/__tests__/link/helper.test.ts +++ b/packages/basic-modules/__tests__/link/helper.test.ts @@ -4,8 +4,9 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' -import { isMenuDisabled, insertLink, updateLink } from '../../src/modules/link/helper' +import { insertLink, isMenuDisabled, updateLink } from '../../src/modules/link/helper' describe('link module helper', () => { let editor: any @@ -31,7 +32,8 @@ describe('link module helper', () => { } const editorConfig = { MENU_CONF: {}, maxLength: 20 } - editorConfig.MENU_CONF['insertLink'] = { + + editorConfig.MENU_CONF.insertLink = { checkLink: customCheckLinkFn, parseLinkUrl: customParseLinkUrl, } @@ -86,8 +88,10 @@ describe('link module helper', () => { await insertLink(editor, 'hello', inValidUrl) const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(1) const linkElem = links[0] + expect(linkElem.url).toBe(url) }) @@ -101,8 +105,10 @@ describe('link module helper', () => { await insertLink(editor, 'https://cycleccc.github.io/docs/', url) await insertLink(editor, 'hello', url) const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(1) const linkElem = links[0] + expect(linkElem.url).toBe(url) }) @@ -115,6 +121,7 @@ describe('link module helper', () => { await insertLink(editor, 'hello', url) const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(0) }) @@ -128,11 +135,14 @@ describe('link module helper', () => { editor.select([]) // 全选 const url = 'https://cycleccc.github.io/docs/' + await insertLink(editor, 'hello', url) const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(1) const linkElem = links[0] + expect(linkElem.url).toBe(url) }) @@ -146,11 +156,14 @@ describe('link module helper', () => { editor.select([]) // 全选 const url = 'https://cycleccc.github.io/docs/' + await insertLink(editor, 'hello', url) const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(1) const linkElem = links[0] + expect(linkElem.url).toBe(url) }) @@ -163,10 +176,13 @@ describe('link module helper', () => { }) editor.select([]) const url = 'https://cycleccc.github.io/docs/' + await insertLink(editor, 'hello', url) - let links = editor.getElemsByTypePrefix('link') + const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(1) - let linkElem = links[0] + const linkElem = links[0] + expect(linkElem.url).toBe(url) }) @@ -175,23 +191,28 @@ describe('link module helper', () => { editor.insertText('123445678901234567890') editor.select([]) const url = 'https://cycleccc.github.io/docs/' + await insertLink(editor, 'hello', url) - let links = editor.getElemsByTypePrefix('link') + const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(0) }) it('parse link', async () => { const url = 'https://cycleccc.github.io/docs/' const editorConfig = { MENU_CONF: {} } - editorConfig.MENU_CONF['insertLink'] = { + + editorConfig.MENU_CONF.insertLink = { parseLinkUrl: false, } const editor = createEditor({ config: editorConfig, }) + editor.select(startLocation) await insertLink(editor, 'hello', url) const images = editor.getElemsByTypePrefix('image') + expect(images.length).toBe(0) }) @@ -199,6 +220,7 @@ describe('link module helper', () => { editor.select(startLocation) const url = 'https://cycleccc.github.io/docs/' + await insertLink(editor, 'hello', url) // 选区移动到 link 内部 @@ -209,11 +231,14 @@ describe('link module helper', () => { // 更新链接 const newUrl = 'https://cycleccc.github.io/docs/index.html' + await updateLink(editor, '', newUrl) const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(1) const linkElem = links[0] + expect(linkElem.url).toBe(newUrl) }) }) diff --git a/packages/basic-modules/__tests__/link/menu/edit-link-menu.test.ts b/packages/basic-modules/__tests__/link/menu/edit-link-menu.test.ts index 72eb10954..8f98dc566 100644 --- a/packages/basic-modules/__tests__/link/menu/edit-link-menu.test.ts +++ b/packages/basic-modules/__tests__/link/menu/edit-link-menu.test.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { waitFor } from '@testing-library/dom' import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import EditLink from '../../../src/modules/link/menu/EditLink' -import { waitFor } from '@testing-library/dom' describe('edit link menu', () => { let editor: any @@ -67,6 +68,7 @@ describe('edit link menu', () => { offset: 1, }) const node = menu.getModalPositionNode(editor) as any + expect(node.type).toBe('link') expect(node.url).toBe(linkNode.url) }) @@ -74,12 +76,14 @@ describe('edit link menu', () => { it('get modal content elem', () => { const spy = jest.spyOn(editor, 'hidePanelOrModal') const elem = menu.getModalContentElem(editor) + editor.select(startLocation) editor.insertText('test') document.body.appendChild(elem) const urlInputId = document.getElementById((menu as any).urlInputId) as HTMLInputElement const button = document.getElementById((menu as any).buttonId) as HTMLButtonElement + urlInputId.value = 'https://cycleccc.github.io/demo/' editor.select(startLocation) button.click() @@ -95,6 +99,7 @@ describe('edit link menu', () => { menu.getModalContentElem(editor) const inputSrc = document.getElementById((menu as any).urlInputId) as HTMLInputElement + jest.spyOn(inputSrc, 'focus') await waitFor(() => { diff --git a/packages/basic-modules/__tests__/link/menu/insert-link-menu.test.ts b/packages/basic-modules/__tests__/link/menu/insert-link-menu.test.ts index 07e025892..7f5de0ada 100644 --- a/packages/basic-modules/__tests__/link/menu/insert-link-menu.test.ts +++ b/packages/basic-modules/__tests__/link/menu/insert-link-menu.test.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { waitFor } from '@testing-library/dom' import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import InsertLinkMenu from '../../../src/modules/link/menu/InsertLink' -import { waitFor } from '@testing-library/dom' describe('insert link menu', () => { let editor: any @@ -48,6 +49,7 @@ describe('insert link menu', () => { it('get modal content elem', () => { const spy = jest.spyOn(editor, 'hidePanelOrModal') const elem = menu.getModalContentElem(editor) + editor.select(startLocation) editor.insertText('test') document.body.appendChild(elem) @@ -56,6 +58,7 @@ describe('insert link menu', () => { const urlInputId = document.getElementById((menu as any).urlInputId) as HTMLInputElement const button = document.getElementById((menu as any).buttonId) as HTMLButtonElement // 模拟用户输入 + textInputId.value = 'hello' urlInputId.value = 'https://cycleccc.github.io/docs/' editor.select(startLocation) @@ -72,6 +75,7 @@ describe('insert link menu', () => { menu.getModalContentElem(editor) const inputSrc = document.getElementById((menu as any).textInputId) as HTMLInputElement + jest.spyOn(inputSrc, 'focus') await waitFor(() => { diff --git a/packages/basic-modules/__tests__/link/menu/unlink-menu.test.ts b/packages/basic-modules/__tests__/link/menu/unlink-menu.test.ts index da113b8fe..37110f819 100644 --- a/packages/basic-modules/__tests__/link/menu/unlink-menu.test.ts +++ b/packages/basic-modules/__tests__/link/menu/unlink-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import UnLink from '../../../src/modules/link/menu/UnLink' @@ -58,6 +59,7 @@ describe('unlink menu test', () => { menu.exec(editor, '') const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(0) }) }) diff --git a/packages/basic-modules/__tests__/link/menu/view-link-menu.test.ts b/packages/basic-modules/__tests__/link/menu/view-link-menu.test.ts index 720a62708..18c795a11 100644 --- a/packages/basic-modules/__tests__/link/menu/view-link-menu.test.ts +++ b/packages/basic-modules/__tests__/link/menu/view-link-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import ViewLink from '../../../src/modules/link/menu/ViewLink' diff --git a/packages/basic-modules/__tests__/link/parse-html.test.ts b/packages/basic-modules/__tests__/link/parse-html.test.ts index d83a6bbfa..752f75b84 100644 --- a/packages/basic-modules/__tests__/link/parse-html.test.ts +++ b/packages/basic-modules/__tests__/link/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseHtmlConf } from '../../src/modules/link/parse-elem-html' @@ -17,6 +18,7 @@ describe('link - parse html', () => { // parse is block let res = parseHtmlConf.parseElemHtml($link[0], table, editor) + expect(res).toEqual({ type: 'link', url: 'http://localhost/', @@ -43,6 +45,7 @@ describe('link - parse html', () => { // parse const res = parseHtmlConf.parseElemHtml($link[0], children, editor) + expect(res).toEqual({ type: 'link', url: 'http://localhost/', diff --git a/packages/basic-modules/__tests__/link/plugin.test.ts b/packages/basic-modules/__tests__/link/plugin.test.ts index c37747b84..558c25642 100644 --- a/packages/basic-modules/__tests__/link/plugin.test.ts +++ b/packages/basic-modules/__tests__/link/plugin.test.ts @@ -4,15 +4,18 @@ */ import { Editor } from 'slate' -import withLink from '../../src/modules/link/plugin' + import createEditor from '../../../../tests/utils/create-editor' +import withLink from '../../src/modules/link/plugin' // 模拟 DataTransfer class MyDataTransfer { private values: object = {} + setData(type: string, value: string) { this.values[type] = value } + getData(type: string): string { return this.values[type] } @@ -24,6 +27,7 @@ describe('link plugin', () => { it('link is inline elem', () => { const elem = { type: 'link', children: [] } + expect(editor.isInline(elem)).toBeTruthy() }) @@ -31,6 +35,7 @@ describe('link plugin', () => { const url = 'https://cycleccc.github.io/docs/' const data = new MyDataTransfer() + data.setData('text/plain', url) editor.select(startLocation) @@ -39,8 +44,10 @@ describe('link plugin', () => { setTimeout(() => { const links = editor.getElemsByTypePrefix('link') + expect(links.length).toBe(1) const linkElem = links[0] as any + expect(linkElem.url).toBe(url) done() }) @@ -50,6 +57,7 @@ describe('link plugin', () => { const imgUrl = 'https://cycleccc.github.io/docs/image/logo.png' const data = new MyDataTransfer() + data.setData('text/html', imgHtml) data.setData('text/plain', imgUrl) @@ -59,8 +67,10 @@ describe('link plugin', () => { setTimeout(() => { const images = editor.getElemsByTypePrefix('image') + expect(images.length).toBe(1) const imgElem = images[0] as any + expect(imgElem.src).toBe('https://www.wangeditor.com/img.jpg') done() }) @@ -70,6 +80,7 @@ describe('link plugin', () => { const text = 'This is a test text.' const data = new MyDataTransfer() + data.setData('text/plain', text) editor.select(startLocation) @@ -78,6 +89,7 @@ describe('link plugin', () => { setTimeout(() => { const content = Editor.string(editor, []) + expect(content).toContain(text) done() }) diff --git a/packages/basic-modules/__tests__/link/render-elem.test.ts b/packages/basic-modules/__tests__/link/render-elem.test.ts index d9e0e8082..56b582273 100644 --- a/packages/basic-modules/__tests__/link/render-elem.test.ts +++ b/packages/basic-modules/__tests__/link/render-elem.test.ts @@ -14,9 +14,12 @@ describe('link render elem', () => { const url = 'https://www.wangeditor.com/' const target = '_blank' - const elem = { type: 'link', url, target, children: [] } + const elem = { + type: 'link', url, target, children: [], + } const vnode = renderLinkConf.renderElem(elem, null, editor) as any + expect(vnode.sel).toBe('a') expect(vnode.data.href).toBe(url) expect(vnode.data.target).toBe(target) diff --git a/packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts b/packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts index dff797c1f..cad18f38c 100644 --- a/packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/paragraph/elem-to-html.test.ts @@ -1,9 +1,9 @@ import { html } from 'dom7' + /** * @description paragraph - elem to html test * @author wangfupeng */ - import { pToHtmlConf } from '../../src/modules/paragraph/elem-to-html' describe('paragraph - elem to html', () => { @@ -12,6 +12,7 @@ describe('paragraph - elem to html', () => { const elem = { type: 'paragraph', children: [] } const html = pToHtmlConf.elemToHtml(elem, 'hello') + expect(html).toBe('

hello

') }) @@ -20,6 +21,7 @@ describe('paragraph - elem to html', () => { const elem = { type: 'paragraph', children: [] } const html = pToHtmlConf.elemToHtml(elem, '') + expect(html).toBe('


') }) }) diff --git a/packages/basic-modules/__tests__/paragraph/parse-html.test.ts b/packages/basic-modules/__tests__/paragraph/parse-html.test.ts index 412fa2d47..1efa3014d 100644 --- a/packages/basic-modules/__tests__/paragraph/parse-html.test.ts +++ b/packages/basic-modules/__tests__/paragraph/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseParagraphHtmlConf } from '../../src/modules/paragraph/parse-elem-html' @@ -20,6 +21,7 @@ describe('paragraph - parse html', () => { // parse let res = parseParagraphHtmlConf.parseElemHtml($elem[0], [], editor) + expect(res).toEqual({ type: 'paragraph', children: [{ text: 'hello world' }], @@ -42,6 +44,7 @@ describe('paragraph - parse html', () => { // parse const res = parseParagraphHtmlConf.parseElemHtml($elem[0], children, editor) + expect(res).toEqual({ type: 'paragraph', children: [{ text: 'hello ' }, { text: 'world', bold: true }], diff --git a/packages/basic-modules/__tests__/paragraph/plugin.test.ts b/packages/basic-modules/__tests__/paragraph/plugin.test.ts index 9919424c8..797f8980c 100644 --- a/packages/basic-modules/__tests__/paragraph/plugin.test.ts +++ b/packages/basic-modules/__tests__/paragraph/plugin.test.ts @@ -3,13 +3,15 @@ * @author wangfupeng */ -import { Editor, Transforms, Point } from 'slate' import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { Editor, Point, Transforms } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import withParagraph from '../../src/modules/paragraph/plugin' let editor: IDomEditor let startLocation: Point + describe('paragraph plugin', () => { beforeEach(() => { editor = withParagraph(createEditor()) @@ -25,6 +27,7 @@ describe('paragraph plugin', () => { Transforms.setNodes(editor, { type: 'header1' }) // 设置 header editor.deleteBackward('character') // 向后删除 const selectedParagraph1 = DomEditor.getSelectedNodeByType(editor, 'paragraph') + expect(selectedParagraph1).not.toBeNull() // 执行删除后,header 变为 paragraph // default delete @@ -33,6 +36,7 @@ describe('paragraph plugin', () => { Transforms.setNodes(editor, { type: 'blockquote' }) // 设置 blockquote editor.deleteForward('character') // 向前删除 const selectedParagraph2 = DomEditor.getSelectedNodeByType(editor, 'paragraph') + expect(selectedParagraph2).not.toBeNull() // 执行删除后,header 变为 paragraph }) }) diff --git a/packages/basic-modules/__tests__/paragraph/render-elem.test.ts b/packages/basic-modules/__tests__/paragraph/render-elem.test.ts index e0ca77fc9..0be9df82a 100644 --- a/packages/basic-modules/__tests__/paragraph/render-elem.test.ts +++ b/packages/basic-modules/__tests__/paragraph/render-elem.test.ts @@ -14,6 +14,7 @@ describe('paragraph - render elem', () => { const elem = { type: 'paragraph', children: [] } const vnode = renderParagraphConf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('p') }) }) diff --git a/packages/basic-modules/__tests__/text-style/menu/clear-style-menu.test.ts b/packages/basic-modules/__tests__/text-style/menu/clear-style-menu.test.ts index 7e09efd19..228749f76 100644 --- a/packages/basic-modules/__tests__/text-style/menu/clear-style-menu.test.ts +++ b/packages/basic-modules/__tests__/text-style/menu/clear-style-menu.test.ts @@ -4,11 +4,12 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import ClearStyleMenu from '../../../src/modules/text-style/menu/ClearStyleMenu' describe('clear style menu', () => { - let editor = createEditor() + const editor = createEditor() const startLocation = Editor.start(editor, []) const menu = new ClearStyleMenu() @@ -41,6 +42,7 @@ describe('clear style menu', () => { menu.exec(editor, '') // 清空样式 const marks = Editor.marks(editor) as any + expect(marks.bold).toBeUndefined() expect(marks.italic).toBeUndefined() }) diff --git a/packages/basic-modules/__tests__/text-style/menu/menus.test.ts b/packages/basic-modules/__tests__/text-style/menu/menus.test.ts index 19605bf03..b41167f10 100644 --- a/packages/basic-modules/__tests__/text-style/menu/menus.test.ts +++ b/packages/basic-modules/__tests__/text-style/menu/menus.test.ts @@ -3,7 +3,8 @@ * @author wangfupeng */ -import { Editor, Transforms, Element } from 'slate' +import { Editor, Element, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import BoldMenu from '../../../src/modules/text-style/menu/BoldMenu' import CodeMenu from '../../../src/modules/text-style/menu/CodeMenu' @@ -24,7 +25,7 @@ const MENU_INFO_LIST = [ ] describe('text style menus', () => { - let editor = createEditor() + const editor = createEditor() const startLocation = Editor.start(editor, []) afterEach(() => { @@ -46,7 +47,7 @@ describe('text style menus', () => { editor.select([]) editor.addMark(mark, true) expect(menu.isActive(editor)).toBeTruthy() - editor.setHtml(`

hello

`) + editor.setHtml('

hello

') expect(menu.isActive(editor)).toBeFalsy() }) }) @@ -85,12 +86,14 @@ describe('text style menus', () => { // 增加 mark menu.exec(editor, false) const marks1 = Editor.marks(editor) as any + expect(marks1[mark]).toBeTruthy() // 取消 mark editor.select([]) menu.exec(editor, true) const marks2 = Editor.marks(editor) as any + expect(marks2[mark]).toBeUndefined() }) }) diff --git a/packages/basic-modules/__tests__/text-style/parse-style-html.test.ts b/packages/basic-modules/__tests__/text-style/parse-style-html.test.ts index c2516405c..a12c245f0 100644 --- a/packages/basic-modules/__tests__/text-style/parse-style-html.test.ts +++ b/packages/basic-modules/__tests__/text-style/parse-style-html.test.ts @@ -1,6 +1,6 @@ +import createEditor from '../../../../tests/utils/create-editor' import { parseStyleHtml } from '../../src/modules/text-style/parse-style-html' import $ from '../../src/utils/dom' -import createEditor from '../../../../tests/utils/create-editor' describe('parse style html', () => { const editor = createEditor() @@ -8,72 +8,84 @@ describe('parse style html', () => { it('it should return directly if give node that type is not text', () => { const element = $('

') const node = { type: 'paragraph', children: [] } + expect(parseStyleHtml(element[0], node, editor)).toEqual(node) }) it('it should do nothing if give not exist element', () => { const element = $('#text') const node = { type: 'paragraph', children: [] } + expect(parseStyleHtml(element[0], node, editor)).toEqual(node) }) it('it should set bold property for node if give strong element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, bold: true }) }) it('it should set bold property for node if give b element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, bold: true }) }) it('it should set italic property for node if give i element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, italic: true }) }) it('it should set italic property for node if give em element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, italic: true }) }) it('it should set underline property for node if give u element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, underline: true }) }) it('it should set through property for node if give s element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, through: true }) }) it('it should set through property for node if give strike element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, through: true }) }) it('it should set sub property for node if give sub element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, sub: true }) }) it('it should set sup property for node if give sup element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, sup: true }) }) it('it should set code property for node if give code element', () => { const element = $('') const node = { text: 'text' } + expect(parseStyleHtml(element[0], node, editor)).toEqual({ ...node, code: true }) }) }) diff --git a/packages/basic-modules/__tests__/text-style/text-style.test.tsx b/packages/basic-modules/__tests__/text-style/text-style.test.tsx index d8bf265fd..e0ab5c0d1 100644 --- a/packages/basic-modules/__tests__/text-style/text-style.test.tsx +++ b/packages/basic-modules/__tests__/text-style/text-style.test.tsx @@ -4,8 +4,9 @@ */ import { jsx } from 'snabbdom' -import { renderStyle } from '../../src/modules/text-style/render-style' + import { StyledText } from '../../src/modules/text-style/custom-types' +import { renderStyle } from '../../src/modules/text-style/render-style' describe('text style - render text style', () => { it('render text style', () => { diff --git a/packages/basic-modules/__tests__/text-style/text-to-html.test.ts b/packages/basic-modules/__tests__/text-style/text-to-html.test.ts index 51be1696a..0ce85f8f8 100644 --- a/packages/basic-modules/__tests__/text-style/text-to-html.test.ts +++ b/packages/basic-modules/__tests__/text-style/text-to-html.test.ts @@ -20,14 +20,16 @@ describe('text style - text to html', () => { // is plain text const html1 = styleToHtml(textNode, 'hello') + expect(html1).toBe( - 'hello' + 'hello', ) // is text tag (exclude
) const html2 = styleToHtml(textNode, 'world') + expect(html2).toBe( - 'world' + 'world', ) }) }) diff --git a/packages/basic-modules/__tests__/todo/elem-to-html.test.ts b/packages/basic-modules/__tests__/todo/elem-to-html.test.ts index a26252f2a..fbe7df87d 100644 --- a/packages/basic-modules/__tests__/todo/elem-to-html.test.ts +++ b/packages/basic-modules/__tests__/todo/elem-to-html.test.ts @@ -15,8 +15,9 @@ describe('todo - elem to html', () => { children: [{ text: '' }], } const html1 = todoToHtmlConf.elemToHtml(todoNode1, 'hello') + expect(html1).toBe( - `
hello
` + '
hello
', ) const todoNode2 = { @@ -25,6 +26,7 @@ describe('todo - elem to html', () => { children: [{ text: '' }], } const html2 = todoToHtmlConf.elemToHtml(todoNode2, 'hello') - expect(html2).toBe(`
hello
`) + + expect(html2).toBe('
hello
') }) }) diff --git a/packages/basic-modules/__tests__/todo/menu/todo-menu.test.ts b/packages/basic-modules/__tests__/todo/menu/todo-menu.test.ts index ddbd82d1b..b07d2d38a 100644 --- a/packages/basic-modules/__tests__/todo/menu/todo-menu.test.ts +++ b/packages/basic-modules/__tests__/todo/menu/todo-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor, Transforms } from 'slate' + import createEditor from '../../../../../tests/utils/create-editor' import TodoMenu from '../../../src/modules/todo/menu/Todo' @@ -96,6 +97,7 @@ describe('todo-menu', () => { menu.exec(editor, '') const todoElems = editor.getElemsByType('todo') + expect(todoElems.length).toBe(1) }) @@ -106,6 +108,7 @@ describe('todo-menu', () => { menu.exec(editor, '') const todoElems = editor.getElemsByType('todo') + expect(todoElems.length).toBe(0) }) }) diff --git a/packages/basic-modules/__tests__/todo/parse-html.test.ts b/packages/basic-modules/__tests__/todo/parse-html.test.ts index a00969f43..b5456d111 100644 --- a/packages/basic-modules/__tests__/todo/parse-html.test.ts +++ b/packages/basic-modules/__tests__/todo/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../../tests/utils/create-editor' import { parseHtmlConf } from '../../src/modules/todo/parse-elem-html' @@ -18,6 +19,7 @@ describe('todo - parse html', () => { // parse const res = parseHtmlConf.parseElemHtml($todo[0], [], editor) + expect(res).toEqual({ type: 'todo', checked: true, @@ -36,6 +38,7 @@ describe('todo - parse html', () => { // parse let res = parseHtmlConf.parseElemHtml($todo[0], children, editor) + expect(res).toEqual({ type: 'todo', checked: false, diff --git a/packages/basic-modules/__tests__/todo/plugin.test.ts b/packages/basic-modules/__tests__/todo/plugin.test.ts index 2a6e59edc..042ef3260 100644 --- a/packages/basic-modules/__tests__/todo/plugin.test.ts +++ b/packages/basic-modules/__tests__/todo/plugin.test.ts @@ -3,15 +3,15 @@ * @author wangfupeng */ -import withTodo from '../../src/modules/todo/plugin' import createEditor from '../../../../tests/utils/create-editor' +import withTodo from '../../src/modules/todo/plugin' describe('todo - plugin', () => { it('delete backward', () => { const editor = withTodo( createEditor({ content: [{ type: 'todo', children: [{ text: '' }] }], - }) + }), ) // test without selection @@ -22,11 +22,13 @@ describe('todo - plugin', () => { }) const todoElems1 = editor.getElemsByType('todo') + expect(todoElems1.length).toBe(1) editor.deleteBackward('character') const todoElems2 = editor.getElemsByType('todo') + expect(todoElems2.length).toBe(0) }) }) diff --git a/packages/basic-modules/__tests__/todo/pre-parse-html.test.ts b/packages/basic-modules/__tests__/todo/pre-parse-html.test.ts index a83d2701f..e5cb118bd 100644 --- a/packages/basic-modules/__tests__/todo/pre-parse-html.test.ts +++ b/packages/basic-modules/__tests__/todo/pre-parse-html.test.ts @@ -4,13 +4,14 @@ */ import { $ } from 'dom7' + import { preParseHtmlConf } from '../../src/modules/todo/pre-parse-html' describe('todo - pre-parse html', () => { it('pre-parse html', () => { // v4 todo html 格式 const $ul = $( - '' + '', ) // match selector @@ -18,8 +19,9 @@ describe('todo - pre-parse html', () => { // parse const res = preParseHtmlConf.preParseHtml($ul[0]) + expect(res.outerHTML).toBe( - '
hello world
' + '
hello world
', ) }) }) diff --git a/packages/basic-modules/__tests__/todo/render-elem.test.ts b/packages/basic-modules/__tests__/todo/render-elem.test.ts index b63a69375..25d2bbbf5 100644 --- a/packages/basic-modules/__tests__/todo/render-elem.test.ts +++ b/packages/basic-modules/__tests__/todo/render-elem.test.ts @@ -14,14 +14,17 @@ describe('todo - render elem', () => { const todo = { type: 'todo', checked: true, children: [{ text: '' }] } const vnode = renderTodoConf.renderElem(todo, null, editor) as any + expect(vnode.sel).toBe('div') expect(vnode.children.length).toBe(2) const spanForInput = vnode.children[0] + expect(spanForInput.sel).toBe('span') expect(spanForInput.data.contentEditable).toBe(false) const input = spanForInput.children[0] + expect(input.sel).toBe('input') expect(input.data.type).toBe('checkbox') expect(input.data.checked).toBe(true) diff --git a/packages/basic-modules/__tests__/undo-redo/redo-menu.test.ts b/packages/basic-modules/__tests__/undo-redo/redo-menu.test.ts index 4aac14179..288fe3713 100644 --- a/packages/basic-modules/__tests__/undo-redo/redo-menu.test.ts +++ b/packages/basic-modules/__tests__/undo-redo/redo-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import RedoMenu from '../../src/modules/undo-redo/menu/RedoMenu' @@ -45,6 +46,7 @@ describe('redo menu', () => { menu.exec(editor, '') const newText = editor.getText() - expect(newText).toBe(text + 'xxx') + + expect(newText).toBe(`${text}xxx`) }) }) diff --git a/packages/basic-modules/__tests__/undo-redo/undo-menu.test.ts b/packages/basic-modules/__tests__/undo-redo/undo-menu.test.ts index 6bc0ed985..55100ea92 100644 --- a/packages/basic-modules/__tests__/undo-redo/undo-menu.test.ts +++ b/packages/basic-modules/__tests__/undo-redo/undo-menu.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import UndoMenu from '../../src/modules/undo-redo/menu/UndoMenu' @@ -42,6 +43,7 @@ describe('undo menu', () => { menu.exec(editor, '') const newText = editor.getText() + expect(newText).toBe(text) }) }) diff --git a/packages/basic-modules/__tests__/utils/dom.test.ts b/packages/basic-modules/__tests__/utils/dom.test.ts index 3e11b0377..650a5ba32 100644 --- a/packages/basic-modules/__tests__/utils/dom.test.ts +++ b/packages/basic-modules/__tests__/utils/dom.test.ts @@ -3,15 +3,17 @@ * @author cycleccc */ -import { getTagName } from '../../src/utils/dom' import { Dom7Array } from 'dom7' +import { getTagName } from '../../src/utils/dom' + describe('redo menu', () => { it('get tag name', () => { // 模拟一个空的 Dom7Array 对象 const emptyElem: Dom7Array = [] as unknown as Dom7Array const result = getTagName(emptyElem) + expect(result).toBe('') // 验证返回的是空字符串 }) }) diff --git a/packages/basic-modules/package.json b/packages/basic-modules/package.json index e304b222c..c4cfbe748 100644 --- a/packages/basic-modules/package.json +++ b/packages/basic-modules/package.json @@ -43,7 +43,7 @@ "@wangeditor-next/core": "1.7.4", "dom7": "^3.0.0", "lodash.throttle": "^4.1.1", - "nanoid": "^5.0.0", + "nanoid": "^3.3.7", "slate": "^0.72.0", "snabbdom": "^3.1.0" }, diff --git a/packages/basic-modules/rollup.config.js b/packages/basic-modules/rollup.config.js index 03d6485c2..6acd3ba2c 100644 --- a/packages/basic-modules/rollup.config.js +++ b/packages/basic-modules/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorBasicModules' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/basic-modules/src/constants/icon-svg.ts b/packages/basic-modules/src/constants/icon-svg.ts index 7fef44389..3cf0cb277 100644 --- a/packages/basic-modules/src/constants/icon-svg.ts +++ b/packages/basic-modules/src/constants/icon-svg.ts @@ -10,151 +10,114 @@ */ // 加粗 -export const BOLD_SVG = - '' +export const BOLD_SVG = '' // 下划线 -export const UNDER_LINE_SVG = - '' +export const UNDER_LINE_SVG = '' // 斜体 -export const ITALIC_SVG = - '' +export const ITALIC_SVG = '' // 删除线 -export const THROUGH_SVG = - '' +export const THROUGH_SVG = '' // 代码 -export const CODE_SVG = - '' +export const CODE_SVG = '' // 清除格式 -export const ERASER_SVG = - '' +export const ERASER_SVG = '' // 链接 -export const LINK_SVG = - '' +export const LINK_SVG = '' // 取消链接 -export const UN_LINK_SVG = - '' +export const UN_LINK_SVG = '' // 编辑 -export const PENCIL_SVG = - '' +export const PENCIL_SVG = '' // 外部(链接) -export const EXTERNAL_SVG = - '' +export const EXTERNAL_SVG = '' // 标题 -export const HEADER_SVG = - '' +export const HEADER_SVG = '' // 字体颜色 -export const FONT_COLOR_SVG = - '' +export const FONT_COLOR_SVG = '' // 背景颜色 -export const BG_COLOR_SVG = - '' +export const BG_COLOR_SVG = '' // 清空(颜色) -export const CLEAN_SVG = - '' +export const CLEAN_SVG = '' // 图片 -export const IMAGE_SVG = - '' +export const IMAGE_SVG = '' // 垃圾桶(删除) -export const TRASH_SVG = - '' +export const TRASH_SVG = '' // 引用 -export const QUOTE_SVG = - '' +export const QUOTE_SVG = '' // 表情 -export const EMOTION_SVG = - '' +export const EMOTION_SVG = '' // fontSize -export const FONT_SIZE_SVG = - '' +export const FONT_SIZE_SVG = '' // 字体 -export const FONT_FAMILY_SVG = - '' +export const FONT_FAMILY_SVG = '' // 缩进 left -export const INDENT_LEFT_SVG = - '' +export const INDENT_LEFT_SVG = '' // 缩进 right -export const INDENT_RIGHT_SVG = - '' +export const INDENT_RIGHT_SVG = '' // 左对齐 -export const JUSTIFY_LEFT_SVG = - '' +export const JUSTIFY_LEFT_SVG = '' // 右对齐 -export const JUSTIFY_RIGHT_SVG = - '' +export const JUSTIFY_RIGHT_SVG = '' // 居中对齐 -export const JUSTIFY_CENTER_SVG = - '' +export const JUSTIFY_CENTER_SVG = '' // 两端对齐 -export const JUSTIFY_JUSTIFY_SVG = - '' +export const JUSTIFY_JUSTIFY_SVG = '' // 行高 -export const LINE_HEIGHT_SVG = - '' +export const LINE_HEIGHT_SVG = '' // 撤销 -export const UNDO_SVG = - '' +export const UNDO_SVG = '' // 重做 -export const REDO_SVG = - '' +export const REDO_SVG = '' // 分割线 -export const DIVIDER_SVG = - '' +export const DIVIDER_SVG = '' // 代码块 -export const CODE_BLOCK_SVG = - '' +export const CODE_BLOCK_SVG = '' // 全屏 -export const FULL_SCREEN_SVG = - '' +export const FULL_SCREEN_SVG = '' // 取消全屏 -export const CANCEL_FULL_SCREEN_SVG = `` +export const CANCEL_FULL_SCREEN_SVG = '' // 上标 -export const SUP_SVG = - '' +export const SUP_SVG = '' // 下标 -export const SUB_SVG = - '' +export const SUB_SVG = '' // checkbox -export const CHECK_BOX_SVG = - '' +export const CHECK_BOX_SVG = '' // 回车 -export const ENTER_SVG = - '' +export const ENTER_SVG = '' // 格式刷 -export const FORMAT_PAINTER = - '' +export const FORMAT_PAINTER = '' diff --git a/packages/basic-modules/src/index.ts b/packages/basic-modules/src/index.ts index 4988f543b..4ab7cf272 100644 --- a/packages/basic-modules/src/index.ts +++ b/packages/basic-modules/src/index.ts @@ -4,29 +4,28 @@ */ import './assets/index.less' - // 配置多语言 import './locale/index' -import wangEditorParagraphModule from './modules/paragraph' -import wangEditorTextStyleModule from './modules/text-style' -import wangEditorHeaderModule from './modules/header' -import wangEditorColorModule from './modules/color' -import wangEditorLinkModule from './modules/link' -import wangEditorImageModule from './modules/image' -import wangEditorTodoModule from './modules/todo' import wangEditorBlockQuoteModule from './modules/blockquote' +import wangEditorCodeBlockModule from './modules/code-block' +import wangEditorColorModule from './modules/color' +import wangEditorCommonModule from './modules/common' +import wangEditorDividerModule from './modules/divider' import wangEditorEmotionModule from './modules/emotion' import wangEditorFontSizeAndFamilyModule from './modules/font-size-family' +import wangEditorFormatPainterModule from './modules/format-painter' +import wangEditorFullScreenModule from './modules/full-screen' +import wangEditorHeaderModule from './modules/header' +import wangEditorImageModule from './modules/image' import wangEditorIndentModule from './modules/indent' import wangEditorJustifyModule from './modules/justify' import wangEditorLineHeightModule from './modules/line-height' +import wangEditorLinkModule from './modules/link' +import wangEditorParagraphModule from './modules/paragraph' +import wangEditorTextStyleModule from './modules/text-style' +import wangEditorTodoModule from './modules/todo' import wangEditorUndoRedoModule from './modules/undo-redo' -import wangEditorDividerModule from './modules/divider' -import wangEditorCodeBlockModule from './modules/code-block' -import wangEditorFullScreenModule from './modules/full-screen' -import wangEditorCommonModule from './modules/common' -import wangEditorFormatPainterModule from './modules/format-painter' export default [ // text style diff --git a/packages/basic-modules/src/locale/index.ts b/packages/basic-modules/src/locale/index.ts index f3b7049e9..120f32884 100644 --- a/packages/basic-modules/src/locale/index.ts +++ b/packages/basic-modules/src/locale/index.ts @@ -4,6 +4,7 @@ */ import { i18nAddResources } from '@wangeditor-next/core' + import enResources from './en' import zhResources from './zh-CN' diff --git a/packages/basic-modules/src/modules/blockquote/custom-types.ts b/packages/basic-modules/src/modules/blockquote/custom-types.ts index f162c645e..26e17e6b6 100644 --- a/packages/basic-modules/src/modules/blockquote/custom-types.ts +++ b/packages/basic-modules/src/modules/blockquote/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type BlockQuoteElement = { type: 'blockquote' diff --git a/packages/basic-modules/src/modules/blockquote/index.ts b/packages/basic-modules/src/modules/blockquote/index.ts index 69abb2fe8..8f7aad51c 100644 --- a/packages/basic-modules/src/modules/blockquote/index.ts +++ b/packages/basic-modules/src/modules/blockquote/index.ts @@ -4,11 +4,12 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import { renderBlockQuoteConf } from './render-elem' + import { quoteToHtmlConf } from './elem-to-html' -import { parseHtmlConf } from './parse-elem-html' import { blockquoteMenuConf } from './menu/index' +import { parseHtmlConf } from './parse-elem-html' import withBlockquote from './plugin' +import { renderBlockQuoteConf } from './render-elem' const blockquote: Partial = { renderElems: [renderBlockQuoteConf], diff --git a/packages/basic-modules/src/modules/blockquote/menu/BlockquoteMenu.ts b/packages/basic-modules/src/modules/blockquote/menu/BlockquoteMenu.ts index 21897424e..99c24410a 100644 --- a/packages/basic-modules/src/modules/blockquote/menu/BlockquoteMenu.ts +++ b/packages/basic-modules/src/modules/blockquote/menu/BlockquoteMenu.ts @@ -3,13 +3,18 @@ * @author wangfupeng */ +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' import { Editor, Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' + import { QUOTE_SVG } from '../../../constants/icon-svg' class BlockquoteMenu implements IButtonMenu { readonly title = t('blockQuote.title') + readonly iconSvg = QUOTE_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -19,19 +24,20 @@ class BlockquoteMenu implements IButtonMenu { isActive(editor: IDomEditor): boolean { const node = DomEditor.getSelectedNodeByType(editor, 'blockquote') + return !!node } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const [nodeEntry] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) // 只可用于 p 和 blockquote - if (type === 'paragraph') return true - if (type === 'blockquote') return true + if (type === 'paragraph') { return true } + if (type === 'blockquote') { return true } return false }, @@ -53,7 +59,7 @@ class BlockquoteMenu implements IButtonMenu { * @param value node.type */ exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } const active = this.isActive(editor) const newType = active ? 'paragraph' : 'blockquote' diff --git a/packages/basic-modules/src/modules/blockquote/parse-elem-html.ts b/packages/basic-modules/src/modules/blockquote/parse-elem-html.ts index 767e5bfff..4bc2c2041 100644 --- a/packages/basic-modules/src/modules/blockquote/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/blockquote/parse-elem-html.ts @@ -3,21 +3,22 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Descendant, Text } from 'slate' + import $, { DOMElement } from '../../utils/dom' -import { IDomEditor } from '@wangeditor-next/core' import { BlockQuoteElement } from './custom-types' function parseHtml( elem: DOMElement, children: Descendant[], - editor: IDomEditor + editor: IDomEditor, ): BlockQuoteElement { const $elem = $(elem) children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true + if (Text.isText(child)) { return true } + if (editor.isInline(child)) { return true } return false }) diff --git a/packages/basic-modules/src/modules/blockquote/plugin.ts b/packages/basic-modules/src/modules/blockquote/plugin.ts index cdc31c746..192c07cf2 100644 --- a/packages/basic-modules/src/modules/blockquote/plugin.ts +++ b/packages/basic-modules/src/modules/blockquote/plugin.ts @@ -3,8 +3,10 @@ * @author wangfupeng */ -import { Editor, Transforms, Node, Point } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' +import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { + Editor, Node, Point, Transforms, +} from 'slate' function withBlockquote(editor: T): T { const { insertBreak, insertText } = editor @@ -13,17 +15,20 @@ function withBlockquote(editor: T): T { // 重写 insertBreak - 换行时插入 p newEditor.insertBreak = () => { const { selection } = newEditor - if (selection == null) return insertBreak() + + if (selection == null) { return insertBreak() } const [nodeEntry] = Editor.nodes(editor, { match: n => DomEditor.checkNodeType(n, 'blockquote'), universal: true, }) - if (!nodeEntry) return insertBreak() + + if (!nodeEntry) { return insertBreak() } const quoteElem = nodeEntry[0] // 如果正在粘贴中,没有 path 可用,则直接换行退出 blockquote // TODO: 粘贴未处理其它富文本一个 block 中套两个 div 用作换行的情况 + if (!DomEditor.getParentNode(editor, quoteElem)) { insertParagraphBeforeNewline(newEditor) return @@ -34,6 +39,7 @@ function withBlockquote(editor: T): T { if (Point.equals(quoteEndLocation, selection.focus)) { // 光标位于 blockquote 最后 const str = Node.string(quoteElem) + if (str && str.slice(-1) === '\n') { insertParagraphBeforeNewline(newEditor) return @@ -53,6 +59,7 @@ function insertParagraphBeforeNewline(editor: any) { // 插入一个 paragraph const p = { type: 'paragraph', children: [{ text: '' }] } + Transforms.insertNodes(editor, p, { mode: 'highest' }) } diff --git a/packages/basic-modules/src/modules/blockquote/render-elem.tsx b/packages/basic-modules/src/modules/blockquote/render-elem.tsx index 3671714e7..5abed1aad 100644 --- a/packages/basic-modules/src/modules/blockquote/render-elem.tsx +++ b/packages/basic-modules/src/modules/blockquote/render-elem.tsx @@ -3,9 +3,9 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor-next/core' /** * render block quote elem @@ -17,9 +17,10 @@ import { IDomEditor } from '@wangeditor-next/core' function renderBlockQuote( elemNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { const vnode =
{children}
+ return vnode } diff --git a/packages/basic-modules/src/modules/code-block/custom-types.ts b/packages/basic-modules/src/modules/code-block/custom-types.ts index 735fc7235..1c1367da4 100644 --- a/packages/basic-modules/src/modules/code-block/custom-types.ts +++ b/packages/basic-modules/src/modules/code-block/custom-types.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts type PureText = { text: string diff --git a/packages/basic-modules/src/modules/code-block/index.ts b/packages/basic-modules/src/modules/code-block/index.ts index 27bad4d48..2ea86be8d 100644 --- a/packages/basic-modules/src/modules/code-block/index.ts +++ b/packages/basic-modules/src/modules/code-block/index.ts @@ -4,12 +4,13 @@ */ import { IModuleConf } from '@wangeditor-next/core' + +import { codeToHtmlConf, preToHtmlConf } from './elem-to-html' import { codeBlockMenuConf } from './menu/index' +import { parseCodeHtmlConf, parsePreHtmlConf } from './parse-elem-html' import withCodeBlock from './plugin' -import { renderPreConf, renderCodeConf } from './render-elem' import { preParseHtmlConf } from './pre-parse-html' -import { parseCodeHtmlConf, parsePreHtmlConf } from './parse-elem-html' -import { codeToHtmlConf, preToHtmlConf } from './elem-to-html' +import { renderCodeConf, renderPreConf } from './render-elem' const codeBlockModule: Partial = { menus: [codeBlockMenuConf], diff --git a/packages/basic-modules/src/modules/code-block/menu/CodeBlockMenu.ts b/packages/basic-modules/src/modules/code-block/menu/CodeBlockMenu.ts index dc456ecec..12326e9a1 100644 --- a/packages/basic-modules/src/modules/code-block/menu/CodeBlockMenu.ts +++ b/packages/basic-modules/src/modules/code-block/menu/CodeBlockMenu.ts @@ -3,22 +3,31 @@ * @author wangfupeng */ -import { Editor, Element, Transforms, Node, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { + Editor, Element, Node, Range, Transforms, +} from 'slate' + import { CODE_BLOCK_SVG } from '../../../constants/icon-svg' import { CodeElement } from '../custom-types' class CodeBlockMenu implements IButtonMenu { readonly title = t('codeBlock.title') + readonly iconSvg = CODE_BLOCK_SVG + readonly tag = 'button' private getSelectCodeElem(editor: IDomEditor): CodeElement | null { const codeNode = DomEditor.getSelectedNodeByType(editor, 'code') - if (codeNode == null) return null + + if (codeNode == null) { return null } const preNode = DomEditor.getParentNode(editor, codeNode) - if (preNode == null) return null - if (DomEditor.getNodeType(preNode) !== 'pre') return null + + if (preNode == null) { return null } + if (DomEditor.getNodeType(preNode) !== 'pre') { return null } return codeNode as CodeElement } @@ -29,34 +38,41 @@ class CodeBlockMenu implements IButtonMenu { */ getValue(editor: IDomEditor): string | boolean { const elem = this.getSelectCodeElem(editor) - if (elem == null) return '' + + if (elem == null) { return '' } return elem.language || '' } isActive(editor: IDomEditor): boolean { const elem = this.getSelectCodeElem(editor) + return !!elem } isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true + + if (selection == null) { return true } const selectedElems = DomEditor.getSelectedElems(editor) const hasVoid = selectedElems.some(elem => editor.isVoid(elem)) - if (hasVoid) return true + + if (hasVoid) { return true } const isMatch = selectedElems.some(elem => { const type = DomEditor.getNodeType(elem) - if (type === 'pre' || type === 'paragraph') return true // 匹配 pre 或 paragraph + + if (type === 'pre' || type === 'paragraph') { return true } // 匹配 pre 或 paragraph }) - if (isMatch) return false // 匹配到,则 enable + + if (isMatch) { return false } // 匹配到,则 enable return true // 否则 disable } exec(editor: IDomEditor, value: string | boolean) { const active = this.isActive(editor) + if (active) { // 当前处于 code-block ,需要转换为普通文本 this.changeToPlainText(editor) @@ -68,7 +84,8 @@ class CodeBlockMenu implements IButtonMenu { private changeToPlainText(editor: IDomEditor) { const elem = this.getSelectCodeElem(editor) - if (elem == null) return + + if (elem == null) { return } // 获取 code 文本 const str = Node.string(elem) @@ -80,6 +97,7 @@ class CodeBlockMenu implements IButtonMenu { const pList = str.split('\n').map(s => { return { type: 'paragraph', children: [{ text: s }] } }) + Transforms.insertNodes(editor, pList, { mode: 'highest' }) } @@ -90,9 +108,11 @@ class CodeBlockMenu implements IButtonMenu { match: n => editor.children.includes(n as Element), // 匹配选中的最高层级的节点 universal: true, }) - for (let nodeEntry of nodeEntries) { + + for (const nodeEntry of nodeEntries) { const [n] = nodeEntry - if (n) strArr.push(Node.string(n)) + + if (n) { strArr.push(Node.string(n)) } } // 删除选中的最高层级的节点 @@ -111,6 +131,7 @@ class CodeBlockMenu implements IButtonMenu { }, ], } + Transforms.insertNodes(editor, newPreNode, { mode: 'highest' }) } } diff --git a/packages/basic-modules/src/modules/code-block/parse-elem-html.ts b/packages/basic-modules/src/modules/code-block/parse-elem-html.ts index 94b425789..0ff26739c 100644 --- a/packages/basic-modules/src/modules/code-block/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/code-block/parse-elem-html.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Descendant } from 'slate' + import $, { DOMElement } from '../../utils/dom' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' -import { PreElement, CodeElement } from './custom-types' +import { CodeElement, PreElement } from './custom-types' function parseCodeHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): CodeElement { const $elem = $(elem) diff --git a/packages/basic-modules/src/modules/code-block/plugin.ts b/packages/basic-modules/src/modules/code-block/plugin.ts index a1712edff..b272f3334 100644 --- a/packages/basic-modules/src/modules/code-block/plugin.ts +++ b/packages/basic-modules/src/modules/code-block/plugin.ts @@ -3,30 +3,37 @@ * @author wangfupeng */ -import { Editor, Transforms, Node as SlateNode, Element as SlateElement } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' +import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { + Editor, Element as SlateElement, Node as SlateNode, Transforms, +} from 'slate' function getLastTextLineBeforeSelection(codeNode: SlateNode, editor: IDomEditor): string { const selection = editor.selection - if (selection == null) return '' + + if (selection == null) { return '' } const codeText = SlateNode.string(codeNode) const anchorOffset = selection.anchor.offset const textBeforeAnchor = codeText.slice(0, anchorOffset) // 选区前的 text const arr = textBeforeAnchor.split('\n') // 选区前的 text ,按换行拆分 const length = arr.length - if (length === 0) return '' + + if (length === 0) { return '' } return arr[length - 1] } function withCodeBlock(editor: T): T { - const { insertBreak, normalizeNode, insertData, insertNode } = editor + const { + insertBreak, normalizeNode, insertData, insertNode, + } = editor const newEditor = editor // 重写换行操作 newEditor.insertBreak = () => { const codeNode = DomEditor.getSelectedNodeByType(newEditor, 'code') + if (codeNode == null) { insertBreak() // 执行默认的换行 return @@ -34,10 +41,13 @@ function withCodeBlock(editor: T): T { // 回车时,根据当前行的空格,自动插入空格 const lastLineBeforeSelection = getLastTextLineBeforeSelection(codeNode, newEditor) + if (lastLineBeforeSelection) { const arr = lastLineBeforeSelection.match(/^\s+/) // 行开始的空格 + if (arr != null && arr[0] != null) { const spaces = arr[0] + newEditor.insertText(`\n${spaces}`) // 换行后插入空格 return } @@ -59,6 +69,7 @@ function withCodeBlock(editor: T): T { if (type === 'pre') { // -------------- pre 是 editor 最后一个节点,需要后面插入 p -------------- const isLast = DomEditor.isLastNode(newEditor, node) + if (isLast) { Transforms.insertNodes(newEditor, DomEditor.genEmptyParagraph(), { at: [path[0] + 1] }) } @@ -77,6 +88,7 @@ function withCodeBlock(editor: T): T { // 重写 insertData - 粘贴文本 newEditor.insertData = (data: DataTransfer) => { const codeNode = DomEditor.getSelectedNodeByType(newEditor, 'code') + if (codeNode == null) { insertData(data) // 执行默认的 insertData return @@ -84,6 +96,7 @@ function withCodeBlock(editor: T): T { // 获取文本,并插入到代码块 const text = data.getData('text/plain') + Editor.insertText(newEditor, text) } diff --git a/packages/basic-modules/src/modules/code-block/pre-parse-html.ts b/packages/basic-modules/src/modules/code-block/pre-parse-html.ts index a6c09fafd..fb8e78160 100644 --- a/packages/basic-modules/src/modules/code-block/pre-parse-html.ts +++ b/packages/basic-modules/src/modules/code-block/pre-parse-html.ts @@ -3,8 +3,7 @@ * @author wangfupeng */ -import $, { DOMElement } from '../../utils/dom' -import { getTagName } from '../../utils/dom' +import $, { DOMElement, getTagName } from '../../utils/dom' /** * pre-prase ,去掉其中的 (兼容 V4) @@ -13,12 +12,15 @@ import { getTagName } from '../../utils/dom' function preParse(codeElem: DOMElement): DOMElement { const $code = $(codeElem) const tagName = getTagName($code) - if (tagName !== 'code') return codeElem + + if (tagName !== 'code') { return codeElem } const $xmp = $code.find('xmp') - if ($xmp.length === 0) return codeElem // 不是 V4 格式 + + if ($xmp.length === 0) { return codeElem } // 不是 V4 格式 const codeText = $xmp.text() + $xmp.remove() $code.text(codeText) diff --git a/packages/basic-modules/src/modules/code-block/render-elem.tsx b/packages/basic-modules/src/modules/code-block/render-elem.tsx index ee67fe8ee..e297a2780 100644 --- a/packages/basic-modules/src/modules/code-block/render-elem.tsx +++ b/packages/basic-modules/src/modules/code-block/render-elem.tsx @@ -3,18 +3,20 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor-next/core' function renderPre(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode { const vnode = <pre>{children}</pre> + return vnode } function renderCode(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode { // 和 basic/simple-style module 的“行内代码”并不冲突。一个是根据 mark 渲染,一个是根据 node.type 渲染 const vnode = <code>{children}</code> + return vnode } diff --git a/packages/basic-modules/src/modules/color/custom-types.ts b/packages/basic-modules/src/modules/color/custom-types.ts index 0a62eedb1..dd80b9240 100644 --- a/packages/basic-modules/src/modules/color/custom-types.ts +++ b/packages/basic-modules/src/modules/color/custom-types.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -//【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts export type ColorText = { text: string diff --git a/packages/basic-modules/src/modules/color/index.ts b/packages/basic-modules/src/modules/color/index.ts index 6e070ce6a..cb09a7511 100644 --- a/packages/basic-modules/src/modules/color/index.ts +++ b/packages/basic-modules/src/modules/color/index.ts @@ -4,11 +4,12 @@ */ import { IModuleConf } from '@wangeditor-next/core' + +import { bgColorMenuConf, colorMenuConf } from './menu/index' +import { parseStyleHtml } from './parse-style-html' +import { preParseHtmlConf } from './pre-parse-html' import { renderStyle } from './render-style' import { styleToHtml } from './style-to-html' -import { preParseHtmlConf } from './pre-parse-html' -import { parseStyleHtml } from './parse-style-html' -import { colorMenuConf, bgColorMenuConf } from './menu/index' const color: Partial<IModuleConf> = { renderStyle, diff --git a/packages/basic-modules/src/modules/color/menu/BaseMenu.ts b/packages/basic-modules/src/modules/color/menu/BaseMenu.ts index ab4fae808..1473b65ec 100644 --- a/packages/basic-modules/src/modules/color/menu/BaseMenu.ts +++ b/packages/basic-modules/src/modules/color/menu/BaseMenu.ts @@ -3,17 +3,25 @@ * @author wangfupeng */ +import { + DomEditor, IDomEditor, IDropPanelMenu, t, +} from '@wangeditor-next/core' import { Editor, Range } from 'slate' -import { IDropPanelMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' -import $, { Dom7Array, DOMElement } from '../../../utils/dom' + import { CLEAN_SVG } from '../../../constants/icon-svg' +import $, { Dom7Array, DOMElement } from '../../../utils/dom' abstract class BaseMenu implements IDropPanelMenu { abstract readonly title: string + abstract readonly iconSvg: string + readonly tag = 'button' + readonly showDropPanel = true // 点击 button 时显示 dropPanel + protected abstract readonly mark: string + private $content: Dom7Array | null = null exec(editor: IDomEditor, value: string | boolean) { @@ -25,24 +33,26 @@ abstract class BaseMenu implements IDropPanelMenu { const mark = this.mark const curMarks = Editor.marks(editor) // @ts-ignore - if (curMarks && curMarks[mark]) return curMarks[mark] + + if (curMarks && curMarks[mark]) { return curMarks[mark] } return '' } isActive(editor: IDomEditor): boolean { const color = this.getValue(editor) + return !!color } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const [match] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) - if (type === 'pre') return true // 代码块 - if (Editor.isVoid(editor, n)) return true // void node + if (type === 'pre') { return true } // 代码块 + if (Editor.isVoid(editor, n)) { return true } // void node return false }, @@ -50,7 +60,7 @@ abstract class BaseMenu implements IDropPanelMenu { }) // 命中,则禁用 - if (match) return true + if (match) { return true } return false } @@ -64,11 +74,13 @@ abstract class BaseMenu implements IDropPanelMenu { // 绑定事件(只在第一次绑定,不要重复绑定) $content.on('click', 'li', (e: Event) => { const { target } = e - if (target == null) return + + if (target == null) { return } e.preventDefault() const { selection } = editor - if (selection == null) return + + if (selection == null) { return } const $li = $(target) const val = $li.attr('data-value') @@ -84,7 +96,8 @@ abstract class BaseMenu implements IDropPanelMenu { this.$content = $content } const $content = this.$content - if ($content == null) return document.createElement('ul') + + if ($content == null) { return document.createElement('ul') } $content.empty() // 清空之后再重置内容 // 当前选中文本的颜色之 @@ -94,11 +107,14 @@ abstract class BaseMenu implements IDropPanelMenu { const colorConf = editor.getMenuConfig(mark) const { colors = [] } = colorConf // 根据菜单配置生成 panel content + colors.forEach((color: string) => { const $block = $(`<div class="color-block" data-value="${color}"></div>`) + $block.css('background-color', color) const $li = $(`<li data-value="${color}"></li>`) + if (selectedColor === color) { $li.addClass('active') } @@ -109,14 +125,16 @@ abstract class BaseMenu implements IDropPanelMenu { // 清除颜色 let clearText = '' - if (mark === 'color') clearText = t('color.default') - if (mark === 'bgColor') clearText = t('color.clear') + + if (mark === 'color') { clearText = t('color.default') } + if (mark === 'bgColor') { clearText = t('color.clear') } const $clearLi = $(` <li data-value="0" class="clear"> ${CLEAN_SVG} ${clearText} </li> `) + $content.prepend($clearLi) return $content[0] diff --git a/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts b/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts index 4d01d2f83..3d385bda3 100644 --- a/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts +++ b/packages/basic-modules/src/modules/color/menu/BgColorMenu.ts @@ -4,12 +4,15 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { BG_COLOR_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class BgColorMenu extends BaseMenu { readonly title = t('color.bgColor') + readonly iconSvg = BG_COLOR_SVG + readonly mark = 'bgColor' } diff --git a/packages/basic-modules/src/modules/color/menu/ColorMenu.ts b/packages/basic-modules/src/modules/color/menu/ColorMenu.ts index 15baf501a..5ae35a950 100644 --- a/packages/basic-modules/src/modules/color/menu/ColorMenu.ts +++ b/packages/basic-modules/src/modules/color/menu/ColorMenu.ts @@ -4,12 +4,15 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { FONT_COLOR_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class ColorMenu extends BaseMenu { readonly title = t('color.color') + readonly iconSvg = FONT_COLOR_SVG + readonly mark = 'color' } diff --git a/packages/basic-modules/src/modules/color/menu/index.ts b/packages/basic-modules/src/modules/color/menu/index.ts index 8dfa76d14..a6f4ce0d3 100644 --- a/packages/basic-modules/src/modules/color/menu/index.ts +++ b/packages/basic-modules/src/modules/color/menu/index.ts @@ -3,9 +3,9 @@ * @author wangfupeng */ -import ColorMenu from './ColorMenu' import BgColorMenu from './BgColorMenu' -import { genColors, genBgColors } from './config' +import ColorMenu from './ColorMenu' +import { genBgColors, genColors } from './config' export const colorMenuConf = { key: 'color', diff --git a/packages/basic-modules/src/modules/color/parse-style-html.ts b/packages/basic-modules/src/modules/color/parse-style-html.ts index 081abd23b..358f94d42 100644 --- a/packages/basic-modules/src/modules/color/parse-style-html.ts +++ b/packages/basic-modules/src/modules/color/parse-style-html.ts @@ -3,24 +3,28 @@ * @author wangfupeng */ -import { Descendant, Text } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { ColorText } from './custom-types' +import { Descendant, Text } from 'slate' + import $, { DOMElement, getStyleValue } from '../../utils/dom' +import { ColorText } from './custom-types' export function parseStyleHtml(text: DOMElement, node: Descendant, editor: IDomEditor): Descendant { const $text = $(text) - if (!Text.isText(node)) return node + + if (!Text.isText(node)) { return node } const textNode = node as ColorText const color = getStyleValue($text, 'color') + if (color) { textNode.color = color } let bgColor = getStyleValue($text, 'background-color') - if (!bgColor) bgColor = getStyleValue($text, 'background') // word 背景色 + + if (!bgColor) { bgColor = getStyleValue($text, 'background') } // word 背景色 if (bgColor) { textNode.bgColor = bgColor } diff --git a/packages/basic-modules/src/modules/color/pre-parse-html.ts b/packages/basic-modules/src/modules/color/pre-parse-html.ts index 9e88028af..51e7b802d 100644 --- a/packages/basic-modules/src/modules/color/pre-parse-html.ts +++ b/packages/basic-modules/src/modules/color/pre-parse-html.ts @@ -12,10 +12,12 @@ import $, { DOMElement, getTagName } from '../../utils/dom' function preParse(fontElem: DOMElement): DOMElement { const $font = $(fontElem) const tagName = getTagName($font) - if (tagName !== 'font') return fontElem + + if (tagName !== 'font') { return fontElem } // 处理 color (V4 使用 <font color="#ccc">xx</font> 格式) const color = $font.attr('color') || '' + if (color) { $font.removeAttr('color') $font.css('color', color) diff --git a/packages/basic-modules/src/modules/color/render-style.tsx b/packages/basic-modules/src/modules/color/render-style.tsx index 264c67a30..6319a2375 100644 --- a/packages/basic-modules/src/modules/color/render-style.tsx +++ b/packages/basic-modules/src/modules/color/render-style.tsx @@ -5,6 +5,7 @@ import { Descendant } from 'slate' import { jsx, VNode } from 'snabbdom' + import { addVnodeStyle } from '../../utils/vdom' import { ColorText } from './custom-types' @@ -16,7 +17,7 @@ import { ColorText } from './custom-types' */ export function renderStyle(node: Descendant, vnode: VNode): VNode { const { color, bgColor } = node as ColorText - let styleVnode: VNode = vnode + const styleVnode: VNode = vnode if (color) { addVnodeStyle(styleVnode, { color }) diff --git a/packages/basic-modules/src/modules/color/style-to-html.ts b/packages/basic-modules/src/modules/color/style-to-html.ts index 3ec1c0be4..764fc37f1 100644 --- a/packages/basic-modules/src/modules/color/style-to-html.ts +++ b/packages/basic-modules/src/modules/color/style-to-html.ts @@ -3,7 +3,8 @@ * @author wangfupeng */ -import { Text, Descendant } from 'slate' +import { Descendant, Text } from 'slate' + import $, { getOuterHTML, getTagName, isPlainText } from '../../utils/dom' import { ColorText } from './custom-types' @@ -14,10 +15,11 @@ import { ColorText } from './custom-types' * @returns styled html */ export function styleToHtml(textNode: Descendant, textHtml: string): string { - if (!Text.isText(textNode)) return textHtml + if (!Text.isText(textNode)) { return textHtml } const { color, bgColor } = textNode as ColorText - if (!color && !bgColor) return textHtml + + if (!color && !bgColor) { return textHtml } let $text @@ -28,6 +30,7 @@ export function styleToHtml(textNode: Descendant, textHtml: string): string { // textHtml 是 html tag $text = $(textHtml) const tagName = getTagName($text) + if (tagName !== 'span') { // 如果不是 span ,则包裹一层,接下来要设置 css $text = $(`<span>${textHtml}</span>`) @@ -35,8 +38,8 @@ export function styleToHtml(textNode: Descendant, textHtml: string): string { } // 设置样式 - if (color) $text.css('color', color) - if (bgColor) $text.css('background-color', bgColor) + if (color) { $text.css('color', color) } + if (bgColor) { $text.css('background-color', bgColor) } // 输出 html return getOuterHTML($text) diff --git a/packages/basic-modules/src/modules/common/index.ts b/packages/basic-modules/src/modules/common/index.ts index 4b6fa136c..47dd745f6 100644 --- a/packages/basic-modules/src/modules/common/index.ts +++ b/packages/basic-modules/src/modules/common/index.ts @@ -3,6 +3,7 @@ * @author wangfupeng */ import { IModuleConf } from '@wangeditor-next/core' + import { enterMenuConf } from './menu/index' const commonModule: Partial<IModuleConf> = { diff --git a/packages/basic-modules/src/modules/common/menu/EnterMenu.ts b/packages/basic-modules/src/modules/common/menu/EnterMenu.ts index cef311f9f..32148678c 100644 --- a/packages/basic-modules/src/modules/common/menu/EnterMenu.ts +++ b/packages/basic-modules/src/modules/common/menu/EnterMenu.ts @@ -3,13 +3,16 @@ * @author wangfupeng */ -import { Range, Transforms, Editor } from 'slate' import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' +import { Editor, Range, Transforms } from 'slate' + import { ENTER_SVG } from '../../../constants/icon-svg' class EnterMenu implements IButtonMenu { title = t('common.enter') + iconSvg = ENTER_SVG + tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -22,20 +25,23 @@ class EnterMenu implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (Range.isExpanded(selection)) return true + + if (selection == null) { return true } + if (Range.isExpanded(selection)) { return true } return false } exec(editor: IDomEditor, value: string | boolean) { const { selection } = editor - if (selection == null) return + + if (selection == null) { return } const { anchor } = selection const { path } = anchor // 在当前位置插入空行,当前元素下移 const newElem = { type: 'paragraph', children: [{ text: '' }] } const newPath = [path[0]] + Transforms.insertNodes(editor, newElem, { at: newPath }) editor.select(Editor.start(editor, newPath)) } diff --git a/packages/basic-modules/src/modules/divider/custom-types.ts b/packages/basic-modules/src/modules/divider/custom-types.ts index 10eca9291..06318a597 100644 --- a/packages/basic-modules/src/modules/divider/custom-types.ts +++ b/packages/basic-modules/src/modules/divider/custom-types.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts type EmptyText = { text: '' diff --git a/packages/basic-modules/src/modules/divider/elem-to-html.ts b/packages/basic-modules/src/modules/divider/elem-to-html.ts index 387f5cb92..f630a2551 100644 --- a/packages/basic-modules/src/modules/divider/elem-to-html.ts +++ b/packages/basic-modules/src/modules/divider/elem-to-html.ts @@ -6,7 +6,7 @@ import { Element } from 'slate' function dividerToHtml(elem: Element, childrenHtml: string): string { - return `<hr/>` + return '<hr/>' } export const dividerToHtmlConf = { diff --git a/packages/basic-modules/src/modules/divider/index.ts b/packages/basic-modules/src/modules/divider/index.ts index b3e961e66..ebb52058d 100644 --- a/packages/basic-modules/src/modules/divider/index.ts +++ b/packages/basic-modules/src/modules/divider/index.ts @@ -4,11 +4,12 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import withDivider from './plugin' -import { renderDividerConf } from './render-elem' + import { dividerToHtmlConf } from './elem-to-html' -import { parseHtmlConf } from './parse-elem-html' import { insertDividerMenuConf } from './menu/index' +import { parseHtmlConf } from './parse-elem-html' +import withDivider from './plugin' +import { renderDividerConf } from './render-elem' const image: Partial<IModuleConf> = { renderElems: [renderDividerConf], diff --git a/packages/basic-modules/src/modules/divider/menu/InsertDividerMenu.ts b/packages/basic-modules/src/modules/divider/menu/InsertDividerMenu.ts index bb728dbe2..efbb2be49 100644 --- a/packages/basic-modules/src/modules/divider/menu/InsertDividerMenu.ts +++ b/packages/basic-modules/src/modules/divider/menu/InsertDividerMenu.ts @@ -3,14 +3,19 @@ * @author wangfupeng */ +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' import { Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' + import { DIVIDER_SVG } from '../../../constants/icon-svg' import { DividerElement } from '../custom-types' class InsertDividerMenu implements IButtonMenu { readonly title = t('divider.title') + readonly iconSvg = DIVIDER_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -24,16 +29,19 @@ class InsertDividerMenu implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true + + if (selection == null) { return true } const selectedElems = DomEditor.getSelectedElems(editor) const hasVoidOrTableOrPre = selectedElems.some(elem => { - if (editor.isVoid(elem)) return true + if (editor.isVoid(elem)) { return true } const type = DomEditor.getNodeType(elem) - if (type === 'table') return true - if (type === 'pre') return true + + if (type === 'table') { return true } + if (type === 'pre') { return true } }) - if (hasVoidOrTableOrPre) return true // 匹配,则 disable + + if (hasVoidOrTableOrPre) { return true } // 匹配,则 disable return false } diff --git a/packages/basic-modules/src/modules/divider/parse-elem-html.ts b/packages/basic-modules/src/modules/divider/parse-elem-html.ts index 91c05ed22..7e6de7e39 100644 --- a/packages/basic-modules/src/modules/divider/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/divider/parse-elem-html.ts @@ -3,9 +3,10 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Descendant } from 'slate' + import $, { DOMElement } from '../../utils/dom' -import { IDomEditor } from '@wangeditor-next/core' import { DividerElement } from './custom-types' function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): DividerElement { diff --git a/packages/basic-modules/src/modules/divider/plugin.ts b/packages/basic-modules/src/modules/divider/plugin.ts index a44765f19..8b65a4c95 100644 --- a/packages/basic-modules/src/modules/divider/plugin.ts +++ b/packages/basic-modules/src/modules/divider/plugin.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import { Transforms, Element } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' +import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { Element, Transforms } from 'slate' function withDivider<T extends IDomEditor>(editor: T): T { const { isVoid, normalizeNode } = editor @@ -24,6 +24,7 @@ function withDivider<T extends IDomEditor>(editor: T): T { // 重新 normalize newEditor.normalizeNode = ([node, path]) => { const type = DomEditor.getNodeType(node) + if (type !== 'divider') { // 未命中 divider ,执行默认的 normalizeNode return normalizeNode([node, path]) @@ -31,6 +32,7 @@ function withDivider<T extends IDomEditor>(editor: T): T { // -------------- divider 是 editor 最后一个节点,需要后面插入 p -------------- const isLast = DomEditor.isLastNode(newEditor, node) + if (isLast) { Transforms.insertNodes(newEditor, DomEditor.genEmptyParagraph(), { at: [path[0] + 1] }) } diff --git a/packages/basic-modules/src/modules/divider/render-elem.tsx b/packages/basic-modules/src/modules/divider/render-elem.tsx index 02bf55c8e..77d468ca6 100644 --- a/packages/basic-modules/src/modules/divider/render-elem.tsx +++ b/packages/basic-modules/src/modules/divider/render-elem.tsx @@ -3,14 +3,14 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { h, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' function renderDivider( elemNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { const renderStyle: any = {} @@ -32,7 +32,7 @@ function renderDivider( mousedown: event => event.preventDefault(), }, }, - [h('hr')] + [h('hr')], ) // 【注意】void node 中,renderElem 不用处理 children 。core 会统一处理。 diff --git a/packages/basic-modules/src/modules/emotion/index.ts b/packages/basic-modules/src/modules/emotion/index.ts index 2683e082a..d4d1428c4 100644 --- a/packages/basic-modules/src/modules/emotion/index.ts +++ b/packages/basic-modules/src/modules/emotion/index.ts @@ -4,6 +4,7 @@ */ import { IModuleConf } from '@wangeditor-next/core' + import { emotionMenuConf } from './menu/index' const emotion: Partial<IModuleConf> = { diff --git a/packages/basic-modules/src/modules/emotion/menu/EmotionMenu.ts b/packages/basic-modules/src/modules/emotion/menu/EmotionMenu.ts index 16b5e6302..29f110f81 100644 --- a/packages/basic-modules/src/modules/emotion/menu/EmotionMenu.ts +++ b/packages/basic-modules/src/modules/emotion/menu/EmotionMenu.ts @@ -3,16 +3,23 @@ * @author wangfupeng */ +import { + DomEditor, IDomEditor, IDropPanelMenu, t, +} from '@wangeditor-next/core' import { Editor } from 'slate' -import { IDropPanelMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' -import $, { Dom7Array, DOMElement } from '../../../utils/dom' + import { EMOTION_SVG } from '../../../constants/icon-svg' +import $, { Dom7Array, DOMElement } from '../../../utils/dom' class EmotionMenu implements IDropPanelMenu { readonly title = t('emotion.title') + readonly iconSvg = EMOTION_SVG + readonly tag = 'button' + readonly showDropPanel = true // 点击 button 时显示 dropPanel + private $content: Dom7Array | null = null exec(editor: IDomEditor, value: string | boolean) { @@ -31,21 +38,21 @@ class EmotionMenu implements IDropPanelMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const [match] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) - if (type === 'pre') return true // 代码块 - if (Editor.isVoid(editor, n)) return true // void node + if (type === 'pre') { return true } // 代码块 + if (Editor.isVoid(editor, n)) { return true } // void node return false }, universal: true, }) - if (match) return true + if (match) { return true } return false } @@ -57,11 +64,13 @@ class EmotionMenu implements IDropPanelMenu { // 绑定事件(仅第一次绑定,不可重复绑定) $content.on('click', 'li', (e: Event) => { const { target } = e - if (target == null) return + + if (target == null) { return } e.preventDefault() const $li = $(target) const emotionStr = $li.text() + editor.insertText(emotionStr) }) @@ -69,15 +78,18 @@ class EmotionMenu implements IDropPanelMenu { } const $content = this.$content - if ($content == null) return document.createElement('ul') + + if ($content == null) { return document.createElement('ul') } $content.empty() // 清空之后再重置内容 // 获取菜单配置 const colorConf = editor.getMenuConfig('emotion') const { emotions = [] } = colorConf // 根据菜单配置生成 panel content + emotions.forEach((emotion: string) => { const $li = $(`<li>${emotion}</li>`) + $content.append($li) }) diff --git a/packages/basic-modules/src/modules/emotion/menu/config.ts b/packages/basic-modules/src/modules/emotion/menu/config.ts index f07b8a2e8..bef5f57a2 100644 --- a/packages/basic-modules/src/modules/emotion/menu/config.ts +++ b/packages/basic-modules/src/modules/emotion/menu/config.ts @@ -4,7 +4,7 @@ */ export function genConfig() { - const emotions = - '😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😛 😝 😜 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😢 😭 😤 😠 😡 😳 😱 😨 🤗 🤔 😶 😑 😬 🙄 😯 😴 😷 🤑 😈 🤡 💩 👻 💀 👀 👣 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🖕 ✍️ 🙏' + const emotions = '😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😛 😝 😜 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😢 😭 😤 😠 😡 😳 😱 😨 🤗 🤔 😶 😑 😬 🙄 😯 😴 😷 🤑 😈 🤡 💩 👻 💀 👀 👣 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🖕 ✍️ 🙏' + return emotions.split(' ') } diff --git a/packages/basic-modules/src/modules/emotion/menu/index.ts b/packages/basic-modules/src/modules/emotion/menu/index.ts index ff318d165..f3c1f8908 100644 --- a/packages/basic-modules/src/modules/emotion/menu/index.ts +++ b/packages/basic-modules/src/modules/emotion/menu/index.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import EmotionMenu from './EmotionMenu' import { genConfig } from './config' +import EmotionMenu from './EmotionMenu' export const emotionMenuConf = { key: 'emotion', diff --git a/packages/basic-modules/src/modules/font-size-family/custom-types.ts b/packages/basic-modules/src/modules/font-size-family/custom-types.ts index fb6c84152..7f1d7cebc 100644 --- a/packages/basic-modules/src/modules/font-size-family/custom-types.ts +++ b/packages/basic-modules/src/modules/font-size-family/custom-types.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -//【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts export type FontSizeAndFamilyText = { text: string diff --git a/packages/basic-modules/src/modules/font-size-family/index.ts b/packages/basic-modules/src/modules/font-size-family/index.ts index e97208a85..a21caf937 100644 --- a/packages/basic-modules/src/modules/font-size-family/index.ts +++ b/packages/basic-modules/src/modules/font-size-family/index.ts @@ -4,11 +4,12 @@ */ import { IModuleConf } from '@wangeditor-next/core' + +import { fontFamilyMenuConf, fontSizeMenuConf } from './menu/index' +import { parseStyleHtml } from './parse-style-html' +import { preParseHtmlConf } from './pre-parse-html' import { renderStyle } from './render-style' import { styleToHtml } from './style-to-html' -import { preParseHtmlConf } from './pre-parse-html' -import { parseStyleHtml } from './parse-style-html' -import { fontSizeMenuConf, fontFamilyMenuConf } from './menu/index' const fontSizeAndFamily: Partial<IModuleConf> = { renderStyle, diff --git a/packages/basic-modules/src/modules/font-size-family/menu/BaseMenu.ts b/packages/basic-modules/src/modules/font-size-family/menu/BaseMenu.ts index c94e38e30..bc3f41ed6 100644 --- a/packages/basic-modules/src/modules/font-size-family/menu/BaseMenu.ts +++ b/packages/basic-modules/src/modules/font-size-family/menu/BaseMenu.ts @@ -3,14 +3,20 @@ * @author wangfupeng */ +import { + DomEditor, IDomEditor, IOption, ISelectMenu, +} from '@wangeditor-next/core' import { Editor } from 'slate' -import { ISelectMenu, IDomEditor, DomEditor, IOption } from '@wangeditor-next/core' abstract class BaseMenu implements ISelectMenu { abstract readonly title: string + abstract readonly iconSvg: string + abstract readonly mark: string // 'fontSize'/'fontFamily' + readonly tag = 'select' + readonly width = 80 abstract getOptions(editor: IDomEditor): IOption[] @@ -24,19 +30,21 @@ abstract class BaseMenu implements ISelectMenu { const mark = this.mark const curMarks = Editor.marks(editor) // @ts-ignore - if (curMarks && curMarks[mark]) return curMarks[mark] + + if (curMarks && curMarks[mark]) { return curMarks[mark] } return '' } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const mark = this.mark const [match] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) - if (type === 'pre') return true // 代码块 - if (Editor.isVoid(editor, n)) return true // void node + + if (type === 'pre') { return true } // 代码块 + if (Editor.isVoid(editor, n)) { return true } // void node return false }, @@ -44,12 +52,13 @@ abstract class BaseMenu implements ISelectMenu { }) // 匹配到,则禁用 - if (match) return true + if (match) { return true } return false } exec(editor: IDomEditor, value: string | boolean) { const mark = this.mark + if (value) { editor.addMark(mark, value) } else { diff --git a/packages/basic-modules/src/modules/font-size-family/menu/FontFamilyMenu.ts b/packages/basic-modules/src/modules/font-size-family/menu/FontFamilyMenu.ts index a70da4b20..48369cdb2 100644 --- a/packages/basic-modules/src/modules/font-size-family/menu/FontFamilyMenu.ts +++ b/packages/basic-modules/src/modules/font-size-family/menu/FontFamilyMenu.ts @@ -4,13 +4,17 @@ */ import { IDomEditor, IOption, t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { FONT_FAMILY_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class FontFamilyMenu extends BaseMenu { readonly title = t('fontFamily.title') + readonly iconSvg = FONT_FAMILY_SVG + readonly mark = 'fontFamily' + readonly selectPanelWidth = 150 getOptions(editor: IDomEditor): IOption[] { @@ -33,6 +37,7 @@ class FontFamilyMenu extends BaseMenu { }) } else if (typeof family === 'object') { const { name, value } = family + options.push({ text: name, value, @@ -43,6 +48,7 @@ class FontFamilyMenu extends BaseMenu { // 设置 selected const curValue = this.getValue(editor) + options.forEach(opt => { if (opt.value === curValue) { opt.selected = true diff --git a/packages/basic-modules/src/modules/font-size-family/menu/FontSizeMenu.ts b/packages/basic-modules/src/modules/font-size-family/menu/FontSizeMenu.ts index 63652b293..23e66e648 100644 --- a/packages/basic-modules/src/modules/font-size-family/menu/FontSizeMenu.ts +++ b/packages/basic-modules/src/modules/font-size-family/menu/FontSizeMenu.ts @@ -4,12 +4,15 @@ */ import { IDomEditor, IOption, t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { FONT_SIZE_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class FontSizeMenu extends BaseMenu { readonly title = t('fontSize.title') + readonly iconSvg = FONT_SIZE_SVG + readonly mark = 'fontSize' getOptions(editor: IDomEditor): IOption[] { @@ -31,15 +34,17 @@ class FontSizeMenu extends BaseMenu { }) } else if (typeof size === 'object') { const { name, value } = size + options.push({ text: name, - value: value, + value, }) } }) // 设置 selected const curValue = this.getValue(editor) + options.forEach(opt => { if (opt.value === curValue) { opt.selected = true diff --git a/packages/basic-modules/src/modules/font-size-family/menu/config.ts b/packages/basic-modules/src/modules/font-size-family/menu/config.ts index dfb9094ea..feec746f9 100644 --- a/packages/basic-modules/src/modules/font-size-family/menu/config.ts +++ b/packages/basic-modules/src/modules/font-size-family/menu/config.ts @@ -24,7 +24,7 @@ export function genFontSizeConfig() { } export function getFontFamilyConfig() { - let fontFamilyList: Array<string | { name: string; value: string }> = [ + const fontFamilyList: Array<string | { name: string; value: string }> = [ // 元素支持两种形式:1. 字符串;2. { name: 'xxx', value: 'xxx' } '黑体', { name: '仿宋', value: '仿宋' }, diff --git a/packages/basic-modules/src/modules/font-size-family/menu/index.ts b/packages/basic-modules/src/modules/font-size-family/menu/index.ts index 727599959..e78926737 100644 --- a/packages/basic-modules/src/modules/font-size-family/menu/index.ts +++ b/packages/basic-modules/src/modules/font-size-family/menu/index.ts @@ -3,9 +3,9 @@ * @author wangfupeng */ -import FontSizeMenu from './FontSizeMenu' -import FontFamilyMenu from './FontFamilyMenu' import { genFontSizeConfig, getFontFamilyConfig } from './config' +import FontFamilyMenu from './FontFamilyMenu' +import FontSizeMenu from './FontSizeMenu' export const fontSizeMenuConf = { key: 'fontSize', diff --git a/packages/basic-modules/src/modules/font-size-family/parse-style-html.ts b/packages/basic-modules/src/modules/font-size-family/parse-style-html.ts index ea1d4cbbd..77b9c7d75 100644 --- a/packages/basic-modules/src/modules/font-size-family/parse-style-html.ts +++ b/packages/basic-modules/src/modules/font-size-family/parse-style-html.ts @@ -3,14 +3,16 @@ * @author wangfupeng */ -import { Descendant, Text } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { FontSizeAndFamilyText } from './custom-types' +import { Descendant, Text } from 'slate' + import $, { DOMElement, getStyleValue } from '../../utils/dom' +import { FontSizeAndFamilyText } from './custom-types' export function parseStyleHtml(text: DOMElement, node: Descendant, editor: IDomEditor): Descendant { const $text = $(text) - if (!Text.isText(node)) return node + + if (!Text.isText(node)) { return node } const textNode = node as FontSizeAndFamilyText @@ -18,9 +20,8 @@ export function parseStyleHtml(text: DOMElement, node: Descendant, editor: IDomE const { fontSizeList = [] } = editor.getMenuConfig('fontSize') const fontSize = getStyleValue($text, 'font-size') - const includesSize = - fontSizeList.find(item => item.value && item.value === fontSize) || - fontSizeList.includes(fontSize) + const includesSize = fontSizeList.find(item => item.value && item.value === fontSize) + || fontSizeList.includes(fontSize) if (fontSize && includesSize) { textNode.fontSize = fontSize @@ -32,9 +33,8 @@ export function parseStyleHtml(text: DOMElement, node: Descendant, editor: IDomE const fontFamily = getStyleValue($text, 'font-family').replace(/"/g, '') // getFontFamilyConfig 配置支持对象形式 - const includesFamily = - fontFamilyList.find(item => item.value && item.value === fontFamily) || - fontFamilyList.includes(fontFamily) + const includesFamily = fontFamilyList.find(item => item.value && item.value === fontFamily) + || fontFamilyList.includes(fontFamily) if (fontFamily && includesFamily) { textNode.fontFamily = fontFamily diff --git a/packages/basic-modules/src/modules/font-size-family/pre-parse-html.ts b/packages/basic-modules/src/modules/font-size-family/pre-parse-html.ts index c99ce7737..367855b66 100644 --- a/packages/basic-modules/src/modules/font-size-family/pre-parse-html.ts +++ b/packages/basic-modules/src/modules/font-size-family/pre-parse-html.ts @@ -7,13 +7,13 @@ import $, { DOMElement, getTagName } from '../../utils/dom' // V4 font-size 对应关系(V4 使用 <font size="1">xxx</font> 格式) const FONT_SIZE_MAP_FOR_V4 = { - '1': '12px', - '2': '14px', - '3': '16px', - '4': '19px', - '5': '24px', - '6': '32px', - '7': '48px', + 1: '12px', + 2: '14px', + 3: '16px', + 4: '19px', + 5: '24px', + 6: '32px', + 7: '48px', } /** @@ -23,10 +23,12 @@ const FONT_SIZE_MAP_FOR_V4 = { function preParse(fontElem: DOMElement): DOMElement { const $font = $(fontElem) const tagName = getTagName($font) - if (tagName !== 'font') return fontElem + + if (tagName !== 'font') { return fontElem } // 处理 size (V4 使用 <font size="1">xxx</font> 格式) const size = $font.attr('size') || '' + if (size) { $font.removeAttr('size') $font.css('font-size', FONT_SIZE_MAP_FOR_V4[size]) @@ -34,6 +36,7 @@ function preParse(fontElem: DOMElement): DOMElement { // 处理 face (V4 使用 <font face="黑体">xx</font> 格式) const face = $font.attr('face') || '' + if (face) { $font.removeAttr('face') $font.css('font-family', face) diff --git a/packages/basic-modules/src/modules/font-size-family/render-style.tsx b/packages/basic-modules/src/modules/font-size-family/render-style.tsx index e0d31eece..a254474dc 100644 --- a/packages/basic-modules/src/modules/font-size-family/render-style.tsx +++ b/packages/basic-modules/src/modules/font-size-family/render-style.tsx @@ -5,6 +5,7 @@ import { Descendant } from 'slate' import { jsx, VNode } from 'snabbdom' + import { addVnodeStyle } from '../../utils/vdom' import { FontSizeAndFamilyText } from './custom-types' @@ -16,7 +17,7 @@ import { FontSizeAndFamilyText } from './custom-types' */ export function renderStyle(node: Descendant, vnode: VNode): VNode { const { fontSize, fontFamily } = node as FontSizeAndFamilyText - let styleVnode: VNode = vnode + const styleVnode: VNode = vnode if (fontSize) { addVnodeStyle(styleVnode, { fontSize }) diff --git a/packages/basic-modules/src/modules/font-size-family/style-to-html.ts b/packages/basic-modules/src/modules/font-size-family/style-to-html.ts index dd74d829d..72f015a39 100644 --- a/packages/basic-modules/src/modules/font-size-family/style-to-html.ts +++ b/packages/basic-modules/src/modules/font-size-family/style-to-html.ts @@ -3,7 +3,8 @@ * @author wangfupeng */ -import { Text, Descendant } from 'slate' +import { Descendant, Text } from 'slate' + import $, { getOuterHTML, getTagName, isPlainText } from '../../utils/dom' import { FontSizeAndFamilyText } from './custom-types' @@ -14,10 +15,11 @@ import { FontSizeAndFamilyText } from './custom-types' * @returns styled html */ export function styleToHtml(textNode: Descendant, textHtml: string): string { - if (!Text.isText(textNode)) return textHtml + if (!Text.isText(textNode)) { return textHtml } const { fontSize, fontFamily } = textNode as FontSizeAndFamilyText - if (!fontSize && !fontFamily) return textHtml + + if (!fontSize && !fontFamily) { return textHtml } let $text @@ -28,14 +30,15 @@ export function styleToHtml(textNode: Descendant, textHtml: string): string { // textHtml 是 html tag $text = $(textHtml) const tagName = getTagName($text) + if (tagName !== 'span') { // 如果不是 span ,则包裹一层,接下来要设置 css $text = $(`<span>${textHtml}</span>`) } } - if (fontSize) $text.css('font-size', fontSize) - if (fontFamily) $text.css('font-family', fontFamily) + if (fontSize) { $text.css('font-size', fontSize) } + if (fontFamily) { $text.css('font-family', fontFamily) } return getOuterHTML($text) } diff --git a/packages/basic-modules/src/modules/format-painter/index.ts b/packages/basic-modules/src/modules/format-painter/index.ts index 54a52ed34..74c995cf6 100644 --- a/packages/basic-modules/src/modules/format-painter/index.ts +++ b/packages/basic-modules/src/modules/format-painter/index.ts @@ -4,6 +4,7 @@ */ import { IModuleConf } from '@wangeditor-next/core' + import { formatPainterConf } from './menu/index' import withFormatPainter from './plugin' diff --git a/packages/basic-modules/src/modules/format-painter/menu/FormatPainter.ts b/packages/basic-modules/src/modules/format-painter/menu/FormatPainter.ts index ace40193c..bc5512bb3 100644 --- a/packages/basic-modules/src/modules/format-painter/menu/FormatPainter.ts +++ b/packages/basic-modules/src/modules/format-painter/menu/FormatPainter.ts @@ -4,8 +4,9 @@ */ import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' +import { Editor, Text } from 'slate' + import { FORMAT_PAINTER } from '../../../constants/icon-svg' -import { Text, Editor } from 'slate' import { clearAllMarks } from '../helper' interface FormatPaintAttributes { @@ -15,8 +16,11 @@ interface FormatPaintAttributes { class FormatPainter implements IButtonMenu { title = t('formatPainter.title') + iconSvg = FORMAT_PAINTER + tag = 'button' + static attrs: FormatPaintAttributes = { isSelect: false, formatStyle: null, @@ -36,7 +40,8 @@ class FormatPainter implements IButtonMenu { setFormatHtml(editor: IDomEditor) { const selectionText = editor.getSelectionText() - if (!selectionText.length) return + + if (!selectionText.length) { return } if (FormatPainter.attrs.formatStyle) { clearAllMarks(editor) for (const [key, value] of Object.entries(FormatPainter.attrs.formatStyle)) { @@ -55,6 +60,7 @@ class FormatPainter implements IButtonMenu { } else { const selectionText = editor.getSelectionText() // 判断是否选中文本 + if (selectionText.length) { FormatPainter.attrs.formatStyle = Editor.marks(editor) FormatPainter.attrs.isSelect = true diff --git a/packages/basic-modules/src/modules/format-painter/plugin.ts b/packages/basic-modules/src/modules/format-painter/plugin.ts index 229f13a70..700afedc5 100644 --- a/packages/basic-modules/src/modules/format-painter/plugin.ts +++ b/packages/basic-modules/src/modules/format-painter/plugin.ts @@ -4,6 +4,7 @@ */ import { IDomEditor } from '@wangeditor-next/core' + import FormatPainter from './menu/FormatPainter' function withFormatPainter<T extends IDomEditor>(editor: T): T { diff --git a/packages/basic-modules/src/modules/full-screen/index.ts b/packages/basic-modules/src/modules/full-screen/index.ts index 9cb1e19f2..68911afe4 100644 --- a/packages/basic-modules/src/modules/full-screen/index.ts +++ b/packages/basic-modules/src/modules/full-screen/index.ts @@ -4,6 +4,7 @@ */ import { IModuleConf } from '@wangeditor-next/core' + import { fullScreenConf } from './menu/index' const fullScreen: Partial<IModuleConf> = { diff --git a/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts b/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts index ec3966d56..81549de9d 100644 --- a/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts +++ b/packages/basic-modules/src/modules/full-screen/menu/FullScreen.ts @@ -4,12 +4,16 @@ */ import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' + import { CANCEL_FULL_SCREEN_SVG, FULL_SCREEN_SVG } from '../../../constants/icon-svg' class FullScreen implements IButtonMenu { title = t('fullScreen.title') + iconSvg = FULL_SCREEN_SVG + tag = 'button' + alwaysEnable = true getValue(editor: IDomEditor): string | boolean { @@ -27,17 +31,17 @@ class FullScreen implements IButtonMenu { getIcon(editor: IDomEditor): string { if (editor.isFullScreen) { return FULL_SCREEN_SVG - } else { - return CANCEL_FULL_SCREEN_SVG } + return CANCEL_FULL_SCREEN_SVG + } getTitle(editor: IDomEditor): string { if (editor.isFullScreen) { return t('fullScreen.title') - } else { - return t('fullScreen.cancelTitle') } + return t('fullScreen.cancelTitle') + } exec(editor: IDomEditor, value: string | boolean) { diff --git a/packages/basic-modules/src/modules/header/custom-types.ts b/packages/basic-modules/src/modules/header/custom-types.ts index 42528f1e5..dd96d76a8 100644 --- a/packages/basic-modules/src/modules/header/custom-types.ts +++ b/packages/basic-modules/src/modules/header/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type Header1Element = { type: 'header1' diff --git a/packages/basic-modules/src/modules/header/helper.ts b/packages/basic-modules/src/modules/header/helper.ts index 71f6a6148..6db2fcd17 100644 --- a/packages/basic-modules/src/modules/header/helper.ts +++ b/packages/basic-modules/src/modules/header/helper.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Editor, Transforms } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' /** * 获取 node type('header1' 'header2' 等),未匹配则返回 'paragraph' @@ -13,13 +13,14 @@ export function getHeaderType(editor: IDomEditor): string { const [match] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) + return type.startsWith('header') // 匹配 node.type 是 header 开头的 node }, universal: true, }) // 未匹配到 header - if (match == null) return 'paragraph' + if (match == null) { return 'paragraph' } // 匹配到 header const [n] = match @@ -28,15 +29,15 @@ export function getHeaderType(editor: IDomEditor): string { } export function isMenuDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const [nodeEntry] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) // 只可用于 p 和 header - if (type === 'paragraph') return true - if (type.startsWith('header')) return true + if (type === 'paragraph') { return true } + if (type.startsWith('header')) { return true } return false }, @@ -56,10 +57,10 @@ export function isMenuDisabled(editor: IDomEditor): boolean { * 设置 node type ('header1' 'header2' 'paragraph' 等) */ export function setHeaderType(editor: IDomEditor, type: string) { - if (!type) return + if (!type) { return } // 执行命令 Transforms.setNodes(editor, { - type: type, + type, }) } diff --git a/packages/basic-modules/src/modules/header/index.ts b/packages/basic-modules/src/modules/header/index.ts index 59acba57d..1373ce826 100644 --- a/packages/basic-modules/src/modules/header/index.ts +++ b/packages/basic-modules/src/modules/header/index.ts @@ -4,31 +4,24 @@ */ import { IModuleConf } from '@wangeditor-next/core' + import { - renderHeader1Conf, - renderHeader2Conf, - renderHeader3Conf, - renderHeader4Conf, - renderHeader5Conf, - renderHeader6Conf, -} from './render-elem' + header1ToHtmlConf, + header2ToHtmlConf, + header3ToHtmlConf, + header4ToHtmlConf, + header5ToHtmlConf, + header6ToHtmlConf, +} from './elem-to-html' import { - HeaderSelectMenuConf, Header1ButtonMenuConf, Header2ButtonMenuConf, Header3ButtonMenuConf, Header4ButtonMenuConf, Header5ButtonMenuConf, Header6ButtonMenuConf, + HeaderSelectMenuConf, } from './menu/index' -import { - header1ToHtmlConf, - header2ToHtmlConf, - header3ToHtmlConf, - header4ToHtmlConf, - header5ToHtmlConf, - header6ToHtmlConf, -} from './elem-to-html' import { parseHeader1HtmlConf, parseHeader2HtmlConf, @@ -38,6 +31,14 @@ import { parseHeader6HtmlConf, } from './parse-elem-html' import withHeader from './plugin' +import { + renderHeader1Conf, + renderHeader2Conf, + renderHeader3Conf, + renderHeader4Conf, + renderHeader5Conf, + renderHeader6Conf, +} from './render-elem' const header: Partial<IModuleConf> = { renderElems: [ diff --git a/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts index 89426b5fc..19e996a46 100644 --- a/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts +++ b/packages/basic-modules/src/modules/header/menu/Header1ButtonMenu.ts @@ -7,6 +7,7 @@ import HeaderButtonMenuBase from './HeaderButtonMenuBase' class Header1ButtonMenu extends HeaderButtonMenuBase { title = 'H1' + type = 'header1' } diff --git a/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts index 28fd480f1..399d2033b 100644 --- a/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts +++ b/packages/basic-modules/src/modules/header/menu/Header2ButtonMenu.ts @@ -7,6 +7,7 @@ import HeaderButtonMenuBase from './HeaderButtonMenuBase' class Header2ButtonMenu extends HeaderButtonMenuBase { title = 'H2' + type = 'header2' } diff --git a/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts index c8915421f..733e47081 100644 --- a/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts +++ b/packages/basic-modules/src/modules/header/menu/Header3ButtonMenu.ts @@ -7,6 +7,7 @@ import HeaderButtonMenuBase from './HeaderButtonMenuBase' class Header3ButtonMenu extends HeaderButtonMenuBase { title = 'H3' + type = 'header3' } diff --git a/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts index ef8ba304f..cc136cfb5 100644 --- a/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts +++ b/packages/basic-modules/src/modules/header/menu/Header4ButtonMenu.ts @@ -7,6 +7,7 @@ import HeaderButtonMenuBase from './HeaderButtonMenuBase' class Header4ButtonMenu extends HeaderButtonMenuBase { title = 'H4' + type = 'header4' } diff --git a/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts index 9cff14a80..c952183a6 100644 --- a/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts +++ b/packages/basic-modules/src/modules/header/menu/Header5ButtonMenu.ts @@ -7,6 +7,7 @@ import HeaderButtonMenuBase from './HeaderButtonMenuBase' class Header5ButtonMenu extends HeaderButtonMenuBase { title = 'H5' + type = 'header5' } diff --git a/packages/basic-modules/src/modules/header/menu/Header6ButtonMenu.ts b/packages/basic-modules/src/modules/header/menu/Header6ButtonMenu.ts index cb885f723..6bf2ee05f 100644 --- a/packages/basic-modules/src/modules/header/menu/Header6ButtonMenu.ts +++ b/packages/basic-modules/src/modules/header/menu/Header6ButtonMenu.ts @@ -7,6 +7,7 @@ import HeaderButtonMenuBase from './HeaderButtonMenuBase' class Header6ButtonMenu extends HeaderButtonMenuBase { title = 'H6' + type = 'header6' } diff --git a/packages/basic-modules/src/modules/header/menu/HeaderButtonMenuBase.ts b/packages/basic-modules/src/modules/header/menu/HeaderButtonMenuBase.ts index 6b3833135..360c57ce0 100644 --- a/packages/basic-modules/src/modules/header/menu/HeaderButtonMenuBase.ts +++ b/packages/basic-modules/src/modules/header/menu/HeaderButtonMenuBase.ts @@ -4,11 +4,14 @@ */ import { IButtonMenu, IDomEditor } from '@wangeditor-next/core' + import { getHeaderType, isMenuDisabled, setHeaderType } from '../helper' abstract class HeaderButtonMenuBase implements IButtonMenu { abstract readonly title: string + abstract readonly type: string // 'header1' 'header2' 等 + readonly tag = 'button' /** @@ -30,6 +33,7 @@ abstract class HeaderButtonMenuBase implements IButtonMenu { exec(editor: IDomEditor, value: string | boolean) { const { type } = this let newType + if (value === type) { // 选中的 node.type 和当前 type 一样,则取消 newType = 'paragraph' diff --git a/packages/basic-modules/src/modules/header/menu/HeaderSelectMenu.ts b/packages/basic-modules/src/modules/header/menu/HeaderSelectMenu.ts index 085fb3f4c..3422ecb50 100644 --- a/packages/basic-modules/src/modules/header/menu/HeaderSelectMenu.ts +++ b/packages/basic-modules/src/modules/header/menu/HeaderSelectMenu.ts @@ -3,14 +3,20 @@ * @author wangfupeng */ -import { ISelectMenu, IDomEditor, IOption, t } from '@wangeditor-next/core' +import { + IDomEditor, IOption, ISelectMenu, t, +} from '@wangeditor-next/core' + import { HEADER_SVG } from '../../../constants/icon-svg' import { getHeaderType, isMenuDisabled, setHeaderType } from '../helper' class HeaderSelectMenu implements ISelectMenu { readonly title = t('header.title') + readonly iconSvg = HEADER_SVG + readonly tag = 'select' + readonly width = 60 getOptions(editor: IDomEditor): IOption[] { @@ -52,6 +58,7 @@ class HeaderSelectMenu implements ISelectMenu { // 获取 value ,设置 selected const curValue = this.getValue(editor).toString() + options.forEach((opt: IOption) => { if (opt.value === curValue) { opt.selected = true @@ -86,7 +93,7 @@ class HeaderSelectMenu implements ISelectMenu { * @param value node.type */ exec(editor: IDomEditor, value: string | boolean) { - //【注意】value 是 select change 时获取的,并不是 this.getValue 的值 + // 【注意】value 是 select change 时获取的,并不是 this.getValue 的值 setHeaderType(editor, value.toString()) } } diff --git a/packages/basic-modules/src/modules/header/menu/index.ts b/packages/basic-modules/src/modules/header/menu/index.ts index 5411cfaed..342104639 100644 --- a/packages/basic-modules/src/modules/header/menu/index.ts +++ b/packages/basic-modules/src/modules/header/menu/index.ts @@ -3,13 +3,13 @@ * @author wangfupeng */ -import HeaderSelectMenu from './HeaderSelectMenu' import Header1ButtonMenu from './Header1ButtonMenu' import Header2ButtonMenu from './Header2ButtonMenu' import Header3ButtonMenu from './Header3ButtonMenu' import Header4ButtonMenu from './Header4ButtonMenu' import Header5ButtonMenu from './Header5ButtonMenu' import Header6ButtonMenu from './Header6ButtonMenu' +import HeaderSelectMenu from './HeaderSelectMenu' export const HeaderSelectMenuConf = { key: 'headerSelect', diff --git a/packages/basic-modules/src/modules/header/parse-elem-html.ts b/packages/basic-modules/src/modules/header/parse-elem-html.ts index 5ca85c20a..934ccdc33 100644 --- a/packages/basic-modules/src/modules/header/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/header/parse-elem-html.ts @@ -3,9 +3,10 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Descendant, Text } from 'slate' + import $, { DOMElement } from '../../utils/dom' -import { IDomEditor } from '@wangeditor-next/core' import { Header1Element, Header2Element, @@ -17,9 +18,10 @@ import { function genParser<T>(level: number) { function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): T { const $elem = $(elem) + children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true + if (Text.isText(child)) { return true } + if (editor.isInline(child)) { return true } return false }) diff --git a/packages/basic-modules/src/modules/header/plugin.ts b/packages/basic-modules/src/modules/header/plugin.ts index 0c56f0b21..8aa903e46 100644 --- a/packages/basic-modules/src/modules/header/plugin.ts +++ b/packages/basic-modules/src/modules/header/plugin.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Editor, Transforms } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' function withHeader<T extends IDomEditor>(editor: T): T { const { insertBreak, insertNode } = editor @@ -15,6 +15,7 @@ function withHeader<T extends IDomEditor>(editor: T): T { const [match] = Editor.nodes(newEditor, { match: n => { const type = DomEditor.getNodeType(n) + return type.startsWith('header') // 匹配 node.type 是 header 开头的 node }, universal: true, @@ -31,6 +32,7 @@ function withHeader<T extends IDomEditor>(editor: T): T { // 如果在行末则插入一个空 p,否则正常换行 if (isAtLineEnd) { const p = { type: 'paragraph', children: [{ text: '' }] } + Transforms.insertNodes(newEditor, p, { mode: 'highest' }) } else { insertBreak() diff --git a/packages/basic-modules/src/modules/header/render-elem.tsx b/packages/basic-modules/src/modules/header/render-elem.tsx index 83a731377..ba23ab709 100644 --- a/packages/basic-modules/src/modules/header/render-elem.tsx +++ b/packages/basic-modules/src/modules/header/render-elem.tsx @@ -3,9 +3,9 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor-next/core' function genRenderElem(level: number) { /** @@ -18,10 +18,11 @@ function genRenderElem(level: number) { function renderHeader( elemNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { const Tag = `h${level}` const vnode = <Tag>{children}</Tag> + return vnode } diff --git a/packages/basic-modules/src/modules/image/custom-types.ts b/packages/basic-modules/src/modules/image/custom-types.ts index fe5befa59..d6868f02d 100644 --- a/packages/basic-modules/src/modules/image/custom-types.ts +++ b/packages/basic-modules/src/modules/image/custom-types.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts type EmptyText = { text: '' diff --git a/packages/basic-modules/src/modules/image/elem-to-html.ts b/packages/basic-modules/src/modules/image/elem-to-html.ts index 63a065d62..9b0acaa2c 100644 --- a/packages/basic-modules/src/modules/image/elem-to-html.ts +++ b/packages/basic-modules/src/modules/image/elem-to-html.ts @@ -4,15 +4,19 @@ */ import { Element } from 'slate' + import { ImageElement } from './custom-types' function imageToHtml(elemNode: Element, childrenHtml: string): string { - const { src, alt = '', href = '', width = '', height = '', style = {} } = elemNode as ImageElement + const { + src, alt = '', href = '', width = '', height = '', style = {}, + } = elemNode as ImageElement const { width: styleWidth = '', height: styleHeight = '' } = style let styleStr = '' - if (styleWidth) styleStr += `width: ${styleWidth};` - if (styleHeight) styleStr += `height: ${styleHeight};` + + if (styleWidth) { styleStr += `width: ${styleWidth};` } + if (styleHeight) { styleStr += `height: ${styleHeight};` } return `<img src="${src}" alt="${alt}" data-href="${href}" width="${width}" height="${height}" style="${styleStr}"/>` } diff --git a/packages/basic-modules/src/modules/image/helper.ts b/packages/basic-modules/src/modules/image/helper.ts index 156b99fce..2709af381 100644 --- a/packages/basic-modules/src/modules/image/helper.ts +++ b/packages/basic-modules/src/modules/image/helper.ts @@ -3,21 +3,24 @@ * @author wangfupeng */ -import { Transforms, Range, Editor } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' -import { ImageElement, ImageStyle } from './custom-types' +import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { Editor, Range, Transforms } from 'slate' + import { replaceSymbols } from '../../utils/util' +import { ImageElement, ImageStyle } from './custom-types' async function check( menuKey: string, editor: IDomEditor, src: string, - alt: string = '', - href: string = '' + alt = '', + href = '', ): Promise<boolean> { const { checkImage } = editor.getMenuConfig(menuKey) + if (checkImage) { const res = await checkImage(src, alt, href) + if (typeof res === 'string') { // 检验未通过,提示信息 editor.alert(res, 'error') @@ -34,8 +37,10 @@ async function check( async function parseSrc(menuKey: string, editor: IDomEditor, src: string): Promise<string> { const { parseImageSrc } = editor.getMenuConfig(menuKey) + if (parseImageSrc) { const newSrc = await parseImageSrc(src) + return newSrc } return src @@ -44,11 +49,12 @@ async function parseSrc(menuKey: string, editor: IDomEditor, src: string): Promi export async function insertImageNode( editor: IDomEditor, src: string, - alt: string = '', - href: string = '' + alt = '', + href = '', ) { const res = await check('insertImage', editor, src, alt, href) - if (!res) return // 检查失败,终止操作 + + if (!res) { return } // 检查失败,终止操作 const parsedSrc = await parseSrc('insertImage', editor, src) @@ -63,37 +69,40 @@ export async function insertImageNode( } // 如果 blur ,则恢复选区 - if (editor.selection === null) editor.restoreSelection() + if (editor.selection === null) { editor.restoreSelection() } // 如果当前正好选中了图片,则 move 一下(如:连续上传多张图片时) if (DomEditor.getSelectedNodeByType(editor, 'image')) { editor.move(1) } - if (isInsertImageMenuDisabled(editor)) return + if (isInsertImageMenuDisabled(editor)) { return } // 插入图片 Transforms.insertNodes(editor, image) // 回调 const { onInsertedImage } = editor.getMenuConfig('insertImage') - if (onInsertedImage) onInsertedImage(image) + + if (onInsertedImage) { onInsertedImage(image) } } export async function updateImageNode( editor: IDomEditor, src: string, - alt: string = '', - href: string = '', - style: ImageStyle = {} + alt = '', + href = '', + style: ImageStyle = {}, ) { const res = await check('editImage', editor, src, alt, href) - if (!res) return // 检查失败,终止操作 + + if (!res) { return } // 检查失败,终止操作 const parsedSrc = await parseSrc('editImage', editor, src) const selectedImageNode = DomEditor.getSelectedNodeByType(editor, 'image') - if (selectedImageNode == null) return + + if (selectedImageNode == null) { return } const { style: curStyle = {} } = selectedImageNode as ImageElement // 修改图片 @@ -106,6 +115,7 @@ export async function updateImageNode( ...style, }, } + Transforms.setNodes(editor, nodeProps, { match: n => DomEditor.checkNodeType(n, 'image'), }) @@ -113,7 +123,8 @@ export async function updateImageNode( // 回调 const imageNode = DomEditor.getSelectedNodeByType(editor, 'image') const { onUpdatedImage } = editor.getMenuConfig('editImage') - if (onUpdatedImage) onUpdatedImage(imageNode) + + if (onUpdatedImage) { onUpdatedImage(imageNode) } } /** @@ -122,26 +133,27 @@ export async function updateImageNode( */ export function isInsertImageMenuDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用 + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } // 选区非折叠,禁用 const [match] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) - if (type === 'code') return true // 代码块 - if (type === 'pre') return true // 代码块 - if (type === 'link') return true // 链接 - if (type === 'list-item') return true // list - if (type.startsWith('header')) return true // 标题 - if (type === 'blockquote') return true // 引用 - if (Editor.isVoid(editor, n)) return true // void + if (type === 'code') { return true } // 代码块 + if (type === 'pre') { return true } // 代码块 + if (type === 'link') { return true } // 链接 + if (type === 'list-item') { return true } // list + if (type.startsWith('header')) { return true } // 标题 + if (type === 'blockquote') { return true } // 引用 + if (Editor.isVoid(editor, n)) { return true } // void return false }, universal: true, }) - if (match) return true + if (match) { return true } return false } diff --git a/packages/basic-modules/src/modules/image/index.ts b/packages/basic-modules/src/modules/image/index.ts index e35f2785c..349de9fdd 100644 --- a/packages/basic-modules/src/modules/image/index.ts +++ b/packages/basic-modules/src/modules/image/index.ts @@ -4,20 +4,21 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import withImage from './plugin' -import { renderImageConf } from './render-elem' + import { imageToHtmlConf } from './elem-to-html' -import { parseHtmlConf } from './parse-elem-html' import { - insertImageMenuConf, deleteImageMenuConf, editImageMenuConf, - viewImageLinkMenuConf, + EditorImageSizeMenuConf, imageWidth30MenuConf, imageWidth50MenuConf, imageWidth100MenuConf, - EditorImageSizeMenuConf, + insertImageMenuConf, + viewImageLinkMenuConf, } from './menu/index' +import { parseHtmlConf } from './parse-elem-html' +import withImage from './plugin' +import { renderImageConf } from './render-elem' const image: Partial<IModuleConf> = { renderElems: [renderImageConf], diff --git a/packages/basic-modules/src/modules/image/menu/DeleteImage.ts b/packages/basic-modules/src/modules/image/menu/DeleteImage.ts index 06f0a07cc..7f968b460 100644 --- a/packages/basic-modules/src/modules/image/menu/DeleteImage.ts +++ b/packages/basic-modules/src/modules/image/menu/DeleteImage.ts @@ -3,13 +3,18 @@ * @author wangfupeng */ +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' import { Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' + import { TRASH_SVG } from '../../../constants/icon-svg' class DeleteImage implements IButtonMenu { readonly title = t('image.delete') + readonly iconSvg = TRASH_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -23,9 +28,10 @@ class DeleteImage implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const imageNode = DomEditor.getSelectedNodeByType(editor, 'image') + if (imageNode == null) { // 选区未处于 image node ,则禁用 return true @@ -34,7 +40,7 @@ class DeleteImage implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } // 删除图片 Transforms.removeNodes(editor, { diff --git a/packages/basic-modules/src/modules/image/menu/EditImage.ts b/packages/basic-modules/src/modules/image/menu/EditImage.ts index b2942abcb..a87d66e16 100644 --- a/packages/basic-modules/src/modules/image/menu/EditImage.ts +++ b/packages/basic-modules/src/modules/image/menu/EditImage.ts @@ -3,20 +3,21 @@ * @author wangfupeng */ -import { Node, Range } from 'slate' import { - IModalMenu, - IDomEditor, DomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Node, Range } from 'slate' + +import { PENCIL_SVG } from '../../../constants/icon-svg' import $, { Dom7Array, DOMElement } from '../../../utils/dom' import { genRandomStr } from '../../../utils/util' -import { PENCIL_SVG } from '../../../constants/icon-svg' -import { updateImageNode } from '../helper' import { ImageElement, ImageStyle } from '../custom-types' +import { updateImageNode } from '../helper' /** * 生成唯一的 DOM ID @@ -27,14 +28,23 @@ function genDomID(): string { class EditImage implements IModalMenu { readonly title = t('image.edit') + readonly iconSvg = PENCIL_SVG + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 300 + private $content: Dom7Array | null = null + private readonly srcInputId = genDomID() + private readonly altInputId = genDomID() + private readonly hrefInputId = genDomID() + private readonly buttonId = genDomID() getValue(editor: IDomEditor): string | boolean { @@ -58,13 +68,14 @@ class EditImage implements IModalMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用 + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } // 选区非折叠,禁用 const imageNode = DomEditor.getSelectedNodeByType(editor, 'image') // 未匹配到 image node 则禁用 - if (imageNode == null) return true + if (imageNode == null) { return true } return false } @@ -73,9 +84,12 @@ class EditImage implements IModalMenu { } getModalContentElem(editor: IDomEditor): DOMElement { - const { srcInputId, altInputId, hrefInputId, buttonId } = this + const { + srcInputId, altInputId, hrefInputId, buttonId, + } = this const selectedImageNode = this.getImageNode(editor) + if (selectedImageNode == null) { throw new Error('Not found selected image node') } @@ -100,6 +114,7 @@ class EditImage implements IModalMenu { const src = $content.find(`#${srcInputId}`).val() const alt = $content.find(`#${altInputId}`).val() const href = $content.find(`#${hrefInputId}`).val() + this.updateImage(editor, src, alt, href) editor.hidePanelOrModal() // 隐藏 modal }) @@ -109,6 +124,7 @@ class EditImage implements IModalMenu { } const $content = this.$content + $content.empty() // 先清空内容 // append inputs and button @@ -119,6 +135,7 @@ class EditImage implements IModalMenu { // 设置 input val const { src, alt = '', href = '' } = selectedImageNode as ImageElement + $inputSrc.val(src) $inputAlt.val(alt) $inputHref.val(href) @@ -134,16 +151,16 @@ class EditImage implements IModalMenu { private updateImage( editor: IDomEditor, src: string, - alt: string = '', - href: string = '', - style: ImageStyle = {} + alt = '', + href = '', + style: ImageStyle = {}, ) { - if (!src) return + if (!src) { return } // 还原选区 editor.restoreSelection() - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } // 修改图片信息 updateImageNode(editor, src, alt, href, style) diff --git a/packages/basic-modules/src/modules/image/menu/EditImageSizeMenu.ts b/packages/basic-modules/src/modules/image/menu/EditImageSizeMenu.ts index d98c470a5..0647ea671 100644 --- a/packages/basic-modules/src/modules/image/menu/EditImageSizeMenu.ts +++ b/packages/basic-modules/src/modules/image/menu/EditImageSizeMenu.ts @@ -3,15 +3,16 @@ * @author wangfupeng */ -import { Node as SlateNode, Transforms } from 'slate' import { - IModalMenu, - IDomEditor, DomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Node as SlateNode, Transforms } from 'slate' + import $, { Dom7Array, DOMElement } from '../../../utils/dom' import { genRandomStr } from '../../../utils/util' import { ImageElement } from '../custom-types' @@ -25,12 +26,19 @@ function genDomID(): string { class EditorImageSizeMenu implements IModalMenu { readonly title = t('image.editSize') + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 320 + private $content: Dom7Array | null = null + private readonly widthInputId = genDomID() + private readonly heightInputId = genDomID() + private readonly buttonId = genDomID() private getSelectedImageNode(editor: IDomEditor): SlateNode | null { @@ -53,9 +61,10 @@ class EditorImageSizeMenu implements IModalMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const imageNode = this.getSelectedImageNode(editor) + if (imageNode == null) { // 选区未处于 image node ,则禁用 return true @@ -75,13 +84,13 @@ class EditorImageSizeMenu implements IModalMenu { const [widthContainerElem, inputWidthElem] = genModalInputElems( t('image.width'), widthInputId, - 'auto' + 'auto', ) const $inputWidth = $(inputWidthElem) const [heightContainerElem, inputHeightElem] = genModalInputElems( t('image.height'), heightInputId, - 'auto' + 'auto', ) const $inputHeight = $(inputHeightElem) const [buttonContainerElem] = genModalButtonElems(buttonId, t('image.ok')) @@ -107,7 +116,7 @@ class EditorImageSizeMenu implements IModalMenu { if (isPercentage(rawWidth)) { width = rawWidth } else if (isNumeric(rawWidth)) { - width = parseInt(rawWidth) + 'px' + width = `${parseInt(rawWidth)}px` } else if (isPixelValue(rawWidth)) { width = rawWidth } @@ -115,7 +124,7 @@ class EditorImageSizeMenu implements IModalMenu { if (isPercentage(rawHeight)) { height = rawHeight } else if (isNumeric(rawHeight)) { - height = parseInt(rawHeight) + 'px' + height = `${parseInt(rawHeight)}px` } else if (isPixelValue(rawHeight)) { height = rawHeight } @@ -126,8 +135,8 @@ class EditorImageSizeMenu implements IModalMenu { const props: Partial<ImageElement> = { ...style, style: { - width: width, - height: height, + width, + height, }, } @@ -150,11 +159,13 @@ class EditorImageSizeMenu implements IModalMenu { $content.append(buttonContainerElem) const imageNode = this.getSelectedImageNode(editor) as ImageElement - if (imageNode == null) return $content[0] + + if (imageNode == null) { return $content[0] } // 初始化 input 值 const { style = {} } = imageNode const { width = 'auto', height = 'auto' } = style + $inputWidth.val(width) $inputHeight.val(height) setTimeout(() => { diff --git a/packages/basic-modules/src/modules/image/menu/InsertImage.ts b/packages/basic-modules/src/modules/image/menu/InsertImage.ts index 8dea6ef8b..cf3c7b2f6 100644 --- a/packages/basic-modules/src/modules/image/menu/InsertImage.ts +++ b/packages/basic-modules/src/modules/image/menu/InsertImage.ts @@ -3,17 +3,18 @@ * @author wangfupeng */ -import { Node } from 'slate' import { - IModalMenu, - IDomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Node } from 'slate' + +import { IMAGE_SVG } from '../../../constants/icon-svg' import $, { Dom7Array, DOMElement } from '../../../utils/dom' import { genRandomStr } from '../../../utils/util' -import { IMAGE_SVG } from '../../../constants/icon-svg' import { insertImageNode, isInsertImageMenuDisabled } from '../helper' /** @@ -25,14 +26,23 @@ function genDomID(): string { class InsertImage implements IModalMenu { readonly title = t('image.netImage') + readonly iconSvg = IMAGE_SVG + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 300 + private $content: Dom7Array | null = null + private readonly srcInputId = genDomID() + private readonly altInputId = genDomID() + private readonly hrefInputId = genDomID() + private readonly buttonId = genDomID() getValue(editor: IDomEditor): string | boolean { @@ -59,7 +69,9 @@ class InsertImage implements IModalMenu { } getModalContentElem(editor: IDomEditor): DOMElement { - const { srcInputId, altInputId, hrefInputId, buttonId } = this + const { + srcInputId, altInputId, hrefInputId, buttonId, + } = this // 获取 input button elem const [srcContainerElem, inputSrcElem] = genModalInputElems(t('image.src'), srcInputId) @@ -80,6 +92,7 @@ class InsertImage implements IModalMenu { const src = $content.find(`#${srcInputId}`).val().trim() const alt = $content.find(`#${altInputId}`).val().trim() const href = $content.find(`#${hrefInputId}`).val().trim() + this.insertImage(editor, src, alt, href) editor.hidePanelOrModal() // 隐藏 modal }) @@ -89,6 +102,7 @@ class InsertImage implements IModalMenu { } const $content = this.$content + $content.empty() // 先清空内容 // append inputs and button @@ -110,13 +124,13 @@ class InsertImage implements IModalMenu { return $content[0] } - private insertImage(editor: IDomEditor, src: string, alt: string = '', href: string = '') { - if (!src) return + private insertImage(editor: IDomEditor, src: string, alt = '', href = '') { + if (!src) { return } // 还原选区 editor.restoreSelection() - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } // 插入图片 insertImageNode(editor, src, alt, href) diff --git a/packages/basic-modules/src/modules/image/menu/ViewImageLink.ts b/packages/basic-modules/src/modules/image/menu/ViewImageLink.ts index 75576df54..16e65d5f1 100644 --- a/packages/basic-modules/src/modules/image/menu/ViewImageLink.ts +++ b/packages/basic-modules/src/modules/image/menu/ViewImageLink.ts @@ -3,17 +3,23 @@ * @author wangfupeng */ -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' + import { EXTERNAL_SVG } from '../../../constants/icon-svg' import { ImageElement } from '../custom-types' class ViewImageLink implements IButtonMenu { readonly title = t('image.viewLink') + readonly iconSvg = EXTERNAL_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { const imageNode = DomEditor.getSelectedNodeByType(editor, 'image') + if (imageNode) { // 选区处于 image node return (imageNode as ImageElement).href || '' @@ -27,9 +33,10 @@ class ViewImageLink implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const href = this.getValue(editor) + if (href) { // 有 image href ,则不禁用 return false @@ -38,7 +45,7 @@ class ViewImageLink implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } if (!value || typeof value !== 'string') { throw new Error(`View image link failed, image.href is '${value}'`) diff --git a/packages/basic-modules/src/modules/image/menu/Width100.ts b/packages/basic-modules/src/modules/image/menu/Width100.ts index f4959e64f..473e28795 100644 --- a/packages/basic-modules/src/modules/image/menu/Width100.ts +++ b/packages/basic-modules/src/modules/image/menu/Width100.ts @@ -7,6 +7,7 @@ import ImageWidthBaseClass from './WidthBase' class ImageWidth100 extends ImageWidthBaseClass { readonly title = '100%' // 菜单标题 + readonly value = '100%' // css width 的值 } diff --git a/packages/basic-modules/src/modules/image/menu/Width30.ts b/packages/basic-modules/src/modules/image/menu/Width30.ts index 2f2047569..5419c559a 100644 --- a/packages/basic-modules/src/modules/image/menu/Width30.ts +++ b/packages/basic-modules/src/modules/image/menu/Width30.ts @@ -7,6 +7,7 @@ import ImageWidthBaseClass from './WidthBase' class ImageWidth30 extends ImageWidthBaseClass { readonly title = '30%' // 菜单标题 + readonly value = '30%' // css width 的值 } diff --git a/packages/basic-modules/src/modules/image/menu/Width50.ts b/packages/basic-modules/src/modules/image/menu/Width50.ts index 9888dfe29..2c93c1e58 100644 --- a/packages/basic-modules/src/modules/image/menu/Width50.ts +++ b/packages/basic-modules/src/modules/image/menu/Width50.ts @@ -7,6 +7,7 @@ import ImageWidthBaseClass from './WidthBase' class ImageWidth50 extends ImageWidthBaseClass { readonly title = '50%' // 菜单标题 + readonly value = '50%' // css width 的值 } diff --git a/packages/basic-modules/src/modules/image/menu/WidthBase.ts b/packages/basic-modules/src/modules/image/menu/WidthBase.ts index 9e9fbb3a8..d55c37432 100644 --- a/packages/basic-modules/src/modules/image/menu/WidthBase.ts +++ b/packages/basic-modules/src/modules/image/menu/WidthBase.ts @@ -3,13 +3,16 @@ * @author wangfupeng */ -import { Transforms, Node } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor-next/core' +import { DomEditor, IButtonMenu, IDomEditor } from '@wangeditor-next/core' +import { Node, Transforms } from 'slate' + import { ImageElement } from '../custom-types' abstract class ImageWidthBaseClass implements IButtonMenu { abstract readonly title: string // 菜单标题 + readonly tag = 'button' + abstract readonly value: string // css width 的值 getValue(editor: IDomEditor): string | boolean { @@ -27,9 +30,10 @@ abstract class ImageWidthBaseClass implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const imageNode = this.getSelectedNode(editor) + if (imageNode == null) { // 选区未处于 image node ,则禁用 return true @@ -38,14 +42,16 @@ abstract class ImageWidthBaseClass implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } const imageNode = this.getSelectedNode(editor) - if (imageNode == null) return + + if (imageNode == null) { return } // 隐藏 hoverbar const hoverbar = DomEditor.getHoverbar(editor) - if (hoverbar) hoverbar.hideAndClean() + + if (hoverbar) { hoverbar.hideAndClean() } const { style = {} } = imageNode as ImageElement const props: Partial<ImageElement> = { diff --git a/packages/basic-modules/src/modules/image/menu/config.ts b/packages/basic-modules/src/modules/image/menu/config.ts index e6b24725c..cad87118a 100644 --- a/packages/basic-modules/src/modules/image/menu/config.ts +++ b/packages/basic-modules/src/modules/image/menu/config.ts @@ -12,7 +12,7 @@ export function genImageMenuConfig() { * @param imageElem ImageElement */ onInsertedImage(imageElem: ImageElement) { - /*自定义*/ + /* 自定义 */ }, /** @@ -20,7 +20,7 @@ export function genImageMenuConfig() { * @param node image node */ onUpdatedImage(node: ImageElement | null) { - /*自定义*/ + /* 自定义 */ }, /** diff --git a/packages/basic-modules/src/modules/image/menu/index.ts b/packages/basic-modules/src/modules/image/menu/index.ts index 850fb4823..d9fbe4e77 100644 --- a/packages/basic-modules/src/modules/image/menu/index.ts +++ b/packages/basic-modules/src/modules/image/menu/index.ts @@ -3,15 +3,15 @@ * @author wangfupeng */ -import InsertImage from './InsertImage' +import { genImageMenuConfig } from './config' import DeleteImage from './DeleteImage' import EditImage from './EditImage' +import EditorImageSizeMenu from './EditImageSizeMenu' +import InsertImage from './InsertImage' import ViewImageLink from './ViewImageLink' import ImageWidth30 from './Width30' import ImageWidth50 from './Width50' import ImageWidth100 from './Width100' -import EditorImageSizeMenu from './EditImageSizeMenu' -import { genImageMenuConfig } from './config' const config = genImageMenuConfig() // menu config diff --git a/packages/basic-modules/src/modules/image/parse-elem-html.ts b/packages/basic-modules/src/modules/image/parse-elem-html.ts index 4833e5d24..2100aa86e 100644 --- a/packages/basic-modules/src/modules/image/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/image/parse-elem-html.ts @@ -3,14 +3,16 @@ * @author wangfupeng */ -import { Descendant } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { ImageElement } from './custom-types' +import { Descendant } from 'slate' + import $, { DOMElement, getStyleValue } from '../../utils/dom' +import { ImageElement } from './custom-types' function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): ImageElement { const $elem = $(elem) let href = $elem.attr('data-href') || '' + href = decodeURIComponent(href) // 兼容 V4 return { diff --git a/packages/basic-modules/src/modules/image/render-elem.tsx b/packages/basic-modules/src/modules/image/render-elem.tsx index 5380e7a19..551982586 100644 --- a/packages/basic-modules/src/modules/image/render-elem.tsx +++ b/packages/basic-modules/src/modules/image/render-elem.tsx @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import throttle from 'lodash.throttle' import { Element as SlateElement, Transforms } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' + import $, { Dom7Array } from '../../utils/dom' import { ImageElement } from './custom-types' @@ -17,6 +18,7 @@ interface IImageSize { function genContainerId(editor: IDomEditor, elemNode: SlateElement) { const { id } = DomEditor.findKey(editor, elemNode) // node 唯一 id + return `w-e-image-container-${id}` } @@ -27,14 +29,15 @@ function renderContainer( editor: IDomEditor, elemNode: SlateElement, imageVnode: VNode, - imageInfo: IImageSize + imageInfo: IImageSize, ): VNode { const { width, height } = imageInfo const style: any = {} - if (width) style.width = width + + if (width) { style.width = width } /** 不强制设置高度 */ - if (height) style.height = height + if (height) { style.height = height } const containerId = genContainerId(editor, elemNode) @@ -52,7 +55,7 @@ function renderResizeContainer( editor: IDomEditor, elemNode: SlateElement, imageVnode: VNode, - imageInfo: IImageSize + imageInfo: IImageSize, ) { const $body = $('body') const containerId = genContainerId(editor, elemNode) @@ -67,7 +70,8 @@ function renderResizeContainer( function getContainerElem(): Dom7Array { const $container = $(`#${containerId}`) - if ($container.length === 0) throw new Error('Cannot find image container elem') + + if ($container.length === 0) { throw new Error('Cannot find image container elem') } return $container } @@ -83,7 +87,8 @@ function renderResizeContainer( // 记录 img 原始宽高 const $img = $container.find('img') - if ($img.length === 0) throw new Error('Cannot find image elem') + + if ($img.length === 0) { throw new Error('Cannot find image elem') } originalWith = $img.width() originalHeight = $img.height() @@ -95,7 +100,8 @@ function renderResizeContainer( // 隐藏 hoverbar const hoverbar = DomEditor.getHoverbar(editor) - if (hoverbar) hoverbar.hideAndClean() + + if (hoverbar) { hoverbar.hideAndClean() } } // mouseover callback (节流) @@ -110,11 +116,11 @@ function renderResizeContainer( /** * 图片有左右3px margin */ - if (newWidth > maxWidth - 6) return // 超过最大宽度,不处理 + if (newWidth > maxWidth - 6) { return } // 超过最大宽度,不处理 // 实时修改 img 宽高 -【注意】这里只修改 DOM ,mouseup 时再统一不修改 node - if ($container == null) return - if (newWidth <= 15 || newHeight <= 15) return // 最小就是 15px + if ($container == null) { return } + if (newWidth <= 15 || newHeight <= 15) { return } // 最小就是 15px $container.css('width', `${newWidth}px`) $container.css('height', `${newHeight}px`) @@ -124,7 +130,7 @@ function renderResizeContainer( // 取消监听 mousemove $body.off('mousemove', onMousemove) - if ($container == null) return + if ($container == null) { return } const newWidth = $container.width().toFixed(2) const newHeight = $container.height().toFixed(2) @@ -136,6 +142,7 @@ function renderResizeContainer( height: `${newHeight}px`, }, } + Transforms.setNodes(editor, props, { at: DomEditor.findPath(editor, elemNode) }) // 取消监听 mouseup @@ -143,8 +150,9 @@ function renderResizeContainer( } const style: any = {} - if (width) style.width = width - if (height) style.height = height + + if (width) { style.width = width } + if (height) { style.height = height } style.boxShadow = '0 0 0 1px #B4D5FF' // 自定义 selected 样式,因为有拖拽触手 return ( @@ -156,6 +164,7 @@ function renderResizeContainer( // 统一绑定拖拽触手的 mousedown 事件 mousedown: (e: MouseEvent) => { const $target = $(e.target as Element) + if (!$target.hasClass('w-e-image-dragger')) { // target 不是 .w-e-image-dragger 拖拽触手,则忽略 return @@ -168,7 +177,8 @@ function renderResizeContainer( // 获取 image 父容器宽度 const parentNode = DomEditor.getParentNode(editor, elemNode) - if (parentNode == null) return + + if (parentNode == null) { return } const parentNodeDom = DomEditor.toDOMNode(editor, parentNode) const rect = parentNodeDom.getBoundingClientRect() // 获取元素的计算样式 @@ -195,13 +205,16 @@ function renderResizeContainer( } function renderImage(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode { - const { src, alt = '', href = '', style = {} } = elemNode as ImageElement + const { + src, alt = '', href = '', style = {}, + } = elemNode as ImageElement const { width = '', height = '' } = style const selected = DomEditor.isNodeSelected(editor, elemNode) // 图片是否选中 const imageStyle: any = { maxWidth: '100%' } - if (width) imageStyle.width = '100%' - if (height) imageStyle.height = '100%' + + if (width) { imageStyle.width = '100%' } + if (height) { imageStyle.height = '100%' } // 【注意】void node 中,renderElem 不用处理 children 。core 会统一处理。 const vnode = <img style={imageStyle} src={src} alt={alt} data-href={href} /> diff --git a/packages/basic-modules/src/modules/indent/custom-types.ts b/packages/basic-modules/src/modules/indent/custom-types.ts index eaafd6873..801559d10 100644 --- a/packages/basic-modules/src/modules/indent/custom-types.ts +++ b/packages/basic-modules/src/modules/indent/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type IndentElement = { type: string diff --git a/packages/basic-modules/src/modules/indent/index.ts b/packages/basic-modules/src/modules/indent/index.ts index 3e1d56677..8c60e2acb 100644 --- a/packages/basic-modules/src/modules/indent/index.ts +++ b/packages/basic-modules/src/modules/indent/index.ts @@ -4,11 +4,12 @@ */ import { IModuleConf } from '@wangeditor-next/core' + +import { delIndentMenuConf, indentMenuConf } from './menu/index' +import { parseStyleHtml } from './parse-style-html' +import { preParseHtmlConf } from './pre-parse-html' import { renderStyle } from './render-style' import { styleToHtml } from './style-to-html' -import { preParseHtmlConf } from './pre-parse-html' -import { parseStyleHtml } from './parse-style-html' -import { indentMenuConf, delIndentMenuConf } from './menu/index' const indent: Partial<IModuleConf> = { renderStyle, diff --git a/packages/basic-modules/src/modules/indent/menu/BaseMenu.ts b/packages/basic-modules/src/modules/indent/menu/BaseMenu.ts index 3b63ced10..1405cb1e9 100644 --- a/packages/basic-modules/src/modules/indent/menu/BaseMenu.ts +++ b/packages/basic-modules/src/modules/indent/menu/BaseMenu.ts @@ -3,12 +3,14 @@ * @author wangfupeng */ +import { DomEditor, IButtonMenu, IDomEditor } from '@wangeditor-next/core' import { Editor, Node } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor-next/core' abstract class BaseMenu implements IButtonMenu { abstract readonly title: string + abstract readonly iconSvg: string + readonly tag = 'button' /** @@ -22,9 +24,10 @@ abstract class BaseMenu implements IButtonMenu { universal: true, }) - if (nodeEntry == null) return '' + if (nodeEntry == null) { return '' } const [n] = nodeEntry // @ts-ignore + return n.indent || '' } @@ -43,8 +46,8 @@ abstract class BaseMenu implements IButtonMenu { const type = DomEditor.getNodeType(n) // 只可用于 p 和 header - if (type === 'paragraph') return true - if (type.startsWith('header')) return true + if (type === 'paragraph') { return true } + if (type.startsWith('header')) { return true } return false }, @@ -52,7 +55,7 @@ abstract class BaseMenu implements IButtonMenu { mode: 'highest', // 匹配最高层级 }) - if (nodeEntry == null) return null + if (nodeEntry == null) { return null } return nodeEntry[0] } diff --git a/packages/basic-modules/src/modules/indent/menu/DecreaseIndentMenu.ts b/packages/basic-modules/src/modules/indent/menu/DecreaseIndentMenu.ts index 93bc00adf..d0174a42d 100644 --- a/packages/basic-modules/src/modules/indent/menu/DecreaseIndentMenu.ts +++ b/packages/basic-modules/src/modules/indent/menu/DecreaseIndentMenu.ts @@ -3,21 +3,25 @@ * @author wangfupeng */ -import { Transforms, Element } from 'slate' import { IDomEditor, t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' +import { Element, Transforms } from 'slate' + import { INDENT_LEFT_SVG } from '../../../constants/icon-svg' import { IndentElement } from '../custom-types' +import BaseMenu from './BaseMenu' class DecreaseIndentMenu extends BaseMenu { readonly title = t('indent.decrease') + readonly iconSvg = INDENT_LEFT_SVG isDisabled(editor: IDomEditor): boolean { const matchNode = this.getMatchNode(editor) - if (matchNode == null) return true // 未匹配 p header 等,则禁用 + + if (matchNode == null) { return true } // 未匹配 p header 等,则禁用 const { indent } = matchNode as IndentElement + if (!indent) { // 没有 indent ,则禁用 return true @@ -32,7 +36,7 @@ class DecreaseIndentMenu extends BaseMenu { { indent: null, }, - { match: n => Element.isElement(n) } + { match: n => Element.isElement(n) }, ) } } diff --git a/packages/basic-modules/src/modules/indent/menu/IncreaseIndentMenu.ts b/packages/basic-modules/src/modules/indent/menu/IncreaseIndentMenu.ts index 7d5e1ba8f..1668a0f4b 100644 --- a/packages/basic-modules/src/modules/indent/menu/IncreaseIndentMenu.ts +++ b/packages/basic-modules/src/modules/indent/menu/IncreaseIndentMenu.ts @@ -3,23 +3,30 @@ * @author wangfupeng */ -import { Transforms, Element, Editor, Text } from 'slate' -import { IDomEditor, t, DomEditor } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' +import { DomEditor, IDomEditor, t } from '@wangeditor-next/core' +import { + Editor, Element, Text, Transforms, +} from 'slate' + import { INDENT_RIGHT_SVG } from '../../../constants/icon-svg' -import { IndentElement } from '../custom-types' import type { FontSizeAndFamilyText } from '../../font-size-family/custom-types' +import { IndentElement } from '../custom-types' +import BaseMenu from './BaseMenu' class IncreaseIndentMenu extends BaseMenu { readonly title = t('indent.increase') + readonly iconSvg = INDENT_RIGHT_SVG + private DEFAULT_INDENT_VALUE = '2em' isDisabled(editor: IDomEditor): boolean { const matchNode = this.getMatchNode(editor) - if (matchNode == null) return true // 未匹配 p header 等,则禁用 + + if (matchNode == null) { return true } // 未匹配 p header 等,则禁用 const { indent } = matchNode as IndentElement + if (indent) { // 有 indent ,则禁用 return true @@ -31,12 +38,12 @@ class IncreaseIndentMenu extends BaseMenu { private getIndentValue(editor: IDomEditor) { const matchNode = this.getMatchNode(editor) - if (!matchNode) return this.DEFAULT_INDENT_VALUE + if (!matchNode) { return this.DEFAULT_INDENT_VALUE } const textChildren = (matchNode as Element).children.filter(Text.isText) const lastTextNode = textChildren[0] as FontSizeAndFamilyText - if (!lastTextNode || !lastTextNode.fontSize) return this.DEFAULT_INDENT_VALUE + if (!lastTextNode || !lastTextNode.fontSize) { return this.DEFAULT_INDENT_VALUE } // 如果段落的第一个 Text 节点 设置了 fontSize 样式,indent 值需要根据 fontSize 进行计算 const fontSize = lastTextNode.fontSize @@ -57,7 +64,7 @@ class IncreaseIndentMenu extends BaseMenu { { match: n => Element.isElement(n), mode: 'highest', - } + }, ) } } diff --git a/packages/basic-modules/src/modules/indent/parse-style-html.ts b/packages/basic-modules/src/modules/indent/parse-style-html.ts index fc0996459..b8775360e 100644 --- a/packages/basic-modules/src/modules/indent/parse-style-html.ts +++ b/packages/basic-modules/src/modules/indent/parse-style-html.ts @@ -3,19 +3,22 @@ * @author wangfupeng */ -import { Descendant, Element } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { IndentElement } from './custom-types' +import { Descendant, Element } from 'slate' + import $, { DOMElement, getStyleValue } from '../../utils/dom' +import { IndentElement } from './custom-types' export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant { const $elem = $(elem) - if (!Element.isElement(node)) return node + + if (!Element.isElement(node)) { return node } const elemNode = node as IndentElement const indent = getStyleValue($elem, 'text-indent') const indentNumber = parseInt(indent, 10) + if (indent && indentNumber > 0) { elemNode.indent = indent } diff --git a/packages/basic-modules/src/modules/indent/pre-parse-html.ts b/packages/basic-modules/src/modules/indent/pre-parse-html.ts index 3c3534c4d..22daf7ab9 100644 --- a/packages/basic-modules/src/modules/indent/pre-parse-html.ts +++ b/packages/basic-modules/src/modules/indent/pre-parse-html.ts @@ -21,6 +21,7 @@ function preParse(elem: DOMElement): DOMElement { if (/\dpx/.test(paddingLeft)) { // px 单位 const num = parseInt(paddingLeft, 10) + if (num % 32 === 0) { // 如 32px 64px ,V5 早期格式 $elem.css('text-indent', '2em') diff --git a/packages/basic-modules/src/modules/indent/render-style.tsx b/packages/basic-modules/src/modules/indent/render-style.tsx index b0019df35..30b2d6806 100644 --- a/packages/basic-modules/src/modules/indent/render-style.tsx +++ b/packages/basic-modules/src/modules/indent/render-style.tsx @@ -3,8 +3,9 @@ * @author wangfupeng */ -import { Element, Descendant } from 'slate' +import { Descendant, Element } from 'slate' import { jsx, VNode } from 'snabbdom' + import { addVnodeStyle } from '../../utils/vdom' import { IndentElement } from './custom-types' @@ -15,10 +16,10 @@ import { IndentElement } from './custom-types' * @returns vnode */ export function renderStyle(node: Descendant, vnode: VNode): VNode { - if (!Element.isElement(node)) return vnode + if (!Element.isElement(node)) { return vnode } const { indent } = node as IndentElement // 如 '2em' - let styleVnode: VNode = vnode + const styleVnode: VNode = vnode if (indent) { addVnodeStyle(styleVnode, { textIndent: indent }) diff --git a/packages/basic-modules/src/modules/indent/style-to-html.ts b/packages/basic-modules/src/modules/indent/style-to-html.ts index c94f0877c..773915ed0 100644 --- a/packages/basic-modules/src/modules/indent/style-to-html.ts +++ b/packages/basic-modules/src/modules/indent/style-to-html.ts @@ -3,18 +3,21 @@ * @author wangfupeng */ -import { Element, Descendant } from 'slate' +import { Descendant, Element } from 'slate' + import $, { getOuterHTML } from '../../utils/dom' import { IndentElement } from './custom-types' export function styleToHtml(node: Descendant, elemHtml: string): string { - if (!Element.isElement(node)) return elemHtml + if (!Element.isElement(node)) { return elemHtml } const { indent } = node as IndentElement // 如 '2em' - if (!indent) return elemHtml + + if (!indent) { return elemHtml } // 设置样式 const $elem = $(elemHtml) + $elem.css('text-indent', indent) // 输出 html diff --git a/packages/basic-modules/src/modules/justify/custom-types.ts b/packages/basic-modules/src/modules/justify/custom-types.ts index 5c2f3b366..67de53e0a 100644 --- a/packages/basic-modules/src/modules/justify/custom-types.ts +++ b/packages/basic-modules/src/modules/justify/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type JustifyElement = { type: string diff --git a/packages/basic-modules/src/modules/justify/index.ts b/packages/basic-modules/src/modules/justify/index.ts index 4b3d0b463..ceb5611ac 100644 --- a/packages/basic-modules/src/modules/justify/index.ts +++ b/packages/basic-modules/src/modules/justify/index.ts @@ -4,15 +4,16 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import { renderStyle } from './render-style' -import { styleToHtml } from './style-to-html' -import { parseStyleHtml } from './parse-style-html' + import { - justifyLeftMenuConf, - justifyRightMenuConf, justifyCenterMenuConf, justifyJustifyMenuConf, + justifyLeftMenuConf, + justifyRightMenuConf, } from './menu/index' +import { parseStyleHtml } from './parse-style-html' +import { renderStyle } from './render-style' +import { styleToHtml } from './style-to-html' const justify: Partial<IModuleConf> = { renderStyle, diff --git a/packages/basic-modules/src/modules/justify/menu/BaseMenu.ts b/packages/basic-modules/src/modules/justify/menu/BaseMenu.ts index 1be8e0f5a..72ad44189 100644 --- a/packages/basic-modules/src/modules/justify/menu/BaseMenu.ts +++ b/packages/basic-modules/src/modules/justify/menu/BaseMenu.ts @@ -3,12 +3,14 @@ * @author wangfupeng */ -import { Editor, Node, Element } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor-next/core' +import { DomEditor, IButtonMenu, IDomEditor } from '@wangeditor-next/core' +import { Editor, Element, Node } from 'slate' abstract class BaseMenu implements IButtonMenu { abstract readonly title: string + abstract readonly iconSvg: string + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -22,17 +24,18 @@ abstract class BaseMenu implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const selectedElems = DomEditor.getSelectedElems(editor) const notMatch = selectedElems.some((elem: Node) => { const { type } = elem as unknown as Element - if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem) && type !== 'video') - return true - if (['pre', 'code'].includes(type)) return true + if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem) && type !== 'video') { return true } + + if (['pre', 'code'].includes(type)) { return true } }) - if (notMatch) return true + + if (notMatch) { return true } return false } diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts index f1ed32ec7..cf2b39757 100644 --- a/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts +++ b/packages/basic-modules/src/modules/justify/menu/JustifyCenterMenu.ts @@ -3,13 +3,15 @@ * @author wangfupeng */ -import { Transforms, Element } from 'slate' import { IDomEditor, t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' +import { Element, Transforms } from 'slate' + import { JUSTIFY_CENTER_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class JustifyCenterMenu extends BaseMenu { readonly title = t('justify.center') + readonly iconSvg = JUSTIFY_CENTER_SVG exec(editor: IDomEditor, value: string | boolean): void { @@ -18,7 +20,7 @@ class JustifyCenterMenu extends BaseMenu { { textAlign: 'center', }, - { match: n => Element.isElement(n) && !editor.isInline(n) } // inline 元素设置text-align 是没作用的 + { match: n => Element.isElement(n) && !editor.isInline(n) }, // inline 元素设置text-align 是没作用的 ) } } diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts index bd932cb3b..21ada6b99 100644 --- a/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts +++ b/packages/basic-modules/src/modules/justify/menu/JustifyJustifyMenu.ts @@ -3,13 +3,15 @@ * @author wangfupeng */ -import { Transforms, Element } from 'slate' import { IDomEditor, t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' +import { Element, Transforms } from 'slate' + import { JUSTIFY_JUSTIFY_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class JustifyJustifyMenu extends BaseMenu { readonly title = t('justify.justify') + readonly iconSvg = JUSTIFY_JUSTIFY_SVG exec(editor: IDomEditor, value: string | boolean): void { @@ -18,7 +20,7 @@ class JustifyJustifyMenu extends BaseMenu { { textAlign: 'justify', }, - { match: n => Element.isElement(n) && !editor.isInline(n) } + { match: n => Element.isElement(n) && !editor.isInline(n) }, ) } } diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts index 8354785f7..9bfde793c 100644 --- a/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts +++ b/packages/basic-modules/src/modules/justify/menu/JustifyLeftMenu.ts @@ -3,13 +3,15 @@ * @author wangfupeng */ -import { Transforms, Element } from 'slate' import { IDomEditor, t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' +import { Element, Transforms } from 'slate' + import { JUSTIFY_LEFT_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class JustifyLeftMenu extends BaseMenu { readonly title = t('justify.left') + readonly iconSvg = JUSTIFY_LEFT_SVG exec(editor: IDomEditor, value: string | boolean): void { @@ -18,7 +20,7 @@ class JustifyLeftMenu extends BaseMenu { { textAlign: 'left', }, - { match: n => Element.isElement(n) && !editor.isInline(n) } + { match: n => Element.isElement(n) && !editor.isInline(n) }, ) } } diff --git a/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts b/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts index d8d158160..463efaef1 100644 --- a/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts +++ b/packages/basic-modules/src/modules/justify/menu/JustifyRightMenu.ts @@ -3,13 +3,15 @@ * @author wangfupeng */ -import { Transforms, Element } from 'slate' import { IDomEditor, t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' +import { Element, Transforms } from 'slate' + import { JUSTIFY_RIGHT_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class JustifyRightMenu extends BaseMenu { readonly title = t('justify.right') + readonly iconSvg = JUSTIFY_RIGHT_SVG exec(editor: IDomEditor, value: string | boolean): void { @@ -18,7 +20,7 @@ class JustifyRightMenu extends BaseMenu { { textAlign: 'right', }, - { match: n => Element.isElement(n) && !editor.isInline(n) } + { match: n => Element.isElement(n) && !editor.isInline(n) }, ) } } diff --git a/packages/basic-modules/src/modules/justify/menu/index.ts b/packages/basic-modules/src/modules/justify/menu/index.ts index 6273afd02..b9662c699 100644 --- a/packages/basic-modules/src/modules/justify/menu/index.ts +++ b/packages/basic-modules/src/modules/justify/menu/index.ts @@ -3,10 +3,10 @@ * @author wangfupeng */ -import JustifyLeftMenu from './JustifyLeftMenu' -import JustifyRightMenu from './JustifyRightMenu' import JustifyCenterMenu from './JustifyCenterMenu' import JustifyJustifyMenu from './JustifyJustifyMenu' +import JustifyLeftMenu from './JustifyLeftMenu' +import JustifyRightMenu from './JustifyRightMenu' export const justifyLeftMenuConf = { key: 'justifyLeft', diff --git a/packages/basic-modules/src/modules/justify/parse-style-html.ts b/packages/basic-modules/src/modules/justify/parse-style-html.ts index 1aaaf0677..ac223e3e7 100644 --- a/packages/basic-modules/src/modules/justify/parse-style-html.ts +++ b/packages/basic-modules/src/modules/justify/parse-style-html.ts @@ -3,18 +3,21 @@ * @author wangfupeng */ -import { Descendant, Element } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { JustifyElement } from './custom-types' +import { Descendant, Element } from 'slate' + import $, { DOMElement, getStyleValue } from '../../utils/dom' +import { JustifyElement } from './custom-types' export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant { const $elem = $(elem) - if (!Element.isElement(node)) return node + + if (!Element.isElement(node)) { return node } const elemNode = node as JustifyElement const textAlign = getStyleValue($elem, 'text-align') + if (textAlign) { elemNode.textAlign = textAlign } diff --git a/packages/basic-modules/src/modules/justify/render-style.tsx b/packages/basic-modules/src/modules/justify/render-style.tsx index c9afe7a30..8947a9891 100644 --- a/packages/basic-modules/src/modules/justify/render-style.tsx +++ b/packages/basic-modules/src/modules/justify/render-style.tsx @@ -5,6 +5,7 @@ import { Descendant, Element } from 'slate' import { jsx, VNode } from 'snabbdom' + import { addVnodeStyle } from '../../utils/vdom' import { JustifyElement } from './custom-types' @@ -15,10 +16,10 @@ import { JustifyElement } from './custom-types' * @returns vnode */ export function renderStyle(node: Descendant, vnode: VNode): VNode { - if (!Element.isElement(node)) return vnode + if (!Element.isElement(node)) { return vnode } const { textAlign } = node as JustifyElement // 如 'left'/'right'/'center' 等 - let styleVnode: VNode = vnode + const styleVnode: VNode = vnode if (textAlign) { addVnodeStyle(styleVnode, { textAlign }) diff --git a/packages/basic-modules/src/modules/justify/style-to-html.ts b/packages/basic-modules/src/modules/justify/style-to-html.ts index a8330a8c1..6e8a29bc6 100644 --- a/packages/basic-modules/src/modules/justify/style-to-html.ts +++ b/packages/basic-modules/src/modules/justify/style-to-html.ts @@ -3,21 +3,25 @@ * @author wangfupeng */ -import { Element, Descendant } from 'slate' +import { Descendant, Element } from 'slate' + import $, { getOuterHTML } from '../../utils/dom' import { JustifyElement } from './custom-types' export function styleToHtml(node: Descendant, elemHtml: string): string { - if (!Element.isElement(node)) return elemHtml + if (!Element.isElement(node)) { return elemHtml } const { textAlign } = node as JustifyElement // 如 'left'/'right'/'center' 等 - if (!textAlign) return elemHtml + + if (!textAlign) { return elemHtml } // 设置样式 const $elem = $(elemHtml) + $elem.css('text-align', textAlign) // 输出 html const outerHtml = getOuterHTML($elem) + return outerHtml } diff --git a/packages/basic-modules/src/modules/line-height/custom-types.ts b/packages/basic-modules/src/modules/line-height/custom-types.ts index 517ae2e8e..aa42a03d5 100644 --- a/packages/basic-modules/src/modules/line-height/custom-types.ts +++ b/packages/basic-modules/src/modules/line-height/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type LineHeightElement = { type: string diff --git a/packages/basic-modules/src/modules/line-height/index.ts b/packages/basic-modules/src/modules/line-height/index.ts index 367b8d3e0..6b7fe135d 100644 --- a/packages/basic-modules/src/modules/line-height/index.ts +++ b/packages/basic-modules/src/modules/line-height/index.ts @@ -4,10 +4,11 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import { renderStyle } from './render-style' -import { styleToHtml } from './style-to-html' + import { lineHeightMenuConf } from './menu/index' import { parseStyleHtml } from './parse-style-html' +import { renderStyle } from './render-style' +import { styleToHtml } from './style-to-html' const lineHeight: Partial<IModuleConf> = { renderStyle, diff --git a/packages/basic-modules/src/modules/line-height/menu/LineHeightMenu.ts b/packages/basic-modules/src/modules/line-height/menu/LineHeightMenu.ts index 3ca2f4b36..c1f5bf400 100644 --- a/packages/basic-modules/src/modules/line-height/menu/LineHeightMenu.ts +++ b/packages/basic-modules/src/modules/line-height/menu/LineHeightMenu.ts @@ -3,15 +3,23 @@ * @author wangfupeng */ -import { Editor, Node, Element, Transforms } from 'slate' -import { ISelectMenu, IDomEditor, DomEditor, IOption, t } from '@wangeditor-next/core' +import { + DomEditor, IDomEditor, IOption, ISelectMenu, t, +} from '@wangeditor-next/core' +import { + Editor, Element, Node, Transforms, +} from 'slate' + import { LINE_HEIGHT_SVG } from '../../../constants/icon-svg' import { LineHeightElement } from '../custom-types' class LineHeightMenu implements ISelectMenu { readonly title = t('lineHeight.title') + readonly iconSvg = LINE_HEIGHT_SVG + readonly tag = 'select' + readonly width = 80 getOptions(editor: IDomEditor): IOption[] { @@ -34,6 +42,7 @@ class LineHeightMenu implements ISelectMenu { // 设置 selected const curValue = this.getValue(editor) + options.forEach(opt => { if (opt.value === curValue) { opt.selected = true @@ -55,7 +64,7 @@ class LineHeightMenu implements ISelectMenu { const type = DomEditor.getNodeType(n) // line-height 匹配如下类型的 node - if (type.startsWith('header')) return true + if (type.startsWith('header')) { return true } if (['paragraph', 'blockquote', 'list-item'].includes(type)) { return true } @@ -66,7 +75,7 @@ class LineHeightMenu implements ISelectMenu { mode: 'highest', // 匹配最高层级 }) - if (nodeEntry == null) return null + if (nodeEntry == null) { return null } return nodeEntry[0] } @@ -81,17 +90,19 @@ class LineHeightMenu implements ISelectMenu { */ getValue(editor: IDomEditor): string | boolean { const node = this.getMatchNode(editor) - if (node == null) return '' - if (!Element.isElement(node)) return '' + + if (node == null) { return '' } + if (!Element.isElement(node)) { return '' } return (node as LineHeightElement).lineHeight || '' } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true // 禁用 + if (editor.selection == null) { return true } // 禁用 const node = this.getMatchNode(editor) - if (node == null) return true // 未匹配到指定 node ,禁用 + + if (node == null) { return true } // 未匹配到指定 node ,禁用 return false } @@ -102,7 +113,7 @@ class LineHeightMenu implements ISelectMenu { { lineHeight: value.toString(), }, - { mode: 'highest' } + { mode: 'highest' }, ) } } diff --git a/packages/basic-modules/src/modules/line-height/menu/index.ts b/packages/basic-modules/src/modules/line-height/menu/index.ts index 1982fdecf..57b541f6b 100644 --- a/packages/basic-modules/src/modules/line-height/menu/index.ts +++ b/packages/basic-modules/src/modules/line-height/menu/index.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import LineHeightMenu from './LineHeightMenu' import { genLineHeightConfig } from './config' +import LineHeightMenu from './LineHeightMenu' export const lineHeightMenuConf = { key: 'lineHeight', diff --git a/packages/basic-modules/src/modules/line-height/parse-style-html.ts b/packages/basic-modules/src/modules/line-height/parse-style-html.ts index 4bc277aea..293e69219 100644 --- a/packages/basic-modules/src/modules/line-height/parse-style-html.ts +++ b/packages/basic-modules/src/modules/line-height/parse-style-html.ts @@ -3,19 +3,22 @@ * @author wangfupeng */ -import { Descendant, Element } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { LineHeightElement } from './custom-types' +import { Descendant, Element } from 'slate' + import $, { DOMElement, getStyleValue } from '../../utils/dom' +import { LineHeightElement } from './custom-types' export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant { const $elem = $(elem) - if (!Element.isElement(node)) return node + + if (!Element.isElement(node)) { return node } const elemNode = node as LineHeightElement const { lineHeightList = [] } = editor.getMenuConfig('lineHeight') const lineHeight = getStyleValue($elem, 'line-height') + if (lineHeight && lineHeightList.includes(lineHeight)) { elemNode.lineHeight = lineHeight } diff --git a/packages/basic-modules/src/modules/line-height/render-style.tsx b/packages/basic-modules/src/modules/line-height/render-style.tsx index 951c816e0..6678d2abd 100644 --- a/packages/basic-modules/src/modules/line-height/render-style.tsx +++ b/packages/basic-modules/src/modules/line-height/render-style.tsx @@ -3,8 +3,9 @@ * @author wangfupeng */ -import { Element, Descendant } from 'slate' +import { Descendant, Element } from 'slate' import { jsx, VNode } from 'snabbdom' + import { addVnodeStyle } from '../../utils/vdom' import { LineHeightElement } from './custom-types' @@ -15,10 +16,10 @@ import { LineHeightElement } from './custom-types' * @returns vnode */ export function renderStyle(node: Descendant, vnode: VNode): VNode { - if (!Element.isElement(node)) return vnode + if (!Element.isElement(node)) { return vnode } const { lineHeight } = node as LineHeightElement // 如 '1' '1.5' - let styleVnode: VNode = vnode + const styleVnode: VNode = vnode if (lineHeight) { addVnodeStyle(styleVnode, { lineHeight }) diff --git a/packages/basic-modules/src/modules/line-height/style-to-html.ts b/packages/basic-modules/src/modules/line-height/style-to-html.ts index d937fdfd6..c10c422b4 100644 --- a/packages/basic-modules/src/modules/line-height/style-to-html.ts +++ b/packages/basic-modules/src/modules/line-height/style-to-html.ts @@ -3,18 +3,21 @@ * @author wangfupeng */ -import { Element, Descendant } from 'slate' +import { Descendant, Element } from 'slate' + import $, { getOuterHTML } from '../../utils/dom' import { LineHeightElement } from './custom-types' export function styleToHtml(node: Descendant, elemHtml: string): string { - if (!Element.isElement(node)) return elemHtml + if (!Element.isElement(node)) { return elemHtml } const { lineHeight } = node as LineHeightElement // 如 '1' '1.5' - if (!lineHeight) return elemHtml + + if (!lineHeight) { return elemHtml } // 设置样式 const $elem = $(elemHtml) + $elem.css('line-height', lineHeight) // 输出 html diff --git a/packages/basic-modules/src/modules/link/custom-types.ts b/packages/basic-modules/src/modules/link/custom-types.ts index ff9647d7b..3834b4f9b 100644 --- a/packages/basic-modules/src/modules/link/custom-types.ts +++ b/packages/basic-modules/src/modules/link/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type LinkElement = { type: 'link' diff --git a/packages/basic-modules/src/modules/link/elem-to-html.ts b/packages/basic-modules/src/modules/link/elem-to-html.ts index 6a270d884..fd566a638 100644 --- a/packages/basic-modules/src/modules/link/elem-to-html.ts +++ b/packages/basic-modules/src/modules/link/elem-to-html.ts @@ -4,6 +4,7 @@ */ import { Element } from 'slate' + import { LinkElement } from './custom-types' function linkToHtml(elem: Element, childrenHtml: string): string { diff --git a/packages/basic-modules/src/modules/link/helper.ts b/packages/basic-modules/src/modules/link/helper.ts index 792ec6c5a..bf467c481 100644 --- a/packages/basic-modules/src/modules/link/helper.ts +++ b/packages/basic-modules/src/modules/link/helper.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Editor, Range, Transforms } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' -import { LinkElement } from './custom-types' + import { replaceSymbols } from '../../utils/util' +import { LinkElement } from './custom-types' /** * 校验 link @@ -19,11 +20,13 @@ async function check( menuKey: string, editor: IDomEditor, text: string, - url: string + url: string, ): Promise<boolean> { const { checkLink } = editor.getMenuConfig(menuKey) + if (checkLink) { const res = await checkLink(text, url) + if (typeof res === 'string') { // 检验未通过,提示信息 editor.alert(res, 'error') @@ -47,23 +50,27 @@ async function check( */ async function parse(menuKey: string, editor: IDomEditor, url: string): Promise<string> { const { parseLinkUrl } = editor.getMenuConfig(menuKey) + if (parseLinkUrl) { const newUrl = await parseLinkUrl(url) + return newUrl } return url } export function isMenuDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const selectedElems = DomEditor.getSelectedElems(editor) const notMatch = selectedElems.some(elem => { const { type } = elem - if (editor.isVoid(elem)) return true - if (['pre', 'code', 'link'].includes(type)) return true + + if (editor.isVoid(elem)) { return true } + if (['pre', 'code', 'link'].includes(type)) { return true } }) - if (notMatch) return true // disabled + + if (notMatch) { return true } // disabled return false // enable } @@ -78,6 +85,7 @@ function genLinkNode(url: string, text?: string): LinkElement { url: replaceSymbols(url), children: text ? [{ text }] : [], } + return linkNode } @@ -88,29 +96,32 @@ function genLinkNode(url: string, text?: string): LinkElement { * @param url url */ export async function insertLink(editor: IDomEditor, text: string, url: string) { - if (!url) return - if (!text) text = url // 无 text 则用 url 代替 + if (!url) { return } + if (!text) { text = url } // 无 text 则用 url 代替 // 还原选区 editor.restoreSelection() - if (isMenuDisabled(editor)) return + if (isMenuDisabled(editor)) { return } // 校验 const checkRes = await check('insertLink', editor, text, url) - if (!checkRes) return // 校验未通过 + + if (!checkRes) { return } // 校验未通过 // 转换 url const parsedUrl = await parse('insertLink', editor, url) // 判断选区是否折叠 const { selection } = editor - if (selection == null) return + + if (selection == null) { return } const isCollapsed = Range.isCollapsed(selection) // 执行:插入链接 if (isCollapsed) { const leftLength = DomEditor.getLeftLengthOfMaxLength(editor) + if (leftLength <= 0) { // 已经触发 maxLength ,不再输入文字 return @@ -125,6 +136,7 @@ export async function insertLink(editor: IDomEditor, text: string, url: string) } const linkNode = genLinkNode(parsedUrl, text) + Transforms.insertNodes(editor, linkNode) // https://github.com/cycleccc/wangEditor/issues/332 @@ -132,10 +144,12 @@ export async function insertLink(editor: IDomEditor, text: string, url: string) editor.insertFragment([{ text: ' ' }]) } else { const selectedText = Editor.string(editor, selection) // 选中的文字 + if (selectedText !== text) { // 选中的文字和输入的文字不一样,则删掉文字,插入链接 const leftLength = DomEditor.getLeftLengthOfMaxLength(editor) + if (leftLength <= 0) { // 已经触发 maxLength ,不再输入文字 return @@ -147,10 +161,12 @@ export async function insertLink(editor: IDomEditor, text: string, url: string) editor.deleteFragment() const linkNode = genLinkNode(parsedUrl, text) + Transforms.insertNodes(editor, linkNode) } else { // 选中的文字和输入的文字一样,则只包裹链接即可 const linkNode = genLinkNode(parsedUrl) + Transforms.wrapNodes(editor, linkNode, { split: true }) Transforms.collapse(editor, { edge: 'end' }) } @@ -164,17 +180,19 @@ export async function insertLink(editor: IDomEditor, text: string, url: string) * @param url link url */ export async function updateLink(editor: IDomEditor, text: string, url: string) { - if (!url) return + if (!url) { return } // 校验 const checkRes = await check('editLink', editor, text, url) - if (!checkRes) return // 校验未通过 + + if (!checkRes) { return } // 校验未通过 // 转换 url const parsedUrl = await parse('editLink', editor, url) // 修改链接 const props: Partial<LinkElement> = { url: replaceSymbols(parsedUrl) } + Transforms.setNodes(editor, props, { match: n => DomEditor.checkNodeType(n, 'link'), }) diff --git a/packages/basic-modules/src/modules/link/index.ts b/packages/basic-modules/src/modules/link/index.ts index d94bd4e91..4df5d1b90 100644 --- a/packages/basic-modules/src/modules/link/index.ts +++ b/packages/basic-modules/src/modules/link/index.ts @@ -4,16 +4,17 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import withLink from './plugin' -import { renderLinkConf } from './render-elem' + import { linkToHtmlConf } from './elem-to-html' -import { parseHtmlConf } from './parse-elem-html' import { - insertLinkMenuConf, editLinkMenuConf, + insertLinkMenuConf, unLinkMenuConf, viewLinkMenuConf, } from './menu/index' +import { parseHtmlConf } from './parse-elem-html' +import withLink from './plugin' +import { renderLinkConf } from './render-elem' const link: Partial<IModuleConf> = { renderElems: [renderLinkConf], diff --git a/packages/basic-modules/src/modules/link/menu/EditLink.ts b/packages/basic-modules/src/modules/link/menu/EditLink.ts index 4abeaed03..bbef1c7bd 100644 --- a/packages/basic-modules/src/modules/link/menu/EditLink.ts +++ b/packages/basic-modules/src/modules/link/menu/EditLink.ts @@ -3,20 +3,21 @@ * @author wangfupeng */ -import { Node } from 'slate' import { - IModalMenu, - IDomEditor, DomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Node } from 'slate' + +import { PENCIL_SVG } from '../../../constants/icon-svg' import $, { Dom7Array, DOMElement } from '../../../utils/dom' import { genRandomStr } from '../../../utils/util' -import { PENCIL_SVG } from '../../../constants/icon-svg' -import { updateLink } from '../helper' import { LinkElement } from '../custom-types' +import { updateLink } from '../helper' /** * 生成唯一的 DOM ID @@ -27,18 +28,25 @@ function genDomID(): string { class EditLinkMenu implements IModalMenu { readonly title = t('link.edit') + readonly iconSvg = PENCIL_SVG + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 300 private $content: Dom7Array | null = null + private urlInputId = genDomID() + private buttonId = genDomID() private getSelectedLinkElem(editor: IDomEditor): LinkElement | null { const node = DomEditor.getSelectedNodeByType(editor, 'link') - if (node == null) return null + + if (node == null) { return null } return node as LinkElement } @@ -48,6 +56,7 @@ class EditLinkMenu implements IModalMenu { */ getValue(editor: IDomEditor): string | boolean { const linkElem = this.getSelectedLinkElem(editor) + if (linkElem) { return linkElem.url || '' } @@ -65,12 +74,12 @@ class EditLinkMenu implements IModalMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const linkElem = this.getSelectedLinkElem(editor) // 未匹配到 link node 则禁用 - if (linkElem == null) return true + if (linkElem == null) { return true } return false } @@ -99,6 +108,7 @@ class EditLinkMenu implements IModalMenu { const n = DomEditor.getSelectedNodeByType(editor, 'link') const text = n ? Node.string(n) : '' const url = $content.find(`#${urlInputId}`).val() + updateLink(editor, text, url) // 修改链接 editor.hidePanelOrModal() // 隐藏 modal @@ -109,6 +119,7 @@ class EditLinkMenu implements IModalMenu { } const $content = this.$content + $content.empty() // 先清空内容 // append input and button @@ -117,6 +128,7 @@ class EditLinkMenu implements IModalMenu { // 设置 input val const url = this.getValue(editor) + $inputUrl.val(url) // focus 一个 input(异步,此时 DOM 尚未渲染) diff --git a/packages/basic-modules/src/modules/link/menu/InsertLink.ts b/packages/basic-modules/src/modules/link/menu/InsertLink.ts index 907481393..edef9c833 100644 --- a/packages/basic-modules/src/modules/link/menu/InsertLink.ts +++ b/packages/basic-modules/src/modules/link/menu/InsertLink.ts @@ -3,18 +3,19 @@ * @author wangfupeng */ -import { Editor, Range, Node } from 'slate' import { - IModalMenu, - IDomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Editor, Node, Range } from 'slate' + +import { LINK_SVG } from '../../../constants/icon-svg' import $, { Dom7Array, DOMElement } from '../../../utils/dom' import { genRandomStr } from '../../../utils/util' -import { LINK_SVG } from '../../../constants/icon-svg' -import { isMenuDisabled, insertLink } from '../helper' +import { insertLink, isMenuDisabled } from '../helper' /** * 生成唯一的 DOM ID @@ -25,13 +26,21 @@ function genDomID(): string { class InsertLinkMenu implements IModalMenu { readonly title = t('link.insert') + readonly iconSvg = LINK_SVG + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 300 + private $content: Dom7Array | null = null + private readonly textInputId = genDomID() + private readonly urlInputId = genDomID() + private readonly buttonId = genDomID() getValue(editor: IDomEditor): string | boolean { @@ -77,6 +86,7 @@ class InsertLinkMenu implements IModalMenu { e.preventDefault() const text = $content.find(`#${textInputId}`).val() const url = $content.find(`#${urlInputId}`).val() + insertLink(editor, text, url) // 插入链接 editor.hidePanelOrModal() // 隐藏 modal }) @@ -86,6 +96,7 @@ class InsertLinkMenu implements IModalMenu { } const $content = this.$content + $content.empty() // 先清空内容 // append inputs and button @@ -100,6 +111,7 @@ class InsertLinkMenu implements IModalMenu { } else { // 选区有内容 const selectionText = Editor.string(editor, selection) + $inputText.val(selectionText) } $inputUrl.val('') diff --git a/packages/basic-modules/src/modules/link/menu/UnLink.ts b/packages/basic-modules/src/modules/link/menu/UnLink.ts index 5dd1af27b..3a906d844 100644 --- a/packages/basic-modules/src/modules/link/menu/UnLink.ts +++ b/packages/basic-modules/src/modules/link/menu/UnLink.ts @@ -3,13 +3,18 @@ * @author wangfupeng */ +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' import { Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' + import { UN_LINK_SVG } from '../../../constants/icon-svg' class UnLink implements IButtonMenu { readonly title = t('link.unLink') + readonly iconSvg = UN_LINK_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -23,9 +28,10 @@ class UnLink implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const linkNode = DomEditor.getSelectedNodeByType(editor, 'link') + if (linkNode == null) { // 选区未处于 link node ,则禁用 return true @@ -34,7 +40,7 @@ class UnLink implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } // 取消链接 Transforms.unwrapNodes(editor, { diff --git a/packages/basic-modules/src/modules/link/menu/ViewLink.ts b/packages/basic-modules/src/modules/link/menu/ViewLink.ts index 51148c382..3c2202d59 100644 --- a/packages/basic-modules/src/modules/link/menu/ViewLink.ts +++ b/packages/basic-modules/src/modules/link/menu/ViewLink.ts @@ -3,23 +3,30 @@ * @author wangfupeng */ -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' + import { EXTERNAL_SVG } from '../../../constants/icon-svg' import { LinkElement } from '../custom-types' class ViewLink implements IButtonMenu { readonly title = t('link.view') + readonly iconSvg = EXTERNAL_SVG + readonly tag = 'button' private getSelectedLinkElem(editor: IDomEditor): LinkElement | null { const node = DomEditor.getSelectedNodeByType(editor, 'link') - if (node == null) return null + + if (node == null) { return null } return node as LinkElement } getValue(editor: IDomEditor): string | boolean { const linkElem = this.getSelectedLinkElem(editor) + if (linkElem) { return linkElem.url || '' } @@ -32,9 +39,10 @@ class ViewLink implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const linkElem = this.getSelectedLinkElem(editor) + if (linkElem == null) { // 选区未处于 link node ,则禁用 return true @@ -43,7 +51,7 @@ class ViewLink implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } if (!value || typeof value !== 'string') { throw new Error(`View link failed, link url is '${value}'`) diff --git a/packages/basic-modules/src/modules/link/menu/index.ts b/packages/basic-modules/src/modules/link/menu/index.ts index c75a8f936..553cf830c 100644 --- a/packages/basic-modules/src/modules/link/menu/index.ts +++ b/packages/basic-modules/src/modules/link/menu/index.ts @@ -3,11 +3,11 @@ * @author wangfupeng */ -import InsertLink from './InsertLink' +import { genLinkMenuConfig } from './config' import EditLink from './EditLink' +import InsertLink from './InsertLink' import UnLink from './UnLink' import ViewLink from './ViewLink' -import { genLinkMenuConfig } from './config' const config = genLinkMenuConfig() // menu config @@ -44,4 +44,6 @@ const viewLinkMenuConf = { }, } -export { insertLinkMenuConf, editLinkMenuConf, unLinkMenuConf, viewLinkMenuConf } +export { + editLinkMenuConf, insertLinkMenuConf, unLinkMenuConf, viewLinkMenuConf, +} diff --git a/packages/basic-modules/src/modules/link/parse-elem-html.ts b/packages/basic-modules/src/modules/link/parse-elem-html.ts index 0bda8473c..f43da9f28 100644 --- a/packages/basic-modules/src/modules/link/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/link/parse-elem-html.ts @@ -3,16 +3,18 @@ * @author wangfupeng */ -import { Descendant, Text } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { LinkElement } from './custom-types' +import { Descendant, Text } from 'slate' + import $, { DOMElement } from '../../utils/dom' +import { LinkElement } from './custom-types' function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): LinkElement { const $elem = $(elem) + children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true + if (Text.isText(child)) { return true } + if (editor.isInline(child)) { return true } return false }) diff --git a/packages/basic-modules/src/modules/link/plugin.ts b/packages/basic-modules/src/modules/link/plugin.ts index 2a5d72ff4..778664365 100644 --- a/packages/basic-modules/src/modules/link/plugin.ts +++ b/packages/basic-modules/src/modules/link/plugin.ts @@ -3,13 +3,16 @@ * @author wangfupeng */ -import { Editor, Node, Transforms } from 'slate' import { DomEditor, IDomEditor } from '@wangeditor-next/core' import isUrl from 'is-url' -import { isMenuDisabled, insertLink } from './helper' +import { Editor, Node, Transforms } from 'slate' + +import { insertLink, isMenuDisabled } from './helper' function withLink<T extends IDomEditor>(editor: T): T { - const { isInline, insertData, normalizeNode, insertNode, insertText } = editor + const { + isInline, insertData, normalizeNode, insertNode, insertText, + } = editor const newEditor = editor // 重写 isInline @@ -26,6 +29,7 @@ function withLink<T extends IDomEditor>(editor: T): T { // 重写 insertData ,粘贴插入链接 newEditor.insertData = (data: DataTransfer) => { const text = data.getData('text/plain') + if (!isUrl(text)) { // 非链接 insertData(data) @@ -38,15 +42,18 @@ function withLink<T extends IDomEditor>(editor: T): T { } // 插入链接 - if (isMenuDisabled(newEditor)) return // disabled + if (isMenuDisabled(newEditor)) { return } // disabled const { selection } = newEditor - if (selection == null) return + + if (selection == null) { return } const selectedText = Editor.string(newEditor, selection) // 获取选中的文字 + insertLink(newEditor, selectedText, text) } newEditor.normalizeNode = ([node, path]) => { const type = DomEditor.getNodeType(node) + if (type !== 'link') { // 未命中 link ,执行默认的 normalizeNode return normalizeNode([node, path]) @@ -54,6 +61,7 @@ function withLink<T extends IDomEditor>(editor: T): T { // 如果链接内容为空,则删除 const str = Node.string(node) + if (str === '') { return Transforms.removeNodes(newEditor, { at: path }) } diff --git a/packages/basic-modules/src/modules/link/render-elem.tsx b/packages/basic-modules/src/modules/link/render-elem.tsx index 1911bf513..1adce7a3d 100644 --- a/packages/basic-modules/src/modules/link/render-elem.tsx +++ b/packages/basic-modules/src/modules/link/render-elem.tsx @@ -3,9 +3,10 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor-next/core' + import { LinkElement } from './custom-types' /** diff --git a/packages/basic-modules/src/modules/paragraph/custom-types.ts b/packages/basic-modules/src/modules/paragraph/custom-types.ts index 950019573..4f966c9d1 100644 --- a/packages/basic-modules/src/modules/paragraph/custom-types.ts +++ b/packages/basic-modules/src/modules/paragraph/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type ParagraphElement = { type: 'paragraph' diff --git a/packages/basic-modules/src/modules/paragraph/index.ts b/packages/basic-modules/src/modules/paragraph/index.ts index 6c8a87667..4cf250ac1 100644 --- a/packages/basic-modules/src/modules/paragraph/index.ts +++ b/packages/basic-modules/src/modules/paragraph/index.ts @@ -4,10 +4,11 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import { renderParagraphConf } from './render-elem' + import { pToHtmlConf } from './elem-to-html' import { parseParagraphHtmlConf } from './parse-elem-html' import withParagraph from './plugin' +import { renderParagraphConf } from './render-elem' const p: Partial<IModuleConf> = { renderElems: [renderParagraphConf], diff --git a/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts b/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts index 50eb8d948..0b9771ace 100644 --- a/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/paragraph/parse-elem-html.ts @@ -3,21 +3,22 @@ * @author wangfupeng */ -import { Descendant, Text } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { ParagraphElement } from './custom-types' +import { Descendant, Text } from 'slate' + import $, { DOMElement } from '../../utils/dom' +import { ParagraphElement } from './custom-types' function parseParagraphHtml( elem: DOMElement, children: Descendant[], - editor: IDomEditor + editor: IDomEditor, ): ParagraphElement { const $elem = $(elem) children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true + if (Text.isText(child)) { return true } + if (editor.isInline(child)) { return true } return false }) diff --git a/packages/basic-modules/src/modules/paragraph/plugin.ts b/packages/basic-modules/src/modules/paragraph/plugin.ts index 110979ae3..572258bdd 100644 --- a/packages/basic-modules/src/modules/paragraph/plugin.ts +++ b/packages/basic-modules/src/modules/paragraph/plugin.ts @@ -3,29 +3,32 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Editor, Element as SlateElement, - Transforms, Node as SlateNode, Text as SlateText, + Transforms, } from 'slate' -import { IDomEditor } from '@wangeditor-next/core' function deleteHandler(newEditor: IDomEditor): boolean { const [nodeEntry] = Editor.nodes(newEditor, { match: n => newEditor.children[0] === n, // editor 第一个节点 mode: 'highest', // 最高层级 }) - if (nodeEntry == null) return false + + if (nodeEntry == null) { return false } const n = nodeEntry[0] - if (!SlateElement.isElement(n)) return false - if (n.type === 'paragraph') return false // 命中了 paragraph ,则不再继续判断 - if (SlateNode.string(n) !== '') return false // 未删除全部内容,则不再继续判断 + + if (!SlateElement.isElement(n)) { return false } + if (n.type === 'paragraph') { return false } // 命中了 paragraph ,则不再继续判断 + if (SlateNode.string(n) !== '') { return false } // 未删除全部内容,则不再继续判断 const { children = [] } = n - if (!SlateText.isText(children[0])) return false // n.children 不是 text (如 table),则不再继续判断 + + if (!SlateText.isText(children[0])) { return false } // n.children 不是 text (如 table),则不再继续判断 // 至此,就命中了一个(非 paragraph)+(children 都是 text)+(内容为空)的顶级 node ,如 header blockQuote 等 // 然后,将其却换为 paragraph @@ -36,20 +39,24 @@ function deleteHandler(newEditor: IDomEditor): boolean { } function withParagraph<T extends IDomEditor>(editor: T): T { - const { deleteBackward, deleteForward, insertText, insertBreak } = editor + const { + deleteBackward, deleteForward, insertText, insertBreak, + } = editor const newEditor = editor // 删除非 p 的文本 elem(如 header blockQuote 等),删除没有内容时,切换为 p newEditor.deleteBackward = unit => { const res = deleteHandler(newEditor) - if (res) return // 命中结果,则 return + + if (res) { return } // 命中结果,则 return // 执行默认的删除 deleteBackward(unit) } newEditor.deleteForward = unit => { const res = deleteHandler(newEditor) - if (res) return // 命中结果,则 return + + if (res) { return } // 命中结果,则 return // 执行默认的删除 deleteForward(unit) diff --git a/packages/basic-modules/src/modules/paragraph/render-elem.tsx b/packages/basic-modules/src/modules/paragraph/render-elem.tsx index c24384b7c..8a1f217e7 100644 --- a/packages/basic-modules/src/modules/paragraph/render-elem.tsx +++ b/packages/basic-modules/src/modules/paragraph/render-elem.tsx @@ -3,9 +3,9 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor-next/core' /** * render paragraph elem @@ -17,9 +17,10 @@ import { IDomEditor } from '@wangeditor-next/core' function renderParagraph( elemNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { const vnode = <p>{children}</p> + return vnode } diff --git a/packages/basic-modules/src/modules/text-style/custom-types.ts b/packages/basic-modules/src/modules/text-style/custom-types.ts index 30ee06a65..6d94a77a1 100644 --- a/packages/basic-modules/src/modules/text-style/custom-types.ts +++ b/packages/basic-modules/src/modules/text-style/custom-types.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -//【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Text 引入到最外层的 custom-types.d.ts export type StyledText = { text: string diff --git a/packages/basic-modules/src/modules/text-style/helper.ts b/packages/basic-modules/src/modules/text-style/helper.ts index a2a31b772..59c4fe391 100644 --- a/packages/basic-modules/src/modules/text-style/helper.ts +++ b/packages/basic-modules/src/modules/text-style/helper.ts @@ -3,18 +3,18 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Editor, Node } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' export function isMenuDisabled(editor: IDomEditor, mark?: string): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const [match] = Editor.nodes(editor, { match: n => { const type = DomEditor.getNodeType(n) - if (type === 'pre') return true // 代码块 - if (Editor.isVoid(editor, n)) return true // void node + if (type === 'pre') { return true } // 代码块 + if (Editor.isVoid(editor, n)) { return true } // void node return false }, @@ -22,13 +22,14 @@ export function isMenuDisabled(editor: IDomEditor, mark?: string): boolean { }) // 命中,则禁用 - if (match) return true + if (match) { return true } return false } export function removeMarks(editor: IDomEditor, textNode: Node) { // 遍历 text node 属性,清除样式 const keys = Object.keys(textNode as object) + keys.forEach(key => { if (key === 'text') { // 保留 text 属性,text node 必须的 diff --git a/packages/basic-modules/src/modules/text-style/index.ts b/packages/basic-modules/src/modules/text-style/index.ts index f902010ee..b4e496bc6 100644 --- a/packages/basic-modules/src/modules/text-style/index.ts +++ b/packages/basic-modules/src/modules/text-style/index.ts @@ -4,19 +4,20 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import { renderStyle } from './render-style' -import { styleToHtml } from './style-to-html' -import { parseStyleHtml } from './parse-style-html' + import { boldMenuConf, - underlineMenuConf, - italicMenuConf, - throughMenuConf, + clearStyleMenuConf, codeMenuConf, + italicMenuConf, subMenuConf, supMenuConf, - clearStyleMenuConf, + throughMenuConf, + underlineMenuConf, } from './menu/index' +import { parseStyleHtml } from './parse-style-html' +import { renderStyle } from './render-style' +import { styleToHtml } from './style-to-html' const textStyle: Partial<IModuleConf> = { renderStyle, diff --git a/packages/basic-modules/src/modules/text-style/menu/BaseMenu.ts b/packages/basic-modules/src/modules/text-style/menu/BaseMenu.ts index 5d09cdc8f..4f03b33e2 100644 --- a/packages/basic-modules/src/modules/text-style/menu/BaseMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/BaseMenu.ts @@ -3,16 +3,22 @@ * @author wangfupeng */ -import { Editor } from 'slate' import { IButtonMenu, IDomEditor } from '@wangeditor-next/core' +import { Editor } from 'slate' + import { isMenuDisabled } from '../helper' abstract class BaseMenu implements IButtonMenu { abstract readonly mark: string + protected readonly marksNeedToRemove: string[] = [] // 增加 mark 的同时,需要移除哪些 mark (互斥,不能共存的) + abstract readonly title: string + abstract readonly iconSvg: string + abstract readonly hotkey: string + readonly tag = 'button' /** @@ -26,17 +32,19 @@ abstract class BaseMenu implements IButtonMenu { // 当 curMarks 存在时,说明用户手动设置,以 curMarks 为准 if (curMarks) { return curMarks[mark] - } else { - const [match] = Editor.nodes(editor, { - // @ts-ignore - match: n => n[mark] === true, - }) - return !!match } + const [match] = Editor.nodes(editor, { + // @ts-ignore + match: n => n[mark] === true, + }) + + return !!match + } isActive(editor: IDomEditor): boolean { const isMark = this.getValue(editor) + return !!isMark } @@ -51,6 +59,7 @@ abstract class BaseMenu implements IButtonMenu { */ exec(editor: IDomEditor, value: string | boolean) { const { mark, marksNeedToRemove } = this + if (value) { // 已,则取消 editor.removeMark(mark) diff --git a/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts b/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts index f18520d4f..7846b6488 100644 --- a/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/BoldMenu.ts @@ -4,13 +4,17 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { BOLD_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class BoldMenu extends BaseMenu { readonly mark = 'bold' + readonly title = t('textStyle.bold') + readonly iconSvg = BOLD_SVG + readonly hotkey = 'mod+b' } diff --git a/packages/basic-modules/src/modules/text-style/menu/ClearStyleMenu.ts b/packages/basic-modules/src/modules/text-style/menu/ClearStyleMenu.ts index b2a86d81a..82644dd8c 100644 --- a/packages/basic-modules/src/modules/text-style/menu/ClearStyleMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/ClearStyleMenu.ts @@ -3,14 +3,17 @@ * @author wangfupeng */ -import { Editor, Text } from 'slate' import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' +import { Editor, Text } from 'slate' + import { ERASER_SVG } from '../../../constants/icon-svg' import { isMenuDisabled, removeMarks } from '../helper' class ClearStyleMenu implements IButtonMenu { readonly title = t('textStyle.clear') + readonly iconSvg = ERASER_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -36,9 +39,11 @@ class ClearStyleMenu implements IButtonMenu { match: n => Text.isText(n), universal: true, }) + for (const nodeEntry of nodeEntries) { // 单个 text node const n = nodeEntry[0] + removeMarks(editor, n) } } diff --git a/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts b/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts index 651e0e8ca..3bc5afcb5 100644 --- a/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/CodeMenu.ts @@ -4,13 +4,17 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { CODE_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class CodeMenu extends BaseMenu { readonly mark = 'code' + readonly title = t('textStyle.code') + readonly iconSvg = CODE_SVG + readonly hotkey = 'mod+e' } diff --git a/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts b/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts index 4abc857f6..beb1f4231 100644 --- a/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/ItalicMenu.ts @@ -4,13 +4,17 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { ITALIC_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class ItalicMenu extends BaseMenu { readonly mark = 'italic' + readonly title = t('textStyle.italic') + readonly iconSvg = ITALIC_SVG + readonly hotkey = 'mod+i' } diff --git a/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts b/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts index 1406d17b6..6aa883fac 100644 --- a/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/SubMenu.ts @@ -4,14 +4,19 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { SUB_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class SubMenu extends BaseMenu { readonly mark = 'sub' + readonly marksNeedToRemove = ['sup'] // sub 和 sup 不能共存 + readonly title = t('textStyle.sub') + readonly iconSvg = SUB_SVG + readonly hotkey = '' } diff --git a/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts b/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts index 91aa8b5d8..6dae6bf2b 100644 --- a/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/SupMenu.ts @@ -4,14 +4,19 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { SUP_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class SupMenu extends BaseMenu { readonly mark = 'sup' + readonly marksNeedToRemove = ['sub'] // sup 和 sub 不能共存 + readonly title = t('textStyle.sup') + readonly iconSvg = SUP_SVG + readonly hotkey = '' } diff --git a/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts b/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts index eafbb71f4..97f9ebe29 100644 --- a/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/ThroughMenu.ts @@ -4,13 +4,17 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { THROUGH_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class ThroughMenu extends BaseMenu { readonly mark = 'through' + readonly title = t('textStyle.through') + readonly iconSvg = THROUGH_SVG + readonly hotkey = 'mod+shift+x' } diff --git a/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts b/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts index f08b7a805..5ba670213 100644 --- a/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts +++ b/packages/basic-modules/src/modules/text-style/menu/UnderlineMenu.ts @@ -4,13 +4,17 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { UNDER_LINE_SVG } from '../../../constants/icon-svg' +import BaseMenu from './BaseMenu' class UnderlineMenu extends BaseMenu { readonly mark = 'underline' + readonly title = t('textStyle.underline') + readonly iconSvg = UNDER_LINE_SVG + readonly hotkey = 'mod+u' } diff --git a/packages/basic-modules/src/modules/text-style/menu/index.ts b/packages/basic-modules/src/modules/text-style/menu/index.ts index 5813c568f..5ca17f52e 100644 --- a/packages/basic-modules/src/modules/text-style/menu/index.ts +++ b/packages/basic-modules/src/modules/text-style/menu/index.ts @@ -4,13 +4,13 @@ */ import BoldMenu from './BoldMenu' +import ClearStyleMenu from './ClearStyleMenu' import CodeMenu from './CodeMenu' import ItalicMenu from './ItalicMenu' -import ThroughMenu from './ThroughMenu' -import UnderlineMenu from './UnderlineMenu' import SubMenu from './SubMenu' import SupMenu from './SupMenu' -import ClearStyleMenu from './ClearStyleMenu' +import ThroughMenu from './ThroughMenu' +import UnderlineMenu from './UnderlineMenu' export const boldMenuConf = { key: 'bold', diff --git a/packages/basic-modules/src/modules/text-style/parse-style-html.ts b/packages/basic-modules/src/modules/text-style/parse-style-html.ts index 6d00617b5..3718d9270 100644 --- a/packages/basic-modules/src/modules/text-style/parse-style-html.ts +++ b/packages/basic-modules/src/modules/text-style/parse-style-html.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ -import { Descendant, Text } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { StyledText } from './custom-types' +import { Descendant, Text } from 'slate' + import $, { Dom7Array, DOMElement } from '../../utils/dom' +import { StyledText } from './custom-types' /** * $text 是否匹配 tags @@ -14,11 +15,11 @@ import $, { Dom7Array, DOMElement } from '../../utils/dom' * @param selector selector 如 'b,strong' 或 'sub' */ function isMatch($text: Dom7Array, selector: string): boolean { - if ($text.length === 0) return false + if ($text.length === 0) { return false } - if ($text[0].matches(selector)) return true + if ($text[0].matches(selector)) { return true } - if ($text.find(selector).length > 0) return true + if ($text.find(selector).length > 0) { return true } return false } @@ -26,11 +27,11 @@ function isMatch($text: Dom7Array, selector: string): boolean { export function parseStyleHtml( textElem: DOMElement, node: Descendant, - editor: IDomEditor + editor: IDomEditor, ): Descendant { const $text = $(textElem) - if (!Text.isText(node)) return node + if (!Text.isText(node)) { return node } const textNode = node as StyledText diff --git a/packages/basic-modules/src/modules/text-style/render-style.tsx b/packages/basic-modules/src/modules/text-style/render-style.tsx index f0efd0dbb..8dffc8a9d 100644 --- a/packages/basic-modules/src/modules/text-style/render-style.tsx +++ b/packages/basic-modules/src/modules/text-style/render-style.tsx @@ -5,6 +5,7 @@ import { Descendant } from 'slate' import { jsx, VNode } from 'snabbdom' + import { StyledText } from './custom-types' /** @@ -14,7 +15,9 @@ import { StyledText } from './custom-types' * @returns vnode */ export function renderStyle(node: Descendant, vnode: VNode): VNode { - const { bold, italic, underline, code, through, sub, sup } = node as StyledText + const { + bold, italic, underline, code, through, sub, sup, + } = node as StyledText let styleVnode: VNode = vnode // color bgColor 在另外的菜单 diff --git a/packages/basic-modules/src/modules/text-style/style-to-html.ts b/packages/basic-modules/src/modules/text-style/style-to-html.ts index bdf385c9a..8f65acde4 100644 --- a/packages/basic-modules/src/modules/text-style/style-to-html.ts +++ b/packages/basic-modules/src/modules/text-style/style-to-html.ts @@ -3,11 +3,12 @@ * @author wangfupeng */ -import { Text, Descendant } from 'slate' -import { StyledText } from './custom-types' +import { Descendant, Text } from 'slate' + import $, { getOuterHTML, getTagName, isPlainText } from '../../utils/dom' +import { StyledText } from './custom-types' -//【注意】color bgColor fontSize fontFamily 在另外的菜单 +// 【注意】color bgColor fontSize fontFamily 在另外的菜单 /** * 生成加了样式的 text html @@ -16,14 +17,17 @@ import $, { getOuterHTML, getTagName, isPlainText } from '../../utils/dom' */ function genStyledHtml(textNode: Descendant, html: string): string { let styledHtml = html - const { bold, italic, underline, code, through, sub, sup } = textNode as StyledText - if (bold) styledHtml = `<strong>${styledHtml}</strong>` - if (code) styledHtml = `<code>${styledHtml}</code>` - if (italic) styledHtml = `<em>${styledHtml}</em>` - if (underline) styledHtml = `<u>${styledHtml}</u>` - if (through) styledHtml = `<s>${styledHtml}</s>` - if (sub) styledHtml = `<sub>${styledHtml}</sub>` - if (sup) styledHtml = `<sup>${styledHtml}</sup>` + const { + bold, italic, underline, code, through, sub, sup, + } = textNode as StyledText + + if (bold) { styledHtml = `<strong>${styledHtml}</strong>` } + if (code) { styledHtml = `<code>${styledHtml}</code>` } + if (italic) { styledHtml = `<em>${styledHtml}</em>` } + if (underline) { styledHtml = `<u>${styledHtml}</u>` } + if (through) { styledHtml = `<s>${styledHtml}</s>` } + if (sub) { styledHtml = `<sub>${styledHtml}</sub>` } + if (sup) { styledHtml = `<sup>${styledHtml}</sup>` } return styledHtml } @@ -34,7 +38,7 @@ function genStyledHtml(textNode: Descendant, html: string): string { * @returns styled html */ export function styleToHtml(textNode: Descendant, textHtml: string): string { - if (!Text.isText(textNode)) return textHtml + if (!Text.isText(textNode)) { return textHtml } if (isPlainText(textHtml)) { // textHtml 是纯文本,而不是 html tag @@ -45,6 +49,7 @@ export function styleToHtml(textNode: Descendant, textHtml: string): string { const $text = $(textHtml) let innerHtml = $text.html() + innerHtml = genStyledHtml(textNode, innerHtml) $text.html(innerHtml) return getOuterHTML($text) diff --git a/packages/basic-modules/src/modules/todo/custom-types.ts b/packages/basic-modules/src/modules/todo/custom-types.ts index 32cc97d0b..2843964fd 100644 --- a/packages/basic-modules/src/modules/todo/custom-types.ts +++ b/packages/basic-modules/src/modules/todo/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type TodoElement = { type: 'todo' diff --git a/packages/basic-modules/src/modules/todo/elem-to-html.ts b/packages/basic-modules/src/modules/todo/elem-to-html.ts index 4a255ba63..f029d0c71 100644 --- a/packages/basic-modules/src/modules/todo/elem-to-html.ts +++ b/packages/basic-modules/src/modules/todo/elem-to-html.ts @@ -4,11 +4,13 @@ */ import { Element } from 'slate' + import { TodoElement } from './custom-types' function todoToHtml(elem: Element, childrenHtml: string): string { const { checked } = elem as TodoElement const checkedAttr = checked ? 'checked' : '' + return `<div data-w-e-type="todo"><input type="checkbox" disabled ${checkedAttr}>${childrenHtml}</div>` } diff --git a/packages/basic-modules/src/modules/todo/index.ts b/packages/basic-modules/src/modules/todo/index.ts index 301e20e42..a67713759 100644 --- a/packages/basic-modules/src/modules/todo/index.ts +++ b/packages/basic-modules/src/modules/todo/index.ts @@ -4,12 +4,13 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import { renderTodoConf } from './render-elem' -import withTodo from './plugin' -import { todoMenuConf } from './menu/index' + import { todoToHtmlConf } from './elem-to-html' +import { todoMenuConf } from './menu/index' import { parseHtmlConf } from './parse-elem-html' +import withTodo from './plugin' import { preParseHtmlConf } from './pre-parse-html' +import { renderTodoConf } from './render-elem' const todo: Partial<IModuleConf> = { renderElems: [renderTodoConf], diff --git a/packages/basic-modules/src/modules/todo/menu/Todo.ts b/packages/basic-modules/src/modules/todo/menu/Todo.ts index f44746310..fa6811ad1 100644 --- a/packages/basic-modules/src/modules/todo/menu/Todo.ts +++ b/packages/basic-modules/src/modules/todo/menu/Todo.ts @@ -3,13 +3,18 @@ * @author wangfupeng */ +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' import { Editor, Element, Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' + import { CHECK_BOX_SVG } from '../../../constants/icon-svg' class TodoMenu implements IButtonMenu { readonly title = t('todo.todo') + readonly iconSvg = CHECK_BOX_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -22,22 +27,25 @@ class TodoMenu implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const selectedElems = DomEditor.getSelectedElems(editor) const notMatch = selectedElems.some((elem: Element) => { - if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem)) return true + if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem)) { return true } const { type } = elem as Element - if (['pre', 'table', 'list-item'].includes(type)) return true + + if (['pre', 'table', 'list-item'].includes(type)) { return true } }) - if (notMatch) return true + + if (notMatch) { return true } return false } exec(editor: IDomEditor, value: string | boolean) { const active = this.isActive(editor) + Transforms.setNodes(editor, { type: active ? 'paragraph' : 'todo' }) } } diff --git a/packages/basic-modules/src/modules/todo/parse-elem-html.ts b/packages/basic-modules/src/modules/todo/parse-elem-html.ts index 5944b3771..4e16b972a 100644 --- a/packages/basic-modules/src/modules/todo/parse-elem-html.ts +++ b/packages/basic-modules/src/modules/todo/parse-elem-html.ts @@ -3,17 +3,18 @@ * @author wangfupeng */ -import { Descendant, Text } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { TodoElement } from './custom-types' +import { Descendant, Text } from 'slate' + import $, { DOMElement } from '../../utils/dom' +import { TodoElement } from './custom-types' function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor): TodoElement { const $elem = $(elem) children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true + if (Text.isText(child)) { return true } + if (editor.isInline(child)) { return true } return false }) @@ -25,6 +26,7 @@ function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor) // 获取 checked let checked = false const $input = $elem.find('input[type="checkbox"]') + if ($input.attr('checked') != null) { checked = true } diff --git a/packages/basic-modules/src/modules/todo/plugin.ts b/packages/basic-modules/src/modules/todo/plugin.ts index 5af330ea9..e8e0b2ac6 100644 --- a/packages/basic-modules/src/modules/todo/plugin.ts +++ b/packages/basic-modules/src/modules/todo/plugin.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import { Node, Transforms, Range } from 'slate' import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { Node, Range, Transforms } from 'slate' function withTodo<T extends IDomEditor>(editor: T): T { const { deleteBackward } = editor @@ -19,6 +19,7 @@ function withTodo<T extends IDomEditor>(editor: T): T { if (selection && Range.isCollapsed(selection)) { // 获取选中的 todo const selectedTodo = DomEditor.getSelectedNodeByType(editor, 'todo') + if (selectedTodo) { if (Node.string(selectedTodo).length === 0) { // 当前 todo 已经没有文字,则转换为 paragraph diff --git a/packages/basic-modules/src/modules/todo/pre-parse-html.ts b/packages/basic-modules/src/modules/todo/pre-parse-html.ts index 86e6469e0..b1de7c268 100644 --- a/packages/basic-modules/src/modules/todo/pre-parse-html.ts +++ b/packages/basic-modules/src/modules/todo/pre-parse-html.ts @@ -20,10 +20,12 @@ function preParse(elem: DOMElement): DOMElement { // 1. 把 input 移动到 $container const $input = $li.find('input[type]') + $container.append($input) // 2. 删除之前包裹 input 的 span const $spanForInput = $li.children()[0] + $spanForInput.remove() // 3. 再把剩余的内容移动到 $container (有纯文本内容,不能用 children ,得用 innerHTML) diff --git a/packages/basic-modules/src/modules/todo/render-elem.tsx b/packages/basic-modules/src/modules/todo/render-elem.tsx index ea712a125..17ad91db1 100644 --- a/packages/basic-modules/src/modules/todo/render-elem.tsx +++ b/packages/basic-modules/src/modules/todo/render-elem.tsx @@ -3,9 +3,10 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement, Transforms } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' + import { TodoElement } from './custom-types' /** @@ -18,7 +19,8 @@ import { TodoElement } from './custom-types' function renderTodo(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode { // 判断 disabled let disabled = false - if (editor.isDisabled()) disabled = true + + if (editor.isDisabled()) { disabled = true } const { checked } = elemNode as TodoElement const vnode = ( @@ -35,6 +37,7 @@ function renderTodo(elemNode: SlateElement, children: VNode[] | null, editor: ID // @ts-ignore checked: event.target.checked, } + Transforms.setNodes(editor, newProps, { at: path }) }, }} diff --git a/packages/basic-modules/src/modules/undo-redo/index.ts b/packages/basic-modules/src/modules/undo-redo/index.ts index 9859436f8..e166b6965 100644 --- a/packages/basic-modules/src/modules/undo-redo/index.ts +++ b/packages/basic-modules/src/modules/undo-redo/index.ts @@ -4,6 +4,7 @@ */ import { IModuleConf } from '@wangeditor-next/core' + import { redoMenuConf, undoMenuConf } from './menu/index' const undoRedo: Partial<IModuleConf> = { diff --git a/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts b/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts index 216f6bf04..666325c44 100644 --- a/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts +++ b/packages/basic-modules/src/modules/undo-redo/menu/RedoMenu.ts @@ -4,11 +4,14 @@ */ import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' + import { REDO_SVG } from '../../../constants/icon-svg' class RedoMenu implements IButtonMenu { title = t('undo.redo') + iconSvg = REDO_SVG + tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -20,7 +23,7 @@ class RedoMenu implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } return false } diff --git a/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts b/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts index ba5a7045c..2776e97a8 100644 --- a/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts +++ b/packages/basic-modules/src/modules/undo-redo/menu/UndoMenu.ts @@ -4,11 +4,14 @@ */ import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' + import { UNDO_SVG } from '../../../constants/icon-svg' class UndoMenu implements IButtonMenu { title = t('undo.undo') + iconSvg = UNDO_SVG + tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -20,7 +23,7 @@ class UndoMenu implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } return false } diff --git a/packages/basic-modules/src/utils/dom.ts b/packages/basic-modules/src/utils/dom.ts index 45d61ecab..6381df0ac 100644 --- a/packages/basic-modules/src/utils/dom.ts +++ b/packages/basic-modules/src/utils/dom.ts @@ -4,60 +4,72 @@ */ import $, { - css, - append, - prepend, addClass, - removeClass, - hasClass, - on, - off, - focus, + append, attr, - hide, - show, - parents, - dataset, - val, - text, - removeAttr, children, + css, + dataset, + Dom7Array, + empty, + filter, + find, + focus, + hasClass, + height, + hide, html, + off, + on, + parents, + prepend, remove, - find, + removeAttr, + removeClass, + show, + text, + val, width, - height, - Dom7Array, - filter, - empty, } from 'dom7' + +// COMPAT: This is required to prevent TypeScript aliases from doing some very +// weird things for Slate's types with the same name as globals. (2019/11/27) +// https://github.com/microsoft/TypeScript/issues/35002 +import DOMNode = globalThis.Node +import DOMComment = globalThis.Comment +import DOMElement = globalThis.Element +import DOMText = globalThis.Text +import DOMRange = globalThis.Range +import DOMSelection = globalThis.Selection +import DOMStaticRange = globalThis.StaticRange + export { Dom7Array } from 'dom7' -if (css) $.fn.css = css -if (append) $.fn.append = append -if (prepend) $.fn.prepend = prepend -if (addClass) $.fn.addClass = addClass -if (removeClass) $.fn.removeClass = removeClass -if (hasClass) $.fn.hasClass = hasClass -if (on) $.fn.on = on -if (off) $.fn.off = off -if (focus) $.fn.focus = focus -if (attr) $.fn.attr = attr -if (removeAttr) $.fn.removeAttr = removeAttr -if (hide) $.fn.hide = hide -if (show) $.fn.show = show -if (parents) $.fn.parents = parents -if (dataset) $.fn.dataset = dataset -if (val) $.fn.val = val -if (text) $.fn.text = text -if (html) $.fn.html = html -if (children) $.fn.children = children -if (remove) $.fn.remove = remove -if (find) $.fn.find = find -if (width) $.fn.width = width -if (height) $.fn.height = height -if (filter) $.fn.filter = filter -if (empty) $.fn.empty = empty +if (css) { $.fn.css = css } +if (append) { $.fn.append = append } +if (prepend) { $.fn.prepend = prepend } +if (addClass) { $.fn.addClass = addClass } +if (removeClass) { $.fn.removeClass = removeClass } +if (hasClass) { $.fn.hasClass = hasClass } +if (on) { $.fn.on = on } +if (off) { $.fn.off = off } +if (focus) { $.fn.focus = focus } +if (attr) { $.fn.attr = attr } +if (removeAttr) { $.fn.removeAttr = removeAttr } +if (hide) { $.fn.hide = hide } +if (show) { $.fn.show = show } +if (parents) { $.fn.parents = parents } +if (dataset) { $.fn.dataset = dataset } +if (val) { $.fn.val = val } +if (text) { $.fn.text = text } +if (html) { $.fn.html = html } +if (children) { $.fn.children = children } +if (remove) { $.fn.remove = remove } +if (find) { $.fn.find = find } +if (width) { $.fn.width = width } +if (height) { $.fn.height = height } +if (filter) { $.fn.filter = filter } +if (empty) { $.fn.empty = empty } export default $ @@ -70,7 +82,7 @@ export function isPlainText(str: string) { // 获取 children length (过滤 `<br>`) const childrenLength = $container.children().filter((child: DOMElement) => { - if (child.tagName === 'BR') return false + if (child.tagName === 'BR') { return false } return true }).length @@ -82,7 +94,7 @@ export function isPlainText(str: string) { * @param $elem dom7 elem */ export function getOuterHTML($elem: Dom7Array) { - if ($elem.length === 0) return '' + if ($elem.length === 0) { return '' } return $elem[0].outerHTML } @@ -91,7 +103,7 @@ export function getOuterHTML($elem: Dom7Array) { * @param $elem $elem */ export function getTagName($elem: Dom7Array): string { - if ($elem.length) return $elem[0].tagName.toLowerCase() + if ($elem.length) { return $elem[0].tagName.toLowerCase() } return '' } @@ -106,10 +118,13 @@ export function getStyleValue($elem: Dom7Array, styleKey: string): string { const styleStr = $elem.attr('style') || '' // 如 'line-height: 2.5; color: red;' const styleArr = styleStr.split(';') // 如 ['line-height: 2.5', ' color: red', ''] const length = styleArr.length + for (let i = 0; i < length; i++) { const styleItemStr = styleArr[i] // 如 'line-height: 2.5' + if (styleItemStr) { const arr = styleItemStr.split(':') // ['line-height', ' 2.5'] + if (arr[0].trim() === styleKey) { res = arr[1].trim() } @@ -119,14 +134,6 @@ export function getStyleValue($elem: Dom7Array, styleKey: string): string { return res } -// COMPAT: This is required to prevent TypeScript aliases from doing some very -// weird things for Slate's types with the same name as globals. (2019/11/27) -// https://github.com/microsoft/TypeScript/issues/35002 -import DOMNode = globalThis.Node -import DOMComment = globalThis.Comment -import DOMElement = globalThis.Element -import DOMText = globalThis.Text -import DOMRange = globalThis.Range -import DOMSelection = globalThis.Selection -import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } +export { + DOMComment, DOMElement, DOMNode, DOMRange, DOMSelection, DOMStaticRange, DOMText, +} diff --git a/packages/basic-modules/src/utils/util.ts b/packages/basic-modules/src/utils/util.ts index 298b3eadc..ece7702fc 100644 --- a/packages/basic-modules/src/utils/util.ts +++ b/packages/basic-modules/src/utils/util.ts @@ -10,7 +10,7 @@ import { nanoid } from 'nanoid' * @param prefix 前缀 * @returns 随机数字符串 */ -export function genRandomStr(prefix: string = 'r'): string { +export function genRandomStr(prefix = 'r'): string { return `${prefix}-${nanoid()}` } diff --git a/packages/basic-modules/src/utils/vdom.ts b/packages/basic-modules/src/utils/vdom.ts index 26808c117..bbd35048c 100644 --- a/packages/basic-modules/src/utils/vdom.ts +++ b/packages/basic-modules/src/utils/vdom.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -import { VNode, VNodeStyle, Dataset } from 'snabbdom' +import { Dataset, VNode, VNodeStyle } from 'snabbdom' // /** // * 给 vnode 添加 dataset @@ -24,9 +24,10 @@ import { VNode, VNodeStyle, Dataset } from 'snabbdom' * @param newStyle { key: val } */ export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) { - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } const data = vnode.data - if (data.style == null) data.style = {} + + if (data.style == null) { data.style = {} } Object.assign(data.style, newStyle) } diff --git a/packages/code-highlight/__tests__/content.ts b/packages/code-highlight/__tests__/content.ts index bbd48d922..b34f282cf 100644 --- a/packages/code-highlight/__tests__/content.ts +++ b/packages/code-highlight/__tests__/content.ts @@ -5,7 +5,7 @@ export const text = 'const a = 100;' -export const textNode = { text: text } +export const textNode = { text } export const language = 'javascript' diff --git a/packages/code-highlight/__tests__/decorate.test.ts b/packages/code-highlight/__tests__/decorate.test.ts index a34b4efc7..cc534ff63 100644 --- a/packages/code-highlight/__tests__/decorate.test.ts +++ b/packages/code-highlight/__tests__/decorate.test.ts @@ -4,10 +4,11 @@ */ import { IDomEditor } from '@wangeditor-next/core' + import createEditor from '../../../tests/utils/create-editor' import codeHighLightDecorate from '../src/decorate/index' -import { content, textNode, textNodePath } from './content' import { getPrismTokenLength } from '../src/vendor/prism' +import { content, textNode, textNodePath } from './content' describe('code-highlight decorate', () => { let editor: IDomEditor | null = null @@ -21,13 +22,14 @@ describe('code-highlight decorate', () => { afterAll(() => { // 销毁 editor - if (editor == null) return + if (editor == null) { return } editor.destroy() editor = null }) it('code-highlight decorate 拆分代码字符串', () => { const ranges = codeHighLightDecorate([textNode, textNodePath]) + expect(ranges.length).toBe(4) // 把 textNode 内容拆分为 4 段 }) @@ -42,6 +44,7 @@ describe('code-highlight decorate', () => { } const result = getPrismTokenLength(token) + expect(result).toBe(16) // 'hello' (5) + 'world' (5) + 'foo' (3) + 'bar' (3) = 16 }) }) diff --git a/packages/code-highlight/__tests__/elem-to-html.test.ts b/packages/code-highlight/__tests__/elem-to-html.test.ts index 0ac576ab9..e30f561b6 100644 --- a/packages/code-highlight/__tests__/elem-to-html.test.ts +++ b/packages/code-highlight/__tests__/elem-to-html.test.ts @@ -4,9 +4,12 @@ */ import { IDomEditor } from '@wangeditor-next/core' + import createEditor from '../../../tests/utils/create-editor' import { codeToHtmlConf } from '../src/module/elem-to-html' -import { content, codeNode, preNode, language } from './content' +import { + codeNode, content, language, preNode, +} from './content' describe('code-highlight elem to html', () => { let editor: IDomEditor | null = null @@ -20,7 +23,7 @@ describe('code-highlight elem to html', () => { afterAll(() => { // 销毁 editor - if (editor == null) return + if (editor == null) { return } editor.destroy() editor = null }) @@ -28,9 +31,10 @@ describe('code-highlight elem to html', () => { it('codeNode to html', () => { expect(codeToHtmlConf.type).toBe('code') - if (editor == null) throw new Error('editor is null') + if (editor == null) { throw new Error('editor is null') } const text = 'var n = 100;' let html = codeToHtmlConf.elemToHtml(codeNode, text) + expect(html).toBe(`<code class="language-${language}">${text}</code>`) html = codeToHtmlConf.elemToHtml(preNode, text) expect(html).toBe(`<code >${text}</code>`) diff --git a/packages/code-highlight/__tests__/parse-html.test.ts b/packages/code-highlight/__tests__/parse-html.test.ts index 32af09b8e..50b044601 100644 --- a/packages/code-highlight/__tests__/parse-html.test.ts +++ b/packages/code-highlight/__tests__/parse-html.test.ts @@ -4,8 +4,9 @@ */ import { $ } from 'dom7' -import { parseCodeStyleHtml } from '../src/module/parse-style-html' + import createEditor from '../../../tests/utils/create-editor' +import { parseCodeStyleHtml } from '../src/module/parse-style-html' describe('code highlight - parse style html', () => { const editor = createEditor() @@ -15,6 +16,7 @@ describe('code highlight - parse style html', () => { const code = { type: 'code', children: [{ text: 'var a = 100;' }] } const res = parseCodeStyleHtml($code[0], code, editor) + expect(res).toEqual({ type: 'code', language: 'javascript', @@ -27,6 +29,7 @@ describe('code highlight - parse style html', () => { const code = { type: 'code', children: [{ text: 'var a = 100;' }] } const res = parseCodeStyleHtml($code[0], code, editor) + expect(res).toEqual({ type: 'code', language: 'javascript', diff --git a/packages/code-highlight/__tests__/render-text-style.test.tsx b/packages/code-highlight/__tests__/render-text-style.test.tsx index b3b1efa4c..eac1082f1 100644 --- a/packages/code-highlight/__tests__/render-text-style.test.tsx +++ b/packages/code-highlight/__tests__/render-text-style.test.tsx @@ -3,9 +3,10 @@ * @author wangfupeng */ -import { renderStyle } from '../src/module/render-style' import { jsx } from 'snabbdom' +import { renderStyle } from '../src/module/render-style' + describe('code-highlight render text style', () => { it('code text style', () => { const leafNode = { text: 'let', keyword: true } // 定义一个 keyword leaf text node @@ -13,6 +14,7 @@ describe('code-highlight render text style', () => { // @ts-ignore 忽略 vnode 格式检查 const newVnode = renderStyle(leafNode, vnode) + expect(newVnode.data?.props?.className).toBe('token keyword') }) }) diff --git a/packages/code-highlight/__tests__/select-lang-menu.test.ts b/packages/code-highlight/__tests__/select-lang-menu.test.ts index fdf1dfd18..bda3321fe 100644 --- a/packages/code-highlight/__tests__/select-lang-menu.test.ts +++ b/packages/code-highlight/__tests__/select-lang-menu.test.ts @@ -4,9 +4,12 @@ */ import { IDomEditor } from '@wangeditor-next/core' + import createEditor from '../../../tests/utils/create-editor' -import { content, codeLocation, paragraphLocation, language } from './content' import SelectLangMenu from '../src/module/menu/SelectLangMenu' +import { + codeLocation, content, language, paragraphLocation, +} from './content' describe('code-highlight select lang menu', () => { let editor: IDomEditor | null = null @@ -24,7 +27,7 @@ describe('code-highlight select lang menu', () => { afterAll(() => { // 销毁 editor - if (editor == null) return + if (editor == null) { return } editor.destroy() editor = null @@ -33,7 +36,7 @@ describe('code-highlight select lang menu', () => { }) it('get langs and selected one', () => { - if (editor == null || menu == null) throw new Error('editor or menu is null') + if (editor == null || menu == null) { throw new Error('editor or menu is null') } // select codeNode editor.select(codeLocation) @@ -45,23 +48,26 @@ describe('code-highlight select lang menu', () => { // 其中有一个 'plain text' const hasPlainText = langs.some(lang => lang.text === 'plain text' && lang.value === '') + expect(hasPlainText).toBeTruthy() // 选中的语言 const selectedLangs = langs.filter(lang => lang.selected) + expect(selectedLangs.length).toBe(1) const selectedLang: any = selectedLangs[0] || {} + expect(selectedLang.value).toBe(language) }) it('menu active is always false', () => { - if (editor == null || menu == null) throw new Error('editor or menu is null') + if (editor == null || menu == null) { throw new Error('editor or menu is null') } expect(menu.isActive(editor)).toBeFalsy() }) it('get menu value (selected lang)', () => { - if (editor == null || menu == null) throw new Error('editor or menu is null') + if (editor == null || menu == null) { throw new Error('editor or menu is null') } // select codeNode editor.select(codeLocation) @@ -73,7 +79,7 @@ describe('code-highlight select lang menu', () => { }) it('menu disable', () => { - if (editor == null || menu == null) throw new Error('editor or menu is null') + if (editor == null || menu == null) { throw new Error('editor or menu is null') } // deselect editor.deselect() @@ -89,14 +95,14 @@ describe('code-highlight select lang menu', () => { }) it('menu exec (change lang)', done => { - if (editor == null || menu == null) throw new Error('editor or menu is null') + if (editor == null || menu == null) { throw new Error('editor or menu is null') } // select codeNode editor.select(codeLocation) menu.exec(editor, 'html') // change lang setTimeout(() => { - if (editor == null || menu == null) return + if (editor == null || menu == null) { return } editor.select(codeLocation) expect(menu.getValue(editor)).toBe('html') @@ -105,14 +111,14 @@ describe('code-highlight select lang menu', () => { }) it('menu exec (without lang)', done => { - if (editor == null || menu == null) throw new Error('editor or menu is null') + if (editor == null || menu == null) { throw new Error('editor or menu is null') } // select codeNode editor.select(codeLocation) menu.exec(editor, 'hello') // change lang setTimeout(() => { - if (editor == null || menu == null) return + if (editor == null || menu == null) { return } editor.select(codeLocation) expect(menu.getValue(editor)).toBe('') diff --git a/packages/code-highlight/rollup.config.js b/packages/code-highlight/rollup.config.js index 636793c53..7a55f44cf 100644 --- a/packages/code-highlight/rollup.config.js +++ b/packages/code-highlight/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorCodeHighLight' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/code-highlight/src/constants/svg.ts b/packages/code-highlight/src/constants/svg.ts index 31b8b7132..ebd094afb 100644 --- a/packages/code-highlight/src/constants/svg.ts +++ b/packages/code-highlight/src/constants/svg.ts @@ -9,5 +9,4 @@ * 找不到再从 iconfont.com 搜索 */ -export const JS_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M64 64v896h896V64H64z m487.6 698.8c0 87.2-51.2 127-125.8 127-67.4 0-106.4-34.8-126.4-77l68.6-41.4c13.2 23.4 25.2 43.2 54.2 43.2 27.6 0 45.2-10.8 45.2-53V475.4h84.2v287.4z m199.2 127c-78.2 0-128.8-37.2-153.4-86l68.6-39.6c18 29.4 41.6 51.2 83 51.2 34.8 0 57.2-17.4 57.2-41.6 0-28.8-22.8-39-61.4-56l-21-9c-60.8-25.8-101-58.4-101-127 0-63.2 48.2-111.2 123.2-111.2 53.6 0 92 18.6 119.6 67.4L800 580c-14.4-25.8-30-36-54.2-36-24.6 0-40.2 15.6-40.2 36 0 25.2 15.6 35.4 51.8 51.2l21 9c71.6 30.6 111.8 62 111.8 132.4 0 75.6-59.6 117.2-139.4 117.2z"></path></svg>' +export const JS_SVG = '<svg viewBox="0 0 1024 1024"><path d="M64 64v896h896V64H64z m487.6 698.8c0 87.2-51.2 127-125.8 127-67.4 0-106.4-34.8-126.4-77l68.6-41.4c13.2 23.4 25.2 43.2 54.2 43.2 27.6 0 45.2-10.8 45.2-53V475.4h84.2v287.4z m199.2 127c-78.2 0-128.8-37.2-153.4-86l68.6-39.6c18 29.4 41.6 51.2 83 51.2 34.8 0 57.2-17.4 57.2-41.6 0-28.8-22.8-39-61.4-56l-21-9c-60.8-25.8-101-58.4-101-127 0-63.2 48.2-111.2 123.2-111.2 53.6 0 92 18.6 119.6 67.4L800 580c-14.4-25.8-30-36-54.2-36-24.6 0-40.2 15.6-40.2 36 0 25.2 15.6 35.4 51.8 51.2l21 9c71.6 30.6 111.8 62 111.8 132.4 0 75.6-59.6 117.2-139.4 117.2z"></path></svg>' diff --git a/packages/code-highlight/src/decorate/index.ts b/packages/code-highlight/src/decorate/index.ts index a7aaa2d05..94551dd0c 100644 --- a/packages/code-highlight/src/decorate/index.ts +++ b/packages/code-highlight/src/decorate/index.ts @@ -3,21 +3,26 @@ * @author wangfupeng */ -import { Node, NodeEntry, Range, Text } from 'slate' import { DomEditor } from '@wangeditor-next/core' -import { getPrismTokens, getPrismTokenLength } from '../vendor/prism' +import { + Node, NodeEntry, Range, Text, +} from 'slate' + import { CodeElement } from '../custom-types' +import { getPrismTokenLength, getPrismTokens } from '../vendor/prism' /** * 获取 code elem * @param node text node */ function getCodeElem(textNode: Node): CodeElement | null { - if (!Text.isText(textNode)) return null // 非文本 node + if (!Text.isText(textNode)) { return null } // 非文本 node const codeNode = DomEditor.getParentNode(null, textNode) + if (codeNode && DomEditor.getNodeType(codeNode) === 'code') { const preNode = DomEditor.getParentNode(null, codeNode) + if (preNode && DomEditor.getNodeType(preNode) === 'pre') { return codeNode as CodeElement } @@ -31,14 +36,17 @@ const codeHighLightDecorate = (nodeEntry: NodeEntry): Range[] => { // 节点不合法,则不处理 const codeElem = getCodeElem(n) - if (codeElem == null) return ranges + + if (codeElem == null) { return ranges } const { language = '' } = codeElem - if (!language) return ranges + + if (!language) { return ranges } const textNode = n as Text const tokens = getPrismTokens(textNode, language) let start = 0 + for (const token of tokens) { const length = getPrismTokenLength(token) const end = start + length diff --git a/packages/code-highlight/src/index.ts b/packages/code-highlight/src/index.ts index b2f121fcc..1745bd385 100644 --- a/packages/code-highlight/src/index.ts +++ b/packages/code-highlight/src/index.ts @@ -4,11 +4,10 @@ */ import './assets/index.less' - // 配置多语言 import './locale/index' -import wangEditorCodeHighlightModule from './module/index' import wangEditorCodeHighLightDecorate from './decorate' +import wangEditorCodeHighlightModule from './module/index' -export { wangEditorCodeHighlightModule, wangEditorCodeHighLightDecorate } +export { wangEditorCodeHighLightDecorate, wangEditorCodeHighlightModule } diff --git a/packages/code-highlight/src/locale/index.ts b/packages/code-highlight/src/locale/index.ts index f3b7049e9..120f32884 100644 --- a/packages/code-highlight/src/locale/index.ts +++ b/packages/code-highlight/src/locale/index.ts @@ -4,6 +4,7 @@ */ import { i18nAddResources } from '@wangeditor-next/core' + import enResources from './en' import zhResources from './zh-CN' diff --git a/packages/code-highlight/src/module/elem-to-html.ts b/packages/code-highlight/src/module/elem-to-html.ts index 0b88c88fd..2c9199f30 100644 --- a/packages/code-highlight/src/module/elem-to-html.ts +++ b/packages/code-highlight/src/module/elem-to-html.ts @@ -4,6 +4,7 @@ */ import { Element } from 'slate' + import { CodeElement } from '../custom-types' function codeToHtml(elem: Element, childrenHtml: string): string { diff --git a/packages/code-highlight/src/module/index.ts b/packages/code-highlight/src/module/index.ts index 6f6b6524d..b4aa0a9b4 100644 --- a/packages/code-highlight/src/module/index.ts +++ b/packages/code-highlight/src/module/index.ts @@ -4,10 +4,11 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import { renderStyle } from './render-style' -import { parseCodeStyleHtml } from './parse-style-html' -import { selectLangMenuConf } from './menu/index' + import { codeToHtmlConf } from './elem-to-html' +import { selectLangMenuConf } from './menu/index' +import { parseCodeStyleHtml } from './parse-style-html' +import { renderStyle } from './render-style' const codeHighlightModule: Partial<IModuleConf> = { renderStyle, diff --git a/packages/code-highlight/src/module/menu/SelectLangMenu.ts b/packages/code-highlight/src/module/menu/SelectLangMenu.ts index 61a0143bc..b6ae06a46 100644 --- a/packages/code-highlight/src/module/menu/SelectLangMenu.ts +++ b/packages/code-highlight/src/module/menu/SelectLangMenu.ts @@ -3,17 +3,25 @@ * @author wangfupeng */ -import { Transforms, Element } from 'slate' -import { ISelectMenu, IDomEditor, IOption, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IDomEditor, IOption, ISelectMenu, t, +} from '@wangeditor-next/core' +import { Element, Transforms } from 'slate' + import { JS_SVG } from '../../constants/svg' import { CodeElement } from '../../custom-types' class SelectLangMenu implements ISelectMenu { readonly title = t('highLightModule.selectLang') + readonly iconSvg = JS_SVG + readonly tag = 'select' + readonly width = 95 + readonly selectPanelWidth = 115 + private defaultCodeLang = '' getOptions(editor: IDomEditor): IOption[] { @@ -39,6 +47,7 @@ class SelectLangMenu implements ISelectMenu { // 设置 selected const curValue = this.getValue(editor) + options.forEach(opt => { if (opt.value === curValue) { opt.selected = true @@ -61,8 +70,9 @@ class SelectLangMenu implements ISelectMenu { */ getValue(editor: IDomEditor): string | boolean { const elem = this.getSelectCodeElem(editor) - if (elem == null) return this.defaultCodeLang - if (!Element.isElement(elem)) return this.defaultCodeLang + + if (elem == null) { return this.defaultCodeLang } + if (!Element.isElement(elem)) { return this.defaultCodeLang } const lang = elem.language.toString() @@ -70,23 +80,26 @@ class SelectLangMenu implements ISelectMenu { const { codeLangs = [] } = editor.getMenuConfig('codeSelectLang') const hasLang = codeLangs.some(item => item.value === lang) - if (hasLang) return lang + if (hasLang) { return lang } return this.defaultCodeLang } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const elem = this.getSelectCodeElem(editor) - if (elem) return false + + if (elem) { return false } return true } exec(editor: IDomEditor, value: string | boolean) { const elem = this.getSelectCodeElem(editor) - if (elem == null) return + + if (elem == null) { return } // 设置语言 const props: Partial<CodeElement> = { language: value.toString() } + Transforms.setNodes(editor, props, { match: n => DomEditor.checkNodeType(n, 'code'), }) @@ -94,10 +107,12 @@ class SelectLangMenu implements ISelectMenu { private getSelectCodeElem(editor: IDomEditor): CodeElement | null { const codeNode = DomEditor.getSelectedNodeByType(editor, 'code') - if (codeNode == null) return null + + if (codeNode == null) { return null } const preNode = DomEditor.getParentNode(editor, codeNode) - if (!Element.isElement(preNode)) return null - if (preNode.type !== 'pre') return null + + if (!Element.isElement(preNode)) { return null } + if (preNode.type !== 'pre') { return null } return codeNode as CodeElement } diff --git a/packages/code-highlight/src/module/menu/index.ts b/packages/code-highlight/src/module/menu/index.ts index 9043602b3..64e341ea7 100644 --- a/packages/code-highlight/src/module/menu/index.ts +++ b/packages/code-highlight/src/module/menu/index.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import SelectLangMenu from './SelectLangMenu' import { genCodeLangs } from './config' +import SelectLangMenu from './SelectLangMenu' export const selectLangMenuConf = { key: 'codeSelectLang', diff --git a/packages/code-highlight/src/module/parse-style-html.ts b/packages/code-highlight/src/module/parse-style-html.ts index 06239afc1..3be678386 100644 --- a/packages/code-highlight/src/module/parse-style-html.ts +++ b/packages/code-highlight/src/module/parse-style-html.ts @@ -3,24 +3,26 @@ * @author wangfupeng */ -import $, { DOMElement } from '../utils/dom' -import { Descendant, Element } from 'slate' import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { Descendant, Element } from 'slate' + import { CodeElement } from '../custom-types' +import $, { DOMElement } from '../utils/dom' export function parseCodeStyleHtml( elem: DOMElement, node: Descendant, - editor: IDomEditor + editor: IDomEditor, ): Descendant { const $elem = $(elem) - if (!Element.isElement(node)) return node - if (DomEditor.getNodeType(node) !== 'code') return node // 只针对 pre/code 元素 + if (!Element.isElement(node)) { return node } + if (DomEditor.getNodeType(node) !== 'code') { return node } // 只针对 pre/code 元素 const elemNode = node as CodeElement const langAttr = $elem.attr('class') || '' + if (langAttr.indexOf('language-') === 0) { // V5 版本,格式如 class="language-javascript" elemNode.language = langAttr.split('-')[1] || '' // 获取 'javascript' diff --git a/packages/code-highlight/src/module/render-style.tsx b/packages/code-highlight/src/module/render-style.tsx index 102d998da..1fc0d9d92 100644 --- a/packages/code-highlight/src/module/render-style.tsx +++ b/packages/code-highlight/src/module/render-style.tsx @@ -3,8 +3,9 @@ * @author wangfupeng */ -import { Text as SlateText, Descendant } from 'slate' +import { Descendant, Text as SlateText } from 'slate' import { jsx, VNode } from 'snabbdom' + import { addVnodeClassName } from '../utils/vdom' import { prismTokenTypes } from '../vendor/prism' @@ -16,11 +17,12 @@ import { prismTokenTypes } from '../vendor/prism' */ export function renderStyle(node: Descendant, vnode: VNode): VNode { const leafNode = node as SlateText & { [key: string]: string } - let styleVnode: VNode = vnode + const styleVnode: VNode = vnode let className = '' + prismTokenTypes.forEach(type => { - if (leafNode[type]) className = type + if (leafNode[type]) { className = type } }) if (className) { diff --git a/packages/code-highlight/src/utils/dom.ts b/packages/code-highlight/src/utils/dom.ts index 103f36c39..54064bfd3 100644 --- a/packages/code-highlight/src/utils/dom.ts +++ b/packages/code-highlight/src/utils/dom.ts @@ -5,12 +5,6 @@ import $, { attr } from 'dom7' -if (attr) $.fn.attr = attr - -export { Dom7Array } from 'dom7' - -export default $ - // COMPAT: This is required to prevent TypeScript aliases from doing some very // weird things for Slate's types with the same name as globals. (2019/11/27) // https://github.com/microsoft/TypeScript/issues/35002 @@ -21,4 +15,12 @@ import DOMText = globalThis.Text import DOMRange = globalThis.Range import DOMSelection = globalThis.Selection import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } + +if (attr) { $.fn.attr = attr } + +export { Dom7Array } from 'dom7' + +export default $ +export { + DOMComment, DOMElement, DOMNode, DOMRange, DOMSelection, DOMStaticRange, DOMText, +} diff --git a/packages/code-highlight/src/utils/vdom.ts b/packages/code-highlight/src/utils/vdom.ts index 2be81d2c9..e4930319a 100644 --- a/packages/code-highlight/src/utils/vdom.ts +++ b/packages/code-highlight/src/utils/vdom.ts @@ -11,9 +11,10 @@ import { VNode } from 'snabbdom' * @param className css class */ export function addVnodeClassName(vnode: VNode, className: string) { - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } const data = vnode.data - if (data.props == null) data.props = {} + + if (data.props == null) { data.props = {} } Object.assign(data.props, { className }) } diff --git a/packages/code-highlight/src/vendor/prism.ts b/packages/code-highlight/src/vendor/prism.ts index 79327877f..4df68fcd1 100644 --- a/packages/code-highlight/src/vendor/prism.ts +++ b/packages/code-highlight/src/vendor/prism.ts @@ -3,9 +3,6 @@ * @author wangfupeng */ -import { Text } from 'slate' - -import Prism from 'prismjs' import 'prismjs/components/prism-jsx' import 'prismjs/components/prism-typescript' import 'prismjs/components/prism-markup' @@ -25,6 +22,9 @@ import 'prismjs/components/prism-markdown' import 'prismjs/components/prism-lua' import 'prismjs/components/prism-groovy' import 'prismjs/components/prism-abap' + +import Prism from 'prismjs' +import { Text } from 'slate' // 语言模块,参考 https://github.com/PrismJS/prism/tree/master/components // prismjs 的 token 类型汇总 @@ -72,16 +72,16 @@ export const prismTokenTypes = [ export function getPrismTokenLength(token: any) { if (typeof token === 'string') { return token.length - } else if (typeof token.content === 'string') { + } if (typeof token.content === 'string') { return token.content.length - } else { - // 累加 length - return token.content.reduce( - // @ts-ignore - (l, t) => l + getPrismTokenLength(t), - 0 - ) } + // 累加 length + return token.content.reduce( + // @ts-ignore + (l, t) => l + getPrismTokenLength(t), + 0, + ) + } /** @@ -90,10 +90,11 @@ export function getPrismTokenLength(token: any) { * @param language 代码语言 */ export function getPrismTokens(textNode: Text, language: string) { - if (!language) return [] + if (!language) { return [] } const langGrammar = Prism.languages[language] - if (!langGrammar) return [] + + if (!langGrammar) { return [] } return Prism.tokenize(textNode.text, langGrammar) diff --git a/packages/core/__tests__/config/editor-config.test.ts b/packages/core/__tests__/config/editor-config.test.ts index 8b976bcad..16af5c08b 100644 --- a/packages/core/__tests__/config/editor-config.test.ts +++ b/packages/core/__tests__/config/editor-config.test.ts @@ -4,6 +4,7 @@ */ import { Editor } from 'slate' + import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor describe('editor config', () => { @@ -13,6 +14,7 @@ describe('editor config', () => { it('if set placeholder option, it will show placeholder element when editor content is empty', () => { const container = document.createElement('div') + createCoreEditor({ selector: container, config: { @@ -20,11 +22,13 @@ describe('editor config', () => { }, }) const el = container.querySelector('.w-e-text-placeholder') + expect(el!.textContent).toBe('editor placeholder') }) it('if set placeholder option, it will hide placeholder element when editor content is not empty', () => { const container = document.createElement('div') + createCoreEditor({ selector: container, config: { @@ -33,6 +37,7 @@ describe('editor config', () => { content: [{ type: 'paragraph', children: [{ text: '123' }] }], }) const el = container.querySelector('.w-e-text-placeholder') + expect(el).toBeNull() }) @@ -42,6 +47,7 @@ describe('editor config', () => { readOnly: true, }, }) + expect(editor.isDisabled()).toBeTruthy() }) @@ -66,6 +72,7 @@ describe('editor config', () => { }, }, }) + editor.select(getStartLocation(editor)) // 插入 9 个字符,小于 maxLength diff --git a/packages/core/__tests__/config/menu-config.test.ts b/packages/core/__tests__/config/menu-config.test.ts index d67fb9d5c..b5dc214ae 100644 --- a/packages/core/__tests__/config/menu-config.test.ts +++ b/packages/core/__tests__/config/menu-config.test.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor import { registerGlobalMenuConf } from '../../src/config/register' +import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor describe('menu config', () => { it('set and get', () => { diff --git a/packages/core/__tests__/config/toolbar-config.test.ts b/packages/core/__tests__/config/toolbar-config.test.ts index 1a5ff0a5a..39ca70870 100644 --- a/packages/core/__tests__/config/toolbar-config.test.ts +++ b/packages/core/__tests__/config/toolbar-config.test.ts @@ -3,16 +3,17 @@ * @author wangfupeng */ -import createCoreEditor from '../create-core-editor' -import { IDomEditor } from '../../src/editor/interface' -import createToolbarForSrc from '../../src/create/create-toolbar' - // 注册几个菜单,测试用 import '../menus/register-menus/index' +import createToolbarForSrc from '../../src/create/create-toolbar' +import { IDomEditor } from '../../src/editor/interface' +import createCoreEditor from '../create-core-editor' + // 创建 toolbar function createToolbar(editor: IDomEditor, customConfig = {}) { const container = document.createElement('div') + document.body.appendChild(container) return createToolbarForSrc(editor, { selector: container, @@ -30,21 +31,24 @@ describe('toolbar config', () => { const toolbar = createToolbar(editor) const defaultConfig = toolbar.getConfig() const { excludeKeys = [], toolbarKeys = [] } = defaultConfig + expect(excludeKeys.length).toBe(0) expect(toolbarKeys.length).toBeGreaterThan(0) }) it('create editor is null', () => { const container = document.createElement('div') + expect(() => { createToolbarForSrc(null as unknown as IDomEditor, { selector: container, }) - }).toThrow(`Cannot create toolbar, because editor is null`) + }).toThrow('Cannot create toolbar, because editor is null') }) it('repeat create tool bar', () => { const container = document.createElement('div') + createToolbarForSrc(editor, { selector: container, }) @@ -63,6 +67,7 @@ describe('toolbar config', () => { }) const { toolbarKeys = [] } = toolbar.getConfig() + expect(toolbarKeys).toEqual(keys) }) @@ -72,6 +77,7 @@ describe('toolbar config', () => { excludeKeys: keys, }) const { excludeKeys = [] } = toolbar.getConfig() + expect(excludeKeys).toEqual(keys) }) @@ -86,6 +92,7 @@ describe('toolbar config', () => { insertKeys: insertKeysInfo, }) const { insertKeys = {} } = toolbar.getConfig() + expect(insertKeys).toEqual(insertKeysInfo) }) }) diff --git a/packages/core/__tests__/create-core-editor.ts b/packages/core/__tests__/create-core-editor.ts index 671bba18f..c7b345cdb 100644 --- a/packages/core/__tests__/create-core-editor.ts +++ b/packages/core/__tests__/create-core-editor.ts @@ -9,6 +9,7 @@ import createToolbarForSrc from '../src/create/create-toolbar' export default function (options: any = {}) { const container = document.createElement('div') + document.body.appendChild(container) return createEditor({ @@ -19,6 +20,7 @@ export default function (options: any = {}) { export const createToolbar = function (editor: IDomEditor, customConfig = {}) { const container = document.createElement('div') + document.body.appendChild(container) return createToolbarForSrc(editor, { selector: container, diff --git a/packages/core/__tests__/create/content-to-html.test.ts b/packages/core/__tests__/create/content-to-html.test.ts index a7642c5ad..881526329 100644 --- a/packages/core/__tests__/create/content-to-html.test.ts +++ b/packages/core/__tests__/create/content-to-html.test.ts @@ -23,6 +23,7 @@ describe('convert to html or text', () => { return e } const editorConfig = { hoverbarKeys: {} } + editorConfig.hoverbarKeys = { link: { menuKeys: ['editLink', 'unLink', 'viewLink'], @@ -37,14 +38,16 @@ describe('convert to html or text', () => { plugins: [fn], config: editorConfig, }) + expect(editor.getHtml()).toBe('<div>hello</div>') }) it('repeat create editor by same selector', () => { - let editor = createEditor({ + const editor = createEditor({ selector: container, content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) + expect(editor.getHtml()).toBe('<div>hello</div>') expect(() => { createEditor({ @@ -59,14 +62,16 @@ describe('convert to html or text', () => { // 不传入 selector ,只有 content content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) + expect(editor.getHtml()).toBe('<div>hello</div>') }) it('convert to html if not give selector option', () => { const editor = createEditor({ // 不传入 selector ,只有 html - html: `hello`, + html: 'hello', }) + expect(editor.getHtml()).toBe('<div>hello</div>') }) @@ -78,6 +83,7 @@ describe('convert to html or text', () => { { type: 'paragraph', children: [{ text: 'world' }] }, ], }) + expect(editor.getText()).toBe('hello\nworld') }) @@ -89,6 +95,7 @@ describe('convert to html or text', () => { { type: 'paragraph', children: [{ text: 'world' }] }, ], }) + expect(editor.getText()).toBe('hello\nworld') }) }) diff --git a/packages/core/__tests__/editor/dom-editor.test.ts b/packages/core/__tests__/editor/dom-editor.test.ts index e046dc923..ca053d1ac 100644 --- a/packages/core/__tests__/editor/dom-editor.test.ts +++ b/packages/core/__tests__/editor/dom-editor.test.ts @@ -4,11 +4,12 @@ */ import { Editor, Range as SlateRange } from 'slate' + import { DomEditor } from '../../src/editor/dom-editor' import { IDomEditor } from '../../src/editor/interface' -import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor import { Key } from '../../src/utils/key' import { NODE_TO_KEY } from '../../src/utils/weak-maps' +import createCoreEditor from '../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor let editor: IDomEditor @@ -83,6 +84,7 @@ describe('Core DomEditor', () => { expect(NODE_TO_KEY.get(node)).toBeUndefined() const newKey = DomEditor.findKey(editor, node) + expect(NODE_TO_KEY.get(node)).toEqual(newKey) }) @@ -105,11 +107,13 @@ describe('Core DomEditor', () => { const textNode = p.children[0] const path = DomEditor.findPath(null, textNode) + expect(path).toEqual([0, 0]) }) test('findDocumentOrShadowRoot', () => { const doc = DomEditor.findDocumentOrShadowRoot(editor) + expect(doc).toBe(document) }) @@ -128,6 +132,7 @@ describe('Core DomEditor', () => { const textNode = p.children[0] const parents = DomEditor.getParentsNodes(editor, textNode) + expect(parents[0]).toBe(p) expect(parents[1]).toBe(editor) }) @@ -138,6 +143,7 @@ describe('Core DomEditor', () => { const textNode = p.children[0] const topNode = DomEditor.getTopNode(editor, textNode) + expect(topNode).toBe(p) }) @@ -147,6 +153,7 @@ describe('Core DomEditor', () => { const key = DomEditor.findKey(editor, p) const domNode = DomEditor.toDOMNode(editor, p) + expect(domNode.tagName).toBe('DIV') expect(domNode.id).toBe(`w-e-element-${key.id}`) }) @@ -156,6 +163,7 @@ describe('Core DomEditor', () => { const domNode = DomEditor.toDOMNode(editor, p) const res = DomEditor.hasDOMNode(editor, domNode) + expect(res).toBeTruthy() }) @@ -170,6 +178,7 @@ describe('Core DomEditor', () => { const domNode = DomEditor.toDOMNode(editor, p) const slateNode = DomEditor.toSlateNode(null, domNode) + expect(slateNode).toBe(p) }) @@ -187,6 +196,7 @@ describe('Core DomEditor', () => { editor.selectAll() const res = DomEditor.hasRange(editor, editor.selection as SlateRange) + expect(res).toBeTruthy() // expect(1).toBe(1) @@ -203,6 +213,7 @@ describe('Core DomEditor', () => { test('checkNodeType', () => { const p = editor.children[0] + expect(DomEditor.checkNodeType(p, 'paragraph')).toBeTruthy() }) @@ -222,6 +233,7 @@ describe('Core DomEditor', () => { test('getSelectedNodeByType', () => { const p = editor.children[0] const selectedNode = DomEditor.getSelectedNodeByType(editor, 'paragraph') + expect(selectedNode).toBe(p) }) @@ -231,6 +243,7 @@ describe('Core DomEditor', () => { const textNode = p.children[0] const selectedTextNode = DomEditor.getSelectedTextNode(editor) + expect(selectedTextNode).toBe(textNode) }) diff --git a/packages/core/__tests__/editor/plugins/with-config.test.ts b/packages/core/__tests__/editor/plugins/with-config.test.ts index 55573c055..c7fcf7733 100644 --- a/packages/core/__tests__/editor/plugins/with-config.test.ts +++ b/packages/core/__tests__/editor/plugins/with-config.test.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor import { withConfig } from '../../../src/editor/plugins/with-config' +import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor function createEditor(...args) { return withConfig(createCoreEditor(...args)) @@ -14,6 +14,7 @@ describe('editor config API', () => { it('get config', () => { const editor = createEditor() const defaultConfig = editor.getConfig() + expect(defaultConfig).not.toBeNull() expect(defaultConfig.autoFocus).toBeTruthy() expect(defaultConfig.readOnly).toBeFalsy() @@ -23,12 +24,14 @@ describe('editor config API', () => { it('get menu config', () => { const editor = createEditor() const insertLinkConfig = editor.getMenuConfig('insertLink') + expect(insertLinkConfig).not.toBeNull() }) it('get all menus', () => { const editor = createEditor() const menuKeys = editor.getAllMenuKeys() + expect(Array.isArray(menuKeys)).toBeTruthy() }) }) diff --git a/packages/core/__tests__/editor/plugins/with-content.test.ts b/packages/core/__tests__/editor/plugins/with-content.test.ts index 26ea3a8a4..a5907c203 100644 --- a/packages/core/__tests__/editor/plugins/with-content.test.ts +++ b/packages/core/__tests__/editor/plugins/with-content.test.ts @@ -3,12 +3,15 @@ * @author wangfupeng */ -import { Editor, Transforms, Node, Selection } from 'slate' -import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor -import { withContent } from '../../../src/editor/plugins/with-content' -import { IDomEditor } from '../../../src/editor/interface' -import { ParseElemHtmlFnType, registerParseElemHtmlConf } from '../../../src' +import { + Editor, Node, Selection, Transforms, +} from 'slate' + import { parseHtmlConf } from '../../../../basic-modules/src/modules/link/parse-elem-html' +import { ParseElemHtmlFnType, registerParseElemHtmlConf } from '../../../src' +import { IDomEditor } from '../../../src/editor/interface' +import { withContent } from '../../../src/editor/plugins/with-content' +import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor function createEditor(...args) { return withContent(createCoreEditor(...args)) @@ -21,7 +24,7 @@ function setEditorSelection( selection: Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } @@ -46,6 +49,7 @@ describe('editor content API', () => { it('handleTab', () => { const editor = createEditor() + editor.select(getStartLocation(editor)) editor.handleTab() expect(editor.getText().length).toBe(4) // 默认 tab 键,输入 4 空格 @@ -57,6 +61,7 @@ describe('editor content API', () => { }) const html = editor.getHtml() + expect(html).toBe('<div>hello</div>') }) @@ -69,6 +74,7 @@ describe('editor content API', () => { }) const html = editor.getHtml() + expect(html).toBe('<div>hello</div><div></div>') }) @@ -80,16 +86,19 @@ describe('editor content API', () => { ], }) const text = editor.getText() + expect(text).toBe('hello\nworld') }) it('isEmpty', () => { const editor1 = createEditor() + expect(editor1.isEmpty()).toBeTruthy() const editor2 = createEditor({ content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) + expect(editor2.isEmpty()).toBeFalsy() }) @@ -97,6 +106,7 @@ describe('editor content API', () => { const editor = createEditor({ content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) + editor.select(getStartLocation(editor)) // 光标在开始位置 expect(editor.getSelectionText()).toBe('') @@ -113,10 +123,13 @@ describe('editor content API', () => { ], }) const headers = editor.getElemsByTypePrefix('header') + expect(headers.length).toBe(2) const pList = editor.getElemsByTypePrefix('paragraph') + expect(pList.length).toBe(1) const images = editor.getElemsByTypePrefix('image') + expect(images.length).toBe(0) }) @@ -129,10 +142,13 @@ describe('editor content API', () => { ], }) const headers = editor.getElemsByType('header') + expect(headers.length).toBe(0) const pList = editor.getElemsByType('paragraph') + expect(pList.length).toBe(1) const images = editor.getElemsByType('image') + expect(images.length).toBe(0) }) @@ -140,6 +156,7 @@ describe('editor content API', () => { const editor = createEditor({ content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) + editor.select(getStartLocation(editor)) // 光标在开始位置 Transforms.move(editor, { distance: 2, unit: 'character' }) // 光标移动 2 个字符 @@ -151,6 +168,7 @@ describe('editor content API', () => { const editor = createEditor({ content: [{ type: 'paragraph', children: [{ text: 'hello world' }] }], }) + editor.select(getStartLocation(editor)) // 光标在开始位置 Transforms.move(editor, { distance: 1, unit: 'word' }) // 光标移动 1 个单词 @@ -162,6 +180,7 @@ describe('editor content API', () => { const editor = createEditor({ content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) + editor.select(getStartLocation(editor)) // 光标在开始位置 Transforms.move(editor, { distance: 1, unit: 'character' }) // 光标移动 1 个字符 @@ -173,6 +192,7 @@ describe('editor content API', () => { const editor = createEditor({ content: [{ type: 'paragraph', children: [{ text: 'hello world' }] }], }) + editor.select(getStartLocation(editor)) // 光标在开始位置 Transforms.move(editor, { distance: 1, unit: 'word' }) // 光标移动 1 个 word @@ -187,6 +207,7 @@ describe('editor content API', () => { { type: 'paragraph', children: [{ text: 'world' }] }, ], }) + editor.select(getStartLocation(editor)) // 光标在开始位置 editor.deleteForward('line') // 向前删除 @@ -198,6 +219,7 @@ describe('editor content API', () => { content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) // 选中 'hel'lo + editor.select({ anchor: { path: [0, 0], @@ -210,6 +232,7 @@ describe('editor content API', () => { }) const fragment = editor.getFragment() // 获取选中内容 + expect(Node.string(fragment[0])).toBe('hel') }) @@ -218,6 +241,7 @@ describe('editor content API', () => { content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) // 选中 'hel'lo + editor.select({ anchor: { path: [0, 0], @@ -235,15 +259,18 @@ describe('editor content API', () => { it('insertBreak', () => { const editor = createEditor() + editor.select(getStartLocation(editor)) // 光标在开始位置 editor.insertBreak() const pList = editor.getElemsByTypePrefix('paragraph') + expect(pList.length).toBe(2) }) it('insertText', () => { const editor = createEditor() + editor.select(getStartLocation(editor)) // 光标在开始位置 editor.insertText('xxx') expect(editor.getText()).toBe('xxx') @@ -253,12 +280,14 @@ describe('editor content API', () => { const editor = createEditor({ content: [{ type: 'paragraph', children: [{ text: 'hello' }] }], }) + editor.clear() expect(editor.getText()).toBe('') }) it('undo', () => { const editor = createEditor() + editor.select(getStartLocation(editor)) // 光标在开始位置 editor.insertText('hello') @@ -270,6 +299,7 @@ describe('editor content API', () => { it('redo', () => { const editor = createEditor() + editor.select(getStartLocation(editor)) // 光标在开始位置 editor.insertText('hello') @@ -294,6 +324,7 @@ describe('editor content API', () => { setEditorSelection(editor) let htmlString = '<div>1</div>' + editor.dangerouslyInsertHtml(htmlString) expect(editor.getText().indexOf('1')).toBeGreaterThan(-1) @@ -306,7 +337,9 @@ describe('editor content API', () => { expect(editor.dangerouslyInsertHtml(htmlString)).toBeUndefined() expect(editor.children).toStrictEqual([ { children: [{ text: '1\n2' }], type: 'paragraph' }, - { children: [{ text: '3' }], url: '', target: '', type: 'link' }, + { + children: [{ text: '3' }], url: '', target: '', type: 'link', + }, ]) }) @@ -314,6 +347,7 @@ describe('editor content API', () => { test(`insert html string with ${tag} element should to be ignore`, () => { setEditorSelection(editor) const htmlString = `<${tag}></${tag}>` + editor.dangerouslyInsertHtml(htmlString) expect(editor.getHtml().indexOf(tag)).toBe(-1) @@ -329,27 +363,33 @@ describe('editor content API', () => { }) const parentNode = editor.getParentNode(textNode) as any + expect(parentNode).not.toBeNull() expect(parentNode.type).toBe('paragraph') }) it('insertNode', () => { const editor = createEditor() + editor.select(getStartLocation(editor)) const p = { type: 'paragraph', children: [{ text: 'hello' }] } + editor.insertNode(p) const pList = editor.getElemsByTypePrefix('paragraph') + expect(pList.length).toBe(2) }) describe('setHtml', () => { it('setHtml normal', () => { const editor = createEditor({ html: '<div>hello</div>' }) + editor.select(getStartLocation(editor)) const newHtml = '<div>world</div>' + editor.setHtml(newHtml) expect(editor.getHtml()).toBe(newHtml) @@ -360,9 +400,11 @@ describe('editor content API', () => { html: '<div>hello</div>', autoFocus: false, }) + expect(editor.isFocused()).toBe(false) const newHtml = '<div>world</div>' + editor.setHtml(newHtml) expect(editor.getHtml()).toBe(newHtml) @@ -371,10 +413,12 @@ describe('editor content API', () => { it('setHtml disabled', () => { const editor = createEditor({ html: '<div>hello</div>' }) + editor.disable() expect(editor.isDisabled()).toBe(true) const newHtml = '<div>world</div>' + editor.setHtml(newHtml) expect(editor.getHtml()).toBe(newHtml) diff --git a/packages/core/__tests__/editor/plugins/with-dom.test.ts b/packages/core/__tests__/editor/plugins/with-dom.test.ts index 4467c225a..d995fd178 100644 --- a/packages/core/__tests__/editor/plugins/with-dom.test.ts +++ b/packages/core/__tests__/editor/plugins/with-dom.test.ts @@ -4,11 +4,12 @@ */ import { Editor } from 'slate' -import createCoreEditor, { createToolbar } from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor + import createBasicEditor from '../../../src/create/create-editor' import { withDOM } from '../../../src/editor/plugins/with-dom' -import { EDITOR_TO_SELECTION } from '../../../src/utils/weak-maps' import $ from '../../../src/utils/dom' +import { EDITOR_TO_SELECTION } from '../../../src/utils/weak-maps' +import createCoreEditor, { createToolbar } from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor function createEditor(...args) { return withDOM(createCoreEditor(...args)) @@ -21,11 +22,13 @@ describe('editor DOM API', () => { it('editor id', () => { const editor = createEditor() + expect(editor.id).not.toBeNull() }) it('destroy', done => { const editorConfig = { hoverbarKeys: {} } + editorConfig.hoverbarKeys = { text: { menuKeys: ['bold', 'insertLink'], @@ -46,6 +49,7 @@ describe('editor DOM API', () => { // 其他参考 https://github.com/cycleccc/wangEditor/blob/master/packages/editor/src/init-default-config/config/hoverbar.ts } const editor = createEditor({ config: editorConfig }) + createToolbar(editor) expect(editor.isDestroyed).toBeFalsy() @@ -58,6 +62,7 @@ describe('editor DOM API', () => { it('scroll to elem', () => { const container = document.createElement('div') + container.setAttribute('id', 'editor-text-area') document.body.appendChild(container) const editor = createBasicEditor({ @@ -65,12 +70,14 @@ describe('editor DOM API', () => { }) const $textarea = $('#editor-text-area') const id = $textarea.attr('id') + editor.scrollToElem(id) // TODO }) it('isFullScreen fullScreen unFullScreen', done => { const editor = createEditor() + createToolbar(editor) expect(editor.isFullScreen).toBeFalsy() @@ -93,6 +100,7 @@ describe('editor DOM API', () => { setTimeout(() => { const domNode = editor.toDOMNode(p) + expect(domNode.tagName).toBe('DIV') done() }) @@ -100,6 +108,7 @@ describe('editor DOM API', () => { it('foucus', () => { const editor = createEditor() + editor.focus() editor.insertText('hello') editor.focus() @@ -113,6 +122,7 @@ describe('editor DOM API', () => { anchor: { offset: 0, path: [0, 0] }, focus: { offset: 3, path: [0, 0] }, } + if (selection != null) { EDITOR_TO_SELECTION.set(editor, selection) } @@ -135,6 +145,7 @@ describe('editor DOM API', () => { it('disable isDisabled enable', () => { const editor = createEditor() + editor.select(getStartLocation(editor)) expect(editor.isDisabled()).toBeFalsy() diff --git a/packages/core/__tests__/editor/plugins/with-emitter.test.ts b/packages/core/__tests__/editor/plugins/with-emitter.test.ts index 424aca8b9..e4539d4d1 100644 --- a/packages/core/__tests__/editor/plugins/with-emitter.test.ts +++ b/packages/core/__tests__/editor/plugins/with-emitter.test.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor import { withEmitter } from '../../../src/editor/plugins/with-emitter' +import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor function createEditor(...args) { return withEmitter(createCoreEditor(...args)) diff --git a/packages/core/__tests__/editor/plugins/with-selection.test.ts b/packages/core/__tests__/editor/plugins/with-selection.test.ts index e630e7b89..335a6509c 100644 --- a/packages/core/__tests__/editor/plugins/with-selection.test.ts +++ b/packages/core/__tests__/editor/plugins/with-selection.test.ts @@ -4,8 +4,9 @@ */ import { Editor } from 'slate' -import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor + import { withSelection } from '../../../src/editor/plugins/with-selection' +import createCoreEditor from '../../create-core-editor' // packages/core 不依赖 packages/editor ,不能使用后者的 createEditor function createEditor(...args) { return withSelection(createCoreEditor(...args)) @@ -37,6 +38,7 @@ describe('editor selection API', () => { it('isSelectedAll', () => { const p = genParagraph() const editor = createEditor({ content: [p] }) + expect(editor.isSelectedAll()).toBeFalsy() editor.select(getStartLocation(editor)) diff --git a/packages/core/__tests__/menus/register-menus/register-button-menu.ts b/packages/core/__tests__/menus/register-menus/register-button-menu.ts index 38f843419..4585f394b 100644 --- a/packages/core/__tests__/menus/register-menus/register-button-menu.ts +++ b/packages/core/__tests__/menus/register-menus/register-button-menu.ts @@ -3,21 +3,26 @@ * @author wangfupeng */ -import { registerMenu, IButtonMenu } from '../../../src/menus/index' import { IDomEditor } from '../../../src/editor/interface' +import { IButtonMenu, registerMenu } from '../../../src/menus/index' class MyButtonMenu implements IButtonMenu { readonly title = 'My Button Menu' + readonly tag = 'button' + getValue(editor: IDomEditor) { return '' } + isActive(editor: IDomEditor) { return false } + isDisabled(editor: IDomEditor) { return false } + exec(editor: IDomEditor, value: string | boolean) { console.log('do..') } diff --git a/packages/core/__tests__/menus/register-menus/register-modal-menu.ts b/packages/core/__tests__/menus/register-menus/register-modal-menu.ts index d03c98264..74296ae12 100644 --- a/packages/core/__tests__/menus/register-menus/register-modal-menu.ts +++ b/packages/core/__tests__/menus/register-menus/register-modal-menu.ts @@ -3,29 +3,38 @@ * @author wangfupeng */ -import { registerMenu, IModalMenu } from '../../../src/menus/index' import { IDomEditor } from '../../../src/editor/interface' +import { IModalMenu, registerMenu } from '../../../src/menus/index' class MyModalMenu implements IModalMenu { readonly title = 'My Modal Menu' + readonly tag = 'button' + readonly showModal = true + readonly modalWidth = 300 + getValue(editor: IDomEditor) { return '' } + isActive(editor: IDomEditor) { return false } + isDisabled(editor: IDomEditor) { return false } + exec(editor: IDomEditor, value: string | boolean) { console.log('do..') } + getModalContentElem(editor: IDomEditor) { return document.createElement('div') } + getModalPositionNode(editor: IDomEditor) { return null } diff --git a/packages/core/__tests__/menus/register-menus/register-select-menu.ts b/packages/core/__tests__/menus/register-menus/register-select-menu.ts index 36cc7c509..561d566a5 100644 --- a/packages/core/__tests__/menus/register-menus/register-select-menu.ts +++ b/packages/core/__tests__/menus/register-menus/register-select-menu.ts @@ -3,24 +3,30 @@ * @author wangfupeng */ -import { registerMenu, ISelectMenu, IOption } from '../../../src/menus/index' import { IDomEditor } from '../../../src/editor/interface' +import { IOption, ISelectMenu, registerMenu } from '../../../src/menus/index' class MySelectMenu implements ISelectMenu { readonly title = 'My Select Menu' + readonly tag = 'select' + getValue(editor: IDomEditor) { return '' } + isActive(editor: IDomEditor) { return false } + isDisabled(editor: IDomEditor) { return false } + exec(editor: IDomEditor, value: string | boolean) { console.log('do..') } + getOptions(): IOption[] { return [ { value: 'a', text: 'a' }, diff --git a/packages/core/__tests__/upload/uploader.test.ts b/packages/core/__tests__/upload/uploader.test.ts index f02307e47..3ca7f18c6 100644 --- a/packages/core/__tests__/upload/uploader.test.ts +++ b/packages/core/__tests__/upload/uploader.test.ts @@ -3,9 +3,10 @@ * @author wangfupeng */ +import nock from 'nock' + import createUploader from '../../src/upload/createUploader' import { IUploadConfig } from '../../src/upload/interface' -import nock from 'nock' const server = 'https://fake-endpoint.wangeditor-v5.com' @@ -22,6 +23,7 @@ describe('uploader', () => { onFailed: (file, res) => {}, onError: (file, err, res) => {}, }) + expect(uppy).not.toBeNull() }) @@ -99,9 +101,10 @@ describe('uploader', () => { .reply(200, {}) const fn = jest.fn() + console.error = fn - let uppy = createUploader({ + const uppy = createUploader({ server, fieldName: 'file1', metaWithUrl: false, @@ -206,6 +209,7 @@ describe('uploader', () => { .reply(400, {}) const fn = jest.fn() + console.error = fn const uppy = createUploader({ server, @@ -240,6 +244,7 @@ describe('uploader', () => { .reply(400, {}) const fn = jest.fn() + console.error = fn const uppy = createUploader({ server, @@ -268,6 +273,7 @@ describe('uploader', () => { test('it should invoke error callback if file size over max size', () => { const fn = jest.fn() const consoleFn = jest.fn() + console.error = consoleFn let uppy = createUploader({ server, diff --git a/packages/core/__tests__/utils/util.test.ts b/packages/core/__tests__/utils/util.test.ts index 9fd193c0c..e4662b98f 100644 --- a/packages/core/__tests__/utils/util.test.ts +++ b/packages/core/__tests__/utils/util.test.ts @@ -4,16 +4,17 @@ */ import { - genRandomStr, addQueryToUrl, - replaceHtmlSpecialSymbols, deReplaceHtmlSpecialSymbols, + genRandomStr, + replaceHtmlSpecialSymbols, } from '../../src/utils/util' describe('utils', () => { it('gen random', () => { const r1 = genRandomStr() const r2 = genRandomStr() + expect(r1).not.toBe(r2) }) @@ -21,27 +22,32 @@ describe('utils', () => { const params = { a: 10, b: 'hello' } const url1 = 'https://wangeditor.com/' + expect(addQueryToUrl(url1, params)).toBe('https://wangeditor.com/?a=10&b=hello') const url2 = 'https://wangeditor.com/?x=1#123' + expect(addQueryToUrl(url2, params)).toBe('https://wangeditor.com/?x=1&a=10&b=hello#123') }) it('replace html symbol', () => { const html = '<p>hello world</p>' const res = replaceHtmlSpecialSymbols(html) + expect(res).toBe('&lt;p&gt;hello &nbsp;world&lt;/p&gt;') }) it('replace html symbol', () => { const html = '&lt;p&gt;hello &nbsp;world&lt;/p&gt;' const res = deReplaceHtmlSpecialSymbols(html) + expect(res).toBe('<p>hello world</p>') }) it('decode html quote symbol', () => { const html = '<p style="font-family:&quot;Times New Roman&quot;;">hello world</p>' const res = deReplaceHtmlSpecialSymbols(html) + expect(res).toBe('<p style="font-family:"Times New Roman";">hello world</p>') }) }) diff --git a/packages/core/__tests__/utils/vdom.test.ts b/packages/core/__tests__/utils/vdom.test.ts index 18d59c1cb..e356d3d1d 100644 --- a/packages/core/__tests__/utils/vdom.test.ts +++ b/packages/core/__tests__/utils/vdom.test.ts @@ -4,13 +4,9 @@ */ import { h, VNode } from 'snabbdom' + import { - normalizeVnodeData, - addVnodeProp, - addVnodeDataset, - addVnodeStyle, -} from '../../src/utils/vdom' -import { + DOMPoint, DOMRange, isDataTransfer, isDOMComment, @@ -18,10 +14,15 @@ import { isDOMText, isHTMLElememt, isPlainTextOnlyPaste, - DOMPoint, - normalizeDOMPoint, NodeType, + normalizeDOMPoint, } from '../../src/utils/dom' +import { + addVnodeDataset, + addVnodeProp, + addVnodeStyle, + normalizeVnodeData, +} from '../../src/utils/vdom' describe('vdom util fns', () => { it('normalize vnode data', () => { @@ -39,17 +40,19 @@ describe('vdom util fns', () => { { id: 'p1', }, - ['hello'] + ['hello'], ), - ] + ], ) normalizeVnodeData(vnode) // 转换 div 自身 const { data = {}, children = [] } = vnode + expect(data.key).toBe('someKey') const { props = {}, dataset = {} } = data + expect(props.id).toBe('div1') expect(props.className).toBe('someClassName') expect(dataset.customName).toBe('someCustomName') @@ -57,30 +60,37 @@ describe('vdom util fns', () => { // 转换 div 子节点 p const pVNode = (children[0] || {}) as VNode const { props: pProps = {} } = pVNode.data || {} + expect(pProps.id).toBe('p1') }) it('add vnode props', () => { const vnode = h('div', {}) + addVnodeProp(vnode, { k1: 'v1' }) const { props = {} } = vnode.data || {} + expect(props.k1).toBe('v1') }) it('add vnode dataset', () => { const vnode = h('div', {}) + addVnodeDataset(vnode, { k1: 'v1' }) const { dataset = {} } = vnode.data || {} + expect(dataset.k1).toBe('v1') }) it('add vnode style', () => { const vnode = h('div', {}) + addVnodeStyle(vnode, { k1: 'v1' }) const { style = {} } = vnode.data || {} + expect(style.k1).toBe('v1') }) @@ -97,6 +107,7 @@ describe('vdom util fns', () => { const mockDOMNode = { nodeType: 8, // Element 类型的 nodeType } + expect(isDOMComment(mockDOMNode)).toBeTruthy() }) @@ -109,6 +120,7 @@ describe('vdom util fns', () => { const mockDOMNode = { nodeType: 3, // Element 类型的 nodeType } + expect(isDOMText(mockDOMNode)).toBeTruthy() }) @@ -121,6 +133,7 @@ describe('vdom util fns', () => { const event = { clipboardData, } as unknown as ClipboardEvent + expect(isPlainTextOnlyPaste(event)).toBeTruthy() }) @@ -133,6 +146,7 @@ describe('vdom util fns', () => { let domPoint = [mockDOMNode, 1] as DOMPoint let result = normalizeDOMPoint(domPoint) + expect(result).toEqual(domPoint) mockDOMNode = { nodeType: 1, // Element 类型的 nodeType diff --git a/packages/core/package.json b/packages/core/package.json index 76cd0d046..6bdfea7ec 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,7 +58,7 @@ "dependencies": { "@types/event-emitter": "^0.3.3", "event-emitter": "^0.3.5", - "html-void-elements": "^2.0.0", + "html-void-elements": "^3.0.0", "i18next": "^20.4.0", "scroll-into-view-if-needed": "^2.2.28", "slate-history": "^0.66.0" diff --git a/packages/core/rollup.config.js b/packages/core/rollup.config.js index 6cc2173be..afd4ca9a3 100644 --- a/packages/core/rollup.config.js +++ b/packages/core/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorCore' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index a3324660f..54b63fceb 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -3,8 +3,9 @@ * @author wangfupeng */ -import forEach from 'lodash.foreach' import cloneDeep from 'lodash.clonedeep' +import forEach from 'lodash.foreach' + import { IEditorConfig, IMenuConfig, IToolbarConfig } from './interface' import { GLOBAL_MENU_CONF } from './register' @@ -17,6 +18,7 @@ export function genEditorConfig(userConfig: Partial<IEditorConfig> = {}): IEdito // 单独处理 menuConf const { MENU_CONF: userMenuConf = {} } = userConfig + forEach(defaultMenuConf, (menuConf, menuKey) => { // 生成新的 menu config newMenuConf[menuKey] = { diff --git a/packages/core/src/config/interface.ts b/packages/core/src/config/interface.ts index 3c7178982..a7edcfc68 100644 --- a/packages/core/src/config/interface.ts +++ b/packages/core/src/config/interface.ts @@ -3,7 +3,8 @@ * @author wangfupeng */ -import { Range, NodeEntry, Node } from 'slate' +import { Node, NodeEntry, Range } from 'slate' + import { IDomEditor } from '../editor/interface' import { IMenuGroup } from '../menus/interface' @@ -29,7 +30,7 @@ export interface IMenuConfig { * editor config */ export interface IEditorConfig { - //【注意】如增加 onXxx 回调函数时,要同步到 vue2/vue3 组件 + // 【注意】如增加 onXxx 回调函数时,要同步到 vue2/vue3 组件 customAlert: (info: string, type: AlertType) => void onCreated?: (editor: IDomEditor) => void diff --git a/packages/core/src/config/register.ts b/packages/core/src/config/register.ts index 25c20b0e8..06a0e9335 100644 --- a/packages/core/src/config/register.ts +++ b/packages/core/src/config/register.ts @@ -14,6 +14,6 @@ export const GLOBAL_MENU_CONF: IMenuConfig = {} * @param config config */ export function registerGlobalMenuConf(key: string, config?: ISingleMenuConfig) { - if (config == null) return + if (config == null) { return } GLOBAL_MENU_CONF[key] = config } diff --git a/packages/core/src/constants/svg.ts b/packages/core/src/constants/svg.ts index a5dcf531f..e145b35fd 100644 --- a/packages/core/src/constants/svg.ts +++ b/packages/core/src/constants/svg.ts @@ -10,13 +10,10 @@ */ // 对号 -export const SVG_CHECK_MARK = - '<svg viewBox="0 0 1446 1024"><path d="M574.116299 786.736392 1238.811249 48.517862C1272.390222 11.224635 1329.414799 7.827718 1366.75664 41.450462 1403.840015 74.840484 1406.731043 132.084741 1373.10189 169.433699L655.118888 966.834607C653.072421 969.716875 650.835807 972.514337 648.407938 975.210759 615.017957 1012.29409 558.292155 1015.652019 521.195664 982.250188L72.778218 578.493306C35.910826 545.297758 32.859041 488.584019 66.481825 451.242134 99.871807 414.158803 156.597563 410.800834 193.694055 444.202665L574.116299 786.736392Z"></path></svg>' +export const SVG_CHECK_MARK = '<svg viewBox="0 0 1446 1024"><path d="M574.116299 786.736392 1238.811249 48.517862C1272.390222 11.224635 1329.414799 7.827718 1366.75664 41.450462 1403.840015 74.840484 1406.731043 132.084741 1373.10189 169.433699L655.118888 966.834607C653.072421 969.716875 650.835807 972.514337 648.407938 975.210759 615.017957 1012.29409 558.292155 1015.652019 521.195664 982.250188L72.778218 578.493306C35.910826 545.297758 32.859041 488.584019 66.481825 451.242134 99.871807 414.158803 156.597563 410.800834 193.694055 444.202665L574.116299 786.736392Z"></path></svg>' // 向下的箭头 -export const SVG_DOWN_ARROW = - '<svg viewBox="0 0 1024 1024"><path d="M498.7 655.8l-197.6-268c-8.1-10.9-0.3-26.4 13.3-26.4h395.2c13.6 0 21.4 15.4 13.3 26.4l-197.6 268c-6.6 9-20 9-26.6 0z"></path></svg>' +export const SVG_DOWN_ARROW = '<svg viewBox="0 0 1024 1024"><path d="M498.7 655.8l-197.6-268c-8.1-10.9-0.3-26.4 13.3-26.4h395.2c13.6 0 21.4 15.4 13.3 26.4l-197.6 268c-6.6 9-20 9-26.6 0z"></path></svg>' // 关闭 -export const SVG_CLOSE = - '<svg viewBox="0 0 1024 1024"><path d="M1024 896.1024l-128 128L512 640 128 1024 0 896 384 512 0 128 128 0 512 384 896.1024 0l128 128L640 512z"></path></svg>' +export const SVG_CLOSE = '<svg viewBox="0 0 1024 1024"><path d="M1024 896.1024l-128 128L512 640 128 1024 0 896 384 512 0 128 128 0 512 384 896.1024 0l128 128L640 512z"></path></svg>' diff --git a/packages/core/src/create/bind-node-relation.ts b/packages/core/src/create/bind-node-relation.ts index e5f012d83..0744e26f9 100644 --- a/packages/core/src/create/bind-node-relation.ts +++ b/packages/core/src/create/bind-node-relation.ts @@ -3,7 +3,10 @@ * @author wangfupeng */ -import { Element, Editor, Node, Ancestor } from 'slate' +import { + Ancestor, Editor, Element, Node, +} from 'slate' + import { IDomEditor } from '../editor/interface' import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps' @@ -21,12 +24,15 @@ function bindNodeRelation(node: Node, index: number, parent: Ancestor, editor: I if (Element.isElement(node)) { const { children = [] } = node + children.forEach((child: Node, i: number) => bindNodeRelation(child, i, node, editor)) // 递归子节点 const isVoid = Editor.isVoid(editor, node) + if (isVoid) { const [[text]] = Node.texts(node) // 记录 text 相关 weakMap + NODE_TO_INDEX.set(text, 0) NODE_TO_PARENT.set(text, node) } diff --git a/packages/core/src/create/create-editor.ts b/packages/core/src/create/create-editor.ts index 30fa80f58..ce4341027 100644 --- a/packages/core/src/create/create-editor.ts +++ b/packages/core/src/create/create-editor.ts @@ -5,31 +5,32 @@ import { createEditor, Descendant } from 'slate' import { withHistory } from 'slate-history' -import { withDOM } from '../editor/plugins/with-dom' + +import { genEditorConfig } from '../config/index' +import { IEditorConfig } from '../config/interface' +import { DomEditor } from '../editor/dom-editor' +import { IDomEditor } from '../editor/interface' import { withConfig } from '../editor/plugins/with-config' import { withContent } from '../editor/plugins/with-content' -import { withEventData } from '../editor/plugins/with-event-data' +import { withDOM } from '../editor/plugins/with-dom' import { withEmitter } from '../editor/plugins/with-emitter' -import { withSelection } from '../editor/plugins/with-selection' +import { withEventData } from '../editor/plugins/with-event-data' import { withMaxLength } from '../editor/plugins/with-max-length' -import TextArea from '../text-area/TextArea' +import { withSelection } from '../editor/plugins/with-selection' import HoverBar from '../menus/bar/HoverBar' -import { genEditorConfig } from '../config/index' -import { IDomEditor } from '../editor/interface' -import { DomEditor } from '../editor/dom-editor' -import { IEditorConfig } from '../config/interface' -import { promiseResolveThen } from '../utils/util' -import { isRepeatedCreateTextarea, genDefaultContent, htmlToContent } from './helper' +import TextArea from '../text-area/TextArea' import type { DOMElement } from '../utils/dom' +import $ from '../utils/dom' +import { promiseResolveThen } from '../utils/util' import { - EDITOR_TO_TEXTAREA, - TEXTAREA_TO_EDITOR, EDITOR_TO_CONFIG, - HOVER_BAR_TO_EDITOR, EDITOR_TO_HOVER_BAR, + EDITOR_TO_TEXTAREA, + HOVER_BAR_TO_EDITOR, + TEXTAREA_TO_EDITOR, } from '../utils/weak-maps' import bindNodeRelation from './bind-node-relation' -import $ from '../utils/dom' +import { genDefaultContent, htmlToContent, isRepeatedCreateTextarea } from './helper' type PluginFnType = <T extends IDomEditor>(editor: T) => T @@ -45,14 +46,17 @@ interface ICreateOption { * 创建编辑器 */ export default function (option: Partial<ICreateOption>) { - const { selector = '', config = {}, content, html, plugins = [] } = option + const { + selector = '', config = {}, content, html, plugins = [], + } = option // 创建实例 - 使用插件 let editor = withHistory( withMaxLength( - withEmitter(withSelection(withContent(withConfig(withDOM(withEventData(createEditor())))))) - ) + withEmitter(withSelection(withContent(withConfig(withDOM(withEventData(createEditor())))))), + ), ) + if (selector) { // 检查是否对同一个 DOM 重复创建 if (isRepeatedCreateTextarea(editor, selector)) { @@ -62,6 +66,7 @@ export default function (option: Partial<ICreateOption>) { // 处理配置 const editorConfig = genEditorConfig(config) + EDITOR_TO_CONFIG.set(editor, editorConfig) const { hoverbarKeys = {} } = editorConfig @@ -86,6 +91,7 @@ export default function (option: Partial<ICreateOption>) { if (selector) { // 传入了 selector ,则创建 textarea DOM const textarea = new TextArea(selector) + EDITOR_TO_TEXTAREA.set(editor, textarea) TEXTAREA_TO_EDITOR.set(textarea, editor) textarea.changeViewState() // 初始化时触发一次,以便能初始化 textarea DOM 和 selection @@ -93,9 +99,11 @@ export default function (option: Partial<ICreateOption>) { // 判断 textarea 最小高度,并给出提示 promiseResolveThen(() => { const $scroll = textarea.$scroll - if ($scroll == null) return + + if ($scroll == null) { return } if ($scroll.height() < 300) { let info = '编辑区域高度 < 300px 这可能会导致 modal hoverbar 定位异常' + info += '\nTextarea height < 300px . This may be cause modal and hoverbar position error' console.warn(info, $scroll) } @@ -103,6 +111,7 @@ export default function (option: Partial<ICreateOption>) { // 创建 hoverbar DOM let hoverbar: HoverBar | null + if (Object.keys(hoverbarKeys).length > 0) { hoverbar = new HoverBar() HOVER_BAR_TO_EDITOR.set(hoverbar, editor) @@ -123,6 +132,7 @@ export default function (option: Partial<ICreateOption>) { // 触发生命周期 const { onCreated, onDestroyed } = editorConfig + if (onCreated) { editor.on('created', () => onCreated(editor)) } diff --git a/packages/core/src/create/create-toolbar.ts b/packages/core/src/create/create-toolbar.ts index adcd64154..9c874d5d9 100644 --- a/packages/core/src/create/create-toolbar.ts +++ b/packages/core/src/create/create-toolbar.ts @@ -3,13 +3,13 @@ * @author wangfupeng */ +import { genToolbarConfig } from '../config/index' +import { IToolbarConfig } from '../config/interface' import { IDomEditor } from '../editor/interface' import Toolbar from '../menus/bar/Toolbar' -import { IToolbarConfig } from '../config/interface' -import { genToolbarConfig } from '../config/index' -import { isRepeatedCreateToolbar } from './helper' import { DOMElement } from '../utils/dom' -import { TOOLBAR_TO_EDITOR, EDITOR_TO_TOOLBAR } from '../utils/weak-maps' +import { EDITOR_TO_TOOLBAR, TOOLBAR_TO_EDITOR } from '../utils/weak-maps' +import { isRepeatedCreateToolbar } from './helper' interface ICreateOption { selector: string | DOMElement @@ -18,7 +18,7 @@ interface ICreateOption { export default function (editor: IDomEditor | null, option: ICreateOption): Toolbar { if (editor == null) { - throw new Error(`Cannot create toolbar, because editor is null`) + throw new Error('Cannot create toolbar, because editor is null') } const { selector, config = {} } = option @@ -33,6 +33,7 @@ export default function (editor: IDomEditor | null, option: ICreateOption): Tool // 创建 toolbar ,并记录和 editor 关系 const toolbar = new Toolbar(selector, toolbarConfig) + TOOLBAR_TO_EDITOR.set(toolbar, editor) EDITOR_TO_TOOLBAR.set(editor, toolbar) diff --git a/packages/core/src/create/helper.ts b/packages/core/src/create/helper.ts index 1438a7584..7821fabfd 100644 --- a/packages/core/src/create/helper.ts +++ b/packages/core/src/create/helper.ts @@ -4,6 +4,7 @@ */ import { Descendant } from 'slate' + import { IDomEditor } from '../editor/interface' import parseElemHtml from '../parse-html/parse-elem-html' import $, { DOMElement } from '../utils/dom' @@ -11,10 +12,11 @@ import $, { DOMElement } from '../utils/dom' function isRepeatedCreate( editor: IDomEditor, attrKey: string, - selector: string | DOMElement + selector: string | DOMElement, ): boolean { // @ts-ignore const $elem = $(selector) + if ($elem.attr(attrKey)) { return true // 有属性,说明已经创建过 } @@ -35,7 +37,7 @@ function isRepeatedCreate( */ export function isRepeatedCreateTextarea( editor: IDomEditor, - selector: string | DOMElement + selector: string | DOMElement, ): boolean { return isRepeatedCreate(editor, 'data-w-e-textarea', selector) } @@ -45,7 +47,7 @@ export function isRepeatedCreateTextarea( */ export function isRepeatedCreateToolbar( editor: IDomEditor, - selector: string | DOMElement + selector: string | DOMElement, ): boolean { return isRepeatedCreate(editor, 'data-w-e-toolbar', selector) } @@ -67,11 +69,11 @@ export function genDefaultContent() { * @param editor editor * @param html html 字符串 */ -export function htmlToContent(editor: IDomEditor, html: string = ''): Descendant[] { +export function htmlToContent(editor: IDomEditor, html = ''): Descendant[] { const res: Descendant[] = [] // 空白内容 - if (html === '') html = '<p><br></p>' + if (html === '') { html = '<p><br></p>' } // 非 HTML 格式,文本格式,用 <p> 包裹 if (html.indexOf('<') !== 0) { @@ -83,6 +85,7 @@ export function htmlToContent(editor: IDomEditor, html: string = ''): Descendant const $content = $(`<div>${html}</div>`) const list = Array.from($content.children()) + list.forEach(child => { const $child = $(child) const parsedRes = parseElemHtml($child, editor) diff --git a/packages/core/src/editor/dom-editor.ts b/packages/core/src/editor/dom-editor.ts index ff5fae06d..18fc7df31 100644 --- a/packages/core/src/editor/dom-editor.ts +++ b/packages/core/src/editor/dom-editor.ts @@ -4,24 +4,13 @@ */ import toArray from 'lodash.toarray' -import { Editor, Node, Element, Path, Point, Range, Ancestor, Text } from 'slate' -import type { IDomEditor } from './interface' -import { Key } from '../utils/key' -import TextArea from '../text-area/TextArea' -import Toolbar from '../menus/bar/Toolbar' -import HoverBar from '../menus/bar/HoverBar' import { - EDITOR_TO_ELEMENT, - ELEMENT_TO_NODE, - KEY_TO_ELEMENT, - NODE_TO_INDEX, - NODE_TO_KEY, - NODE_TO_PARENT, - EDITOR_TO_TEXTAREA, - EDITOR_TO_TOOLBAR, - EDITOR_TO_HOVER_BAR, - EDITOR_TO_WINDOW, -} from '../utils/weak-maps' + Ancestor, Editor, Element, Node, Path, Point, Range, Text, +} from 'slate' + +import HoverBar from '../menus/bar/HoverBar' +import Toolbar from '../menus/bar/Toolbar' +import TextArea from '../text-area/TextArea' import $, { DOMElement, DOMNode, @@ -29,15 +18,29 @@ import $, { DOMRange, DOMSelection, DOMStaticRange, + hasShadowRoot, isDocument, - isShadowRoot, isDOMElement, - normalizeDOMPoint, isDOMSelection, - hasShadowRoot, + isShadowRoot, + normalizeDOMPoint, walkTextNodes, } from '../utils/dom' +import { Key } from '../utils/key' import { IS_CHROME, IS_FIREFOX } from '../utils/ua' +import { + EDITOR_TO_ELEMENT, + EDITOR_TO_HOVER_BAR, + EDITOR_TO_TEXTAREA, + EDITOR_TO_TOOLBAR, + EDITOR_TO_WINDOW, + ELEMENT_TO_NODE, + KEY_TO_ELEMENT, + NODE_TO_INDEX, + NODE_TO_KEY, + NODE_TO_PARENT, +} from '../utils/weak-maps' +import type { IDomEditor } from './interface' /** * 自定义全局 command @@ -48,6 +51,7 @@ export const DomEditor = { */ getWindow(editor: IDomEditor): Window { const window = EDITOR_TO_WINDOW.get(editor) + if (!window) { throw new Error('Unable to find a host window element for this editor') } @@ -72,6 +76,7 @@ export const DomEditor = { setNewKey(node: Node) { const key = new Key() + NODE_TO_KEY.set(node, key) }, @@ -91,9 +96,9 @@ export const DomEditor = { if (Editor.isEditor(child)) { // 已到达最顶层,返回 patch return path - } else { - break } + break + } // 获取该节点在父节点中的 index @@ -147,8 +152,10 @@ export const DomEditor = { getParentsNodes(editor: IDomEditor, node: Node): Ancestor[] { const nodes: Ancestor[] = [] let curNode = node + while (curNode !== editor && curNode != null) { const parentNode = DomEditor.getParentNode(editor, curNode) + if (parentNode == null) { break } else { @@ -167,6 +174,7 @@ export const DomEditor = { getTopNode(editor: IDomEditor, curNode: Node): Node { const path = DomEditor.findPath(editor, curNode) const topPath = [path[0]] + return Node.get(editor, topPath) }, @@ -176,10 +184,12 @@ export const DomEditor = { toDOMNode(editor: IDomEditor, node: Node): HTMLElement { let domNode const isEditor = Editor.isEditor(node) + if (isEditor) { domNode = EDITOR_TO_ELEMENT.get(editor) } else { const key = DomEditor.findKey(editor, node) + domNode = KEY_TO_ELEMENT.get(key) } @@ -205,7 +215,10 @@ export const DomEditor = { try { targetEl = (isDOMElement(target) ? target : target.parentElement) as HTMLElement } catch (err) { - if (!err.message.includes('Permission denied to access property "nodeType"')) { + if ( + err instanceof Error + && !err.message.includes('Permission denied to access property "nodeType"') + ) { throw err } } @@ -216,14 +229,14 @@ export const DomEditor = { return ( // 祖先节点中包括 data-slate-editor 属性,即 textarea - (targetEl.closest(`[data-slate-editor]`) === editorEl && + (targetEl.closest('[data-slate-editor]') === editorEl // 通过参数 editable 控制开启是否验证是可编辑元素或零宽字符 // 补全 data-slate-string 可参考本文代码 - //(data-slate-zero-width、data-slate-string)判断一起出现,唯独此处欠缺,补全 - (!editable || - targetEl.isContentEditable || - !!targetEl.getAttribute('data-slate-zero-width'))) || - !!targetEl.getAttribute('data-slate-string') + // (data-slate-zero-width、data-slate-string)判断一起出现,唯独此处欠缺,补全 + && (!editable + || targetEl.isContentEditable + || !!targetEl.getAttribute('data-slate-zero-width'))) + || !!targetEl.getAttribute('data-slate-string') ) }, @@ -277,7 +290,7 @@ export const DomEditor = { // For each leaf, we need to isolate its content, which means filtering // to its direct text and zero-width spans. (We have to filter out any // other siblings that may have been rendered alongside them.) - const selector = `[data-slate-string], [data-slate-zero-width]` + const selector = '[data-slate-string], [data-slate-zero-width]' const texts = Array.from(el.querySelectorAll(selector)) let start = 0 @@ -295,6 +308,7 @@ export const DomEditor = { if (point.offset <= end) { const offset = Math.min(length, Math.max(0, point.offset - start)) + domPoint = [domNode, offset] break } @@ -316,7 +330,7 @@ export const DomEditor = { let domEl = isDOMElement(domNode) ? domNode : domNode.parentElement if (domEl && !domEl.hasAttribute('data-slate-node')) { - domEl = domEl.closest(`[data-slate-node]`) + domEl = domEl.closest('[data-slate-node]') } const node = domEl ? ELEMENT_TO_NODE.get(domEl as HTMLElement) : null @@ -362,6 +376,7 @@ export const DomEditor = { if (point) { const range = Editor.range(editor, point) + return range } } @@ -375,6 +390,7 @@ export const DomEditor = { domRange = document.caretRangeFromPoint(x, y) } else { const position = document.caretPositionFromPoint(x, y) + if (position) { domRange = document.createRange() domRange.setStart(position.offsetNode, position.offset) @@ -391,6 +407,7 @@ export const DomEditor = { exactMatch: false, suppressThrow: false, }) + return range }, @@ -403,7 +420,7 @@ export const DomEditor = { options: { exactMatch: T suppressThrow: T - } + }, ): T extends true ? Range | null : Range { const { exactMatch, suppressThrow } = options const el = isDOMSelection(domRange) ? domRange.anchorNode : domRange.startContainer @@ -424,9 +441,8 @@ export const DomEditor = { // (2020/08/08) // https://bugs.chromium.org/p/chromium/issues/detail?id=447523 if (IS_CHROME && hasShadowRoot()) { - isCollapsed = - domRange.anchorNode === domRange.focusNode && - domRange.anchorOffset === domRange.focusOffset + isCollapsed = domRange.anchorNode === domRange.focusNode + && domRange.anchorOffset === domRange.focusOffset } else { isCollapsed = domRange.isCollapsed } @@ -447,6 +463,7 @@ export const DomEditor = { exactMatch, suppressThrow, }) + if (!anchor) { return null as T extends true ? Range | null : Range } @@ -454,6 +471,7 @@ export const DomEditor = { const focus = isCollapsed ? anchor : DomEditor.toSlatePoint(editor, [focusNode, focusOffset], { exactMatch, suppressThrow }) + if (!focus) { return null as T extends true ? Range | null : Range } @@ -465,11 +483,12 @@ export const DomEditor = { // and the DOM focus is an Element // (meaning that the selection ends before the element) // unhang the range to avoid mistakenly including the void + if ( - Range.isExpanded(range) && - Range.isForward(range) && - isDOMElement(focusNode) && - Editor.void(editor, { at: range.focus, mode: 'highest' }) + Range.isExpanded(range) + && Range.isForward(range) + && isDOMElement(focusNode) + && Editor.void(editor, { at: range.focus, mode: 'highest' }) ) { range = Editor.unhangRange(editor, range, { voids: true }) } @@ -486,7 +505,7 @@ export const DomEditor = { options: { exactMatch: T suppressThrow: T - } + }, ): T extends true ? Point | null : Point { const { exactMatch, suppressThrow } = options const [nearestNode, nearestOffset] = exactMatch ? domPoint : normalizeDOMPoint(domPoint) @@ -505,6 +524,7 @@ export const DomEditor = { textNode = leafNode.closest('[data-slate-node="text"]')! const window = DomEditor.getWindow(editor) const range = window.document.createRange() + range.setStart(textNode, 0) range.setEnd(nearestNode, nearestOffset) const contents = range.cloneContents() @@ -543,18 +563,18 @@ export const DomEditor = { } if ( - domNode && - offset === domNode.textContent!.length && + domNode + && offset === domNode.textContent!.length // COMPAT: If the parent node is a Slate zero-width space, editor is // because the text node should have no characters. However, during IME // composition the ASCII characters will be prepended to the zero-width // space, so subtract 1 from the offset to account for the zero-width // space character. - (parentNode.hasAttribute('data-slate-zero-width') || + && (parentNode.hasAttribute('data-slate-zero-width') // COMPAT: In Firefox, `range.cloneContents()` returns an extra trailing '\n' // when the document ends with a new-line character. This results in the offset // length being off by one, so we need to subtract one to account for this. - (IS_FIREFOX && domNode.textContent?.endsWith('\n'))) + || (IS_FIREFOX && domNode.textContent?.endsWith('\n'))) ) { offset-- } @@ -572,11 +592,13 @@ export const DomEditor = { // first, and then afterwards for the correct `element`. (2017/03/03) const slateNode = DomEditor.toSlateNode(editor, textNode!) const path = DomEditor.findPath(editor, slateNode) + return { path, offset } as T extends true ? Point | null : Point }, hasRange(editor: IDomEditor, range: Range): boolean { const { anchor, focus } = range + return Editor.hasPath(editor, anchor.path) && Editor.hasPath(editor, focus.path) }, @@ -599,9 +621,11 @@ export const DomEditor = { const elems: Element[] = [] const nodeEntries = Editor.nodes(editor, { universal: true }) - for (let nodeEntry of nodeEntries) { + + for (const nodeEntry of nodeEntries) { const [node] = nodeEntry - if (Element.isElement(node)) elems.push(node) + + if (Element.isElement(node)) { elems.push(node) } } return elems @@ -613,7 +637,7 @@ export const DomEditor = { universal: true, }) - if (nodeEntry == null) return null + if (nodeEntry == null) { return null } return nodeEntry[0] }, @@ -623,7 +647,7 @@ export const DomEditor = { universal: true, }) - if (nodeEntry == null) return null + if (nodeEntry == null) { return null } return nodeEntry[0] }, @@ -632,10 +656,12 @@ export const DomEditor = { match: n => n === node, universal: true, }) - if (nodeEntry == null) return false + + if (nodeEntry == null) { return false } const [n] = nodeEntry - if (n === node) return true + + if (n === node) { return true } return false }, @@ -643,10 +669,9 @@ export const DomEditor = { isSelectionAtLineEnd(editor: IDomEditor, path: Path): boolean { const { selection } = editor - if (!selection) return false + if (!selection) { return false } - const isAtLineEnd = - Editor.isEnd(editor, selection.anchor, path) || Editor.isEnd(editor, selection.focus, path) + const isAtLineEnd = Editor.isEnd(editor, selection.anchor, path) || Editor.isEnd(editor, selection.focus, path) return isAtLineEnd }, @@ -654,7 +679,8 @@ export const DomEditor = { // 获取 textarea 实例 getTextarea(editor: IDomEditor): TextArea { const textarea = EDITOR_TO_TEXTAREA.get(editor) - if (textarea == null) throw new Error('Cannot find textarea instance by editor') + + if (textarea == null) { throw new Error('Cannot find textarea instance by editor') } return textarea }, @@ -683,7 +709,7 @@ export const DomEditor = { const { maxLength, onMaxLength } = editor.getConfig() // 未设置 maxLength ,则返回 number 最大值 - if (typeof maxLength !== 'number' || maxLength <= 0) return Infinity + if (typeof maxLength !== 'number' || maxLength <= 0) { return Infinity } const editorText = editor.getText().replace(/\r|\n|(\r\n)/g, '') // 去掉换行 const curLength = editorText.length @@ -691,7 +717,7 @@ export const DomEditor = { if (leftLength <= 0) { // 触发 maxLength 限制,不再继续插入文字 - if (onMaxLength) onMaxLength(editor) + if (onMaxLength) { onMaxLength(editor) } } return leftLength @@ -702,6 +728,7 @@ export const DomEditor = { // 有时候全选删除新增的文本节点可能不在段落内,因此遍历textArea删除掉 const { $textArea } = DomEditor.getTextarea(editor) const childNodes = $textArea?.[0].childNodes + if (childNodes) { for (const node of Array.from(childNodes)) { if (node.nodeType === 3) { @@ -724,7 +751,8 @@ export const DomEditor = { }, universal: true, }) - for (let nodeEntry of nodeEntries) { + + for (const nodeEntry of nodeEntries) { if (nodeEntry != null) { const n = nodeEntry[0] const elem = DomEditor.toDOMNode(editor, n) @@ -732,6 +760,7 @@ export const DomEditor = { // 只遍历 elem 范围,考虑性能 walkTextNodes(elem, (textNode, parent) => { const $parent = $(parent) + if ($parent.attr('data-slate-string')) { return // 正常的 text } @@ -757,6 +786,7 @@ export const DomEditor = { isLastNode(editor: IDomEditor, node: Node) { const editorChildren = editor.children || [] const editorChildrenLength = editorChildren.length + return editorChildren[editorChildrenLength - 1] === node }, @@ -776,6 +806,7 @@ export const DomEditor = { match: n => editor.isVoid(n as Element), }) let len = 0 + for (const n of voidNodes) { len++ } @@ -788,18 +819,22 @@ export const DomEditor = { */ isSelectedEmptyParagraph(editor: IDomEditor) { const { selection } = editor - if (selection == null) return false - if (Range.isExpanded(selection)) return false + if (selection == null) { return false } + + if (Range.isExpanded(selection)) { return false } const selectedNode = DomEditor.getSelectedNodeByType(editor, 'paragraph') - if (selectedNode === null) return false + + if (selectedNode === null) { return false } const { children } = selectedNode as Element - if (children.length !== 1) return false + + if (children.length !== 1) { return false } const { text } = children[0] as Text - if (text === '') return true + + if (text === '') { return true } }, /** @@ -809,14 +844,17 @@ export const DomEditor = { */ isEmptyPath(editor: IDomEditor, path: Path): boolean { const entry = Editor.node(editor, path) - if (entry == null) return false + + if (entry == null) { return false } const [node] = entry const { children } = node as Element + if (children.length === 1) { const { text } = children[0] as Text - if (text === '') return true // 内容为空 + + if (text === '') { return true } // 内容为空 } return false diff --git a/packages/core/src/editor/interface.ts b/packages/core/src/editor/interface.ts index e62edaad3..c43e21484 100644 --- a/packages/core/src/editor/interface.ts +++ b/packages/core/src/editor/interface.ts @@ -3,9 +3,12 @@ * @author wangfupeng */ -import { Editor, Location, Node, Ancestor, Element } from 'slate' import ee from 'event-emitter' -import { IEditorConfig, AlertType, ISingleMenuConfig } from '../config/interface' +import { + Ancestor, Editor, Element, Location, Node, +} from 'slate' + +import { AlertType, IEditorConfig, ISingleMenuConfig } from '../config/interface' import { IPositionStyle } from '../menus/interface' import { DOMElement } from '../utils/dom' diff --git a/packages/core/src/editor/plugins/with-config.ts b/packages/core/src/editor/plugins/with-config.ts index c1901f1ba..2a277d63b 100644 --- a/packages/core/src/editor/plugins/with-config.ts +++ b/packages/core/src/editor/plugins/with-config.ts @@ -4,17 +4,19 @@ */ import { Editor } from 'slate' + import { IDomEditor } from '../..' -import { EDITOR_TO_CONFIG } from '../../utils/weak-maps' -import { IEditorConfig, AlertType, ISingleMenuConfig } from '../../config/interface' +import { AlertType, IEditorConfig, ISingleMenuConfig } from '../../config/interface' import { MENU_ITEM_FACTORIES } from '../../menus/register' +import { EDITOR_TO_CONFIG } from '../../utils/weak-maps' export const withConfig = <T extends Editor>(editor: T) => { const e = editor as T & IDomEditor e.getAllMenuKeys = (): string[] => { const arr: string[] = [] - for (let key in MENU_ITEM_FACTORIES) { + + for (const key in MENU_ITEM_FACTORIES) { arr.push(key) } return arr @@ -23,20 +25,23 @@ export const withConfig = <T extends Editor>(editor: T) => { // 获取 editor 配置信息 e.getConfig = (): IEditorConfig => { const config = EDITOR_TO_CONFIG.get(e) - if (config == null) throw new Error('Can not get editor config') + + if (config == null) { throw new Error('Can not get editor config') } return config } // 获取 menu config e.getMenuConfig = (menuKey: string): ISingleMenuConfig => { const { MENU_CONF = {} } = e.getConfig() + return MENU_CONF[menuKey] || {} } // alert e.alert = (info: string, type: AlertType = 'info') => { const { customAlert } = e.getConfig() - if (customAlert) customAlert(info, type) + + if (customAlert) { customAlert(info, type) } } return e diff --git a/packages/core/src/editor/plugins/with-content.ts b/packages/core/src/editor/plugins/with-content.ts index 91a665ab0..c474db476 100644 --- a/packages/core/src/editor/plugins/with-content.ts +++ b/packages/core/src/editor/plugins/with-content.ts @@ -3,20 +3,23 @@ * @author wangfupeng */ -import { Editor, Node, Text, Path, Operation, Range, Transforms, Element, Descendant } from 'slate' -import { DomEditor } from '../dom-editor' +import { + Descendant, Editor, Element, Node, Operation, Path, Range, Text, Transforms, +} from 'slate' + import { IDomEditor } from '../..' -import { EDITOR_TO_SELECTION, NODE_TO_KEY } from '../../utils/weak-maps' -import node2html from '../../to-html/node2html' +import { IGNORE_TAGS } from '../../constants' +import { htmlToContent } from '../../create/helper' +import { PARSE_ELEM_HTML_CONF, TEXT_TAGS } from '../../parse-html/index' +import parseElemHtml from '../../parse-html/parse-elem-html' import { genElemId } from '../../render/helper' -import { Key } from '../../utils/key' +import node2html from '../../to-html/node2html' import $, { DOMElement, NodeType } from '../../utils/dom' +import { Key } from '../../utils/key' import { findCurrentLineRange } from '../../utils/line' +import { EDITOR_TO_SELECTION, NODE_TO_KEY } from '../../utils/weak-maps' +import { DomEditor } from '../dom-editor' import { ElementWithId } from '../interface' -import { PARSE_ELEM_HTML_CONF, TEXT_TAGS } from '../../parse-html/index' -import parseElemHtml from '../../parse-html/parse-elem-html' -import { htmlToContent } from '../../create/helper' -import { IGNORE_TAGS } from '../../constants' /** * 把 elem 插入到编辑器 @@ -29,7 +32,7 @@ function insertElemToEditor(editor: IDomEditor, elem: Element) { editor.insertNode(elem) // link 特殊处理,否则后面插入的文字全都在 a 里面 issue#4573 - if (elem.type === 'link') editor.insertFragment([{ text: '' }]) + if (elem.type === 'link') { editor.insertFragment([{ text: '' }]) } } else { // block elem ,另起一行插入 —— 重要 Transforms.insertNodes(editor, elem, { mode: 'highest' }) @@ -38,11 +41,14 @@ function insertElemToEditor(editor: IDomEditor, elem: Element) { export const withContent = <T extends Editor>(editor: T) => { const e = editor as T & IDomEditor - const { onChange, insertText, apply, deleteBackward } = e + const { + onChange, insertText, apply, deleteBackward, + } = e e.insertText = (text: string) => { const { readOnly } = e.getConfig() - if (readOnly) return + + if (readOnly) { return } insertText(text) } @@ -60,6 +66,7 @@ export const withContent = <T extends Editor>(editor: T) => { for (const [node, path] of Editor.levels(e, { at: op.path })) { // 在当前节点寻找 const key = DomEditor.findKey(e, node) + matches.push([path, key]) } break @@ -72,6 +79,7 @@ export const withContent = <T extends Editor>(editor: T) => { for (const [node, path] of Editor.levels(e, { at: Path.parent(op.path) })) { // 在父节点寻找 const key = DomEditor.findKey(e, node) + matches.push([path, key]) } break @@ -82,6 +90,7 @@ export const withContent = <T extends Editor>(editor: T) => { at: Path.common(Path.parent(op.path), Path.parent(op.newPath)), })) { const key = DomEditor.findKey(e, node) + matches.push([path, key]) } break @@ -94,6 +103,7 @@ export const withContent = <T extends Editor>(editor: T) => { // 绑定 node 和 key for (const [path, key] of matches) { const [node] = Editor.node(e, path) + NODE_TO_KEY.set(node, key) } } @@ -126,6 +136,7 @@ export const withContent = <T extends Editor>(editor: T) => { e.onChange = () => { // 记录当前选区 const { selection } = e + if (selection != null) { EDITOR_TO_SELECTION.set(e, selection) } @@ -145,19 +156,22 @@ export const withContent = <T extends Editor>(editor: T) => { e.getHtml = (): string => { const { children = [] } = e const html = children.map(child => node2html(child, e)).join('') + return html } // 获取 text e.getText = (): string => { const { children = [] } = e + return children.map(child => Node.string(child)).join('\n') } // 获取选区文字 e.getSelectionText = (): string => { const { selection } = e - if (selection == null) return '' + + if (selection == null) { return '' } return Editor.string(editor, selection) } @@ -170,11 +184,14 @@ export const withContent = <T extends Editor>(editor: T) => { at: [], universal: true, }) - for (let nodeEntry of nodeEntries) { + + for (const nodeEntry of nodeEntries) { const [node] = nodeEntry + if (Element.isElement(node)) { // 判断 type (前缀 or 全等) - let flag = isPrefix ? node.type.indexOf(type) >= 0 : node.type === type + const flag = isPrefix ? node.type.indexOf(type) >= 0 : node.type === type + if (flag) { const key = DomEditor.findKey(e, node) const id = genElemId(key.id) @@ -201,19 +218,23 @@ export const withContent = <T extends Editor>(editor: T) => { */ e.isEmpty = () => { const { children = [] } = e - if (children.length > 1) return false // >1 个顶级节点 + + if (children.length > 1) { return false } // >1 个顶级节点 const firstNode = children[0] - if (firstNode == null) return true // editor.children 空数组 + + if (firstNode == null) { return true } // editor.children 空数组 if (Element.isElement(firstNode) && firstNode.type === 'paragraph') { const { children: texts = [] } = firstNode - if (texts.length > 1) return false // >1 text node + + if (texts.length > 1) { return false } // >1 text node const t = texts[0] - if (t == null) return true // 无 text 节点 - if (Text.isText(t) && t.text === '') return true // 只有一个 text 且是空字符串 + if (t == null) { return true } // 无 text 节点 + + if (Text.isText(t) && t.text === '') { return true } // 只有一个 text 且是空字符串 } return false @@ -251,11 +272,12 @@ export const withContent = <T extends Editor>(editor: T) => { * @param html html string * @param isRecursive 是否递归调用(内部使用,使用者不要传参) */ - e.dangerouslyInsertHtml = (html: string = '', isRecursive = false) => { - if (!html) return + e.dangerouslyInsertHtml = (html = '', isRecursive = false) => { + if (!html) { return } // ------------- 把 html 转换为 DOM nodes ------------- const div = document.createElement('div') + div.innerHTML = html let domNodes = Array.from(div.childNodes) @@ -263,28 +285,31 @@ export const withContent = <T extends Editor>(editor: T) => { domNodes = domNodes.filter(n => { const { nodeType, nodeName } = n // Text Node - if (nodeType === NodeType.TEXT_NODE) return true + + if (nodeType === NodeType.TEXT_NODE) { return true } // Element Node if (nodeType === NodeType.ELEMENT_NODE) { // 过滤掉忽略的 tag - if (IGNORE_TAGS.has(nodeName.toLowerCase())) return false - else return true + if (IGNORE_TAGS.has(nodeName.toLowerCase())) { return false } + return true } return false }) - if (domNodes.length === 0) return + if (domNodes.length === 0) { return } // ------------- 把 DOM nodes 转换为 slate nodes ,并插入到编辑器 ------------- const { selection } = e - if (selection == null) return + + if (selection == null) { return } let curEmptyParagraphPath: Path | null = null // 是否当前选中了一个空 p (如果是,后面会删掉) // 递归调用时不判断 if (DomEditor.isSelectedEmptyParagraph(e) && !isRecursive) { const { focus } = selection + curEmptyParagraphPath = [focus.path[0]] // 只记录顶级 path 即可 } @@ -292,15 +317,16 @@ export const withContent = <T extends Editor>(editor: T) => { document.body.appendChild(div) let insertedElemNum = 0 // 记录插入 elem 的数量 ( textNode 不算 ) + domNodes.forEach((n, index) => { const { nodeType, nodeName, textContent = '' } = n // ------ Text node ------ if (nodeType === NodeType.TEXT_NODE) { - if (!textContent || !textContent.trim()) return // 无内容的 Text + if (!textContent || !textContent.trim()) { return } // 无内容的 Text // 插入文本 - //【注意】insertNode 和 insertText 有区别:后者会继承光标处的文本样式(如加粗);前者会加入纯文本,无样式; + // 【注意】insertNode 和 insertText 有区别:后者会继承光标处的文本样式(如加粗);前者会加入纯文本,无样式; e.insertNode({ text: textContent }) return } @@ -314,11 +340,12 @@ export const withContent = <T extends Editor>(editor: T) => { // 判断当前的 el 是否是可识别的 tag const el = n as DOMElement let isParseMatch = false + if (TEXT_TAGS.includes(nodeName.toLowerCase())) { // text elem,如 <span> isParseMatch = true } else { - for (let selector in PARSE_ELEM_HTML_CONF) { + for (const selector in PARSE_ELEM_HTML_CONF) { if (el.matches(selector)) { // 普通 elem,如 <p> <a> 等(非 text elem) isParseMatch = true @@ -342,22 +369,24 @@ export const withContent = <T extends Editor>(editor: T) => { } // 如果当前选中 void node ,则选区移动一下 - if (DomEditor.isSelectedVoidNode(e)) e.move(1) + if (DomEditor.isSelectedVoidNode(e)) { e.move(1) } return } // 没有匹配上(如 div ) const display = window.getComputedStyle(el).display + if (!DomEditor.isSelectedEmptyParagraph(e)) { // 当前不是空行,且 非 inline - 则换行 if (display.indexOf('inline') < 0) { if (index >= 1) { const prevEl = domNodes[index - 1] as DOMElement // 如果是 list 列表需要多插入一个回车,模拟双回车删除空 list + if ( - 'matches' in prevEl && - prevEl.matches('ul:not([data-w-e-type]),ol:not([data-w-e-type])') + 'matches' in prevEl + && prevEl.matches('ul:not([data-w-e-type]),ol:not([data-w-e-type])') ) { e.insertBreak() } @@ -397,6 +426,7 @@ export const withContent = <T extends Editor>(editor: T) => { e.clear() // 设置新内容 const newContent = htmlToContent(e, html == null ? '' : html) + Transforms.insertFragment(e, newContent) // 恢复编辑器状态和选区 diff --git a/packages/core/src/editor/plugins/with-dom.ts b/packages/core/src/editor/plugins/with-dom.ts index 46955e739..f81ded60c 100644 --- a/packages/core/src/editor/plugins/with-dom.ts +++ b/packages/core/src/editor/plugins/with-dom.ts @@ -3,21 +3,22 @@ * @author wangfupeng */ -import { Node, Editor, Transforms } from 'slate' -import { DomEditor } from '../dom-editor' +import { Editor, Node, Transforms } from 'slate' + import { IDomEditor } from '../..' import $, { Dom7Array } from '../../utils/dom' import { - IS_FOCUSED, + EDITOR_TO_HOVER_BAR, EDITOR_TO_PANEL_AND_MODAL, + EDITOR_TO_SELECTION, EDITOR_TO_TEXTAREA, - TEXTAREA_TO_EDITOR, EDITOR_TO_TOOLBAR, - TOOLBAR_TO_EDITOR, - EDITOR_TO_HOVER_BAR, HOVER_BAR_TO_EDITOR, - EDITOR_TO_SELECTION, + IS_FOCUSED, + TEXTAREA_TO_EDITOR, + TOOLBAR_TO_EDITOR, } from '../../utils/weak-maps' +import { DomEditor } from '../dom-editor' let ID = 1 @@ -36,6 +37,7 @@ export const withDOM = <T extends Editor>(editor: T) => { // focus e.focus = (isEnd?: boolean) => { const el = DomEditor.toDOMNode(e, e) + el.focus({ preventScroll: true }) IS_FOCUSED.set(e, true) @@ -44,9 +46,11 @@ export const withDOM = <T extends Editor>(editor: T) => { if (isEnd) { // 选区定位到结尾 const end = Editor.end(e, []) + Transforms.select(e, end) } else { const selection = EDITOR_TO_SELECTION.get(e) + if (selection) { Transforms.select(e, selection) // 选区定位到之前的位置 } else { @@ -63,6 +67,7 @@ export const withDOM = <T extends Editor>(editor: T) => { // blur e.blur = () => { const el = DomEditor.toDOMNode(e, e) + el.blur() // 手动执行一次光标 deselect, 触发 onchange 回调,改变 Toolbar 的状态 @@ -74,26 +79,31 @@ export const withDOM = <T extends Editor>(editor: T) => { // 手动更新视图 e.updateView = () => { const textarea = DomEditor.getTextarea(e) + textarea.changeViewState() const toolbar = DomEditor.getToolbar(e) + toolbar && toolbar.changeToolbarState() const hoverbar = DomEditor.getHoverbar(e) + hoverbar && hoverbar.changeHoverbarState() } // destroy e.destroy = () => { // 销毁相关实例(会销毁 DOM) - if (e.isDestroyed) return + if (e.isDestroyed) { return } // fix https://github.com/cycleccc/wangEditor-v5/issues/457 const textarea = DomEditor.getTextarea(e) + textarea.destroy() EDITOR_TO_TEXTAREA.delete(e) TEXTAREA_TO_EDITOR.delete(textarea) const toolbar = DomEditor.getToolbar(e) + if (toolbar) { toolbar.destroy() EDITOR_TO_TOOLBAR.delete(e) @@ -101,6 +111,7 @@ export const withDOM = <T extends Editor>(editor: T) => { } const hoverbar = DomEditor.getHoverbar(e) + if (hoverbar) { hoverbar.destroy() EDITOR_TO_HOVER_BAR.delete(e) @@ -117,21 +128,26 @@ export const withDOM = <T extends Editor>(editor: T) => { // scroll to elem e.scrollToElem = (id: string) => { const { scroll } = e.getConfig() + if (!scroll) { // 没有设置编辑区域滚动,则不能用 let info = '编辑器禁用了 scroll ,编辑器内容无法滚动,请自行实现该功能' + info += '\nYou has disabled editor scroll, please do this yourself' console.warn(info) return } const $elem = $(`#${id}`) - if ($elem.length === 0) return + + if ($elem.length === 0) { return } // $elem 不在 editor DOM 范围之内 const elem = $elem[0] + if (!DomEditor.hasDOMNode(e, elem)) { let info = `Element (found by id is '${id}') is not in editor DOM` + info += `\n 通过 id '${id}' 找到的 element 不在 editor DOM 之内` console.error(info, elem) return @@ -150,22 +166,25 @@ export const withDOM = <T extends Editor>(editor: T) => { // showProgressBar e.showProgressBar = (progress: number) => { // progress 值范围: 0 - 100 - if (progress < 1) return + if (progress < 1) { return } // 显示进度条 const textarea = DomEditor.getTextarea(e) + textarea.changeProgress(progress) } // 隐藏 panel 或 modal e.hidePanelOrModal = () => { const set = EDITOR_TO_PANEL_AND_MODAL.get(e) - if (set == null) return + + if (set == null) { return } set.forEach(panelOrModal => panelOrModal.hide()) } e.enable = () => { const config = e.getConfig() + config.readOnly = false // 更新视图 @@ -174,6 +193,7 @@ export const withDOM = <T extends Editor>(editor: T) => { e.disable = () => { const config = e.getConfig() + config.readOnly = true // 更新视图 @@ -182,6 +202,7 @@ export const withDOM = <T extends Editor>(editor: T) => { e.isDisabled = () => { const config = e.getConfig() + return config.readOnly } @@ -190,10 +211,11 @@ export const withDOM = <T extends Editor>(editor: T) => { } e.fullScreen = () => { - if (e.isFullScreen) return + if (e.isFullScreen) { return } let $toolbarBox: Dom7Array | null = null const toolbar = DomEditor.getToolbar(e) + if (toolbar) { $toolbarBox = toolbar.$box } @@ -204,8 +226,8 @@ export const withDOM = <T extends Editor>(editor: T) => { if ($toolbarBox && $toolbarBox.parent()[0] !== $parent[0]) { // toolbar DOM 父节点,和 editor DOM 父节点不一致,则不能设置全屏 - let info = - 'Can not set full screen, cause toolbar DOM parent is not equal to textarea DOM parent' + let info = 'Can not set full screen, cause toolbar DOM parent is not equal to textarea DOM parent' + info += '\n不能设置全屏,因为 toolbar DOM 父节点和 textarea DOM 父节点不一致' throw new Error(info) } @@ -215,6 +237,7 @@ export const withDOM = <T extends Editor>(editor: T) => { // 设置 z-index const curZIndex = $parent.css('z-index') + $parent.attr('data-z-index', curZIndex.toString()) // 记录属性 @@ -225,7 +248,7 @@ export const withDOM = <T extends Editor>(editor: T) => { } e.unFullScreen = () => { - if (!e.isFullScreen) return + if (!e.isFullScreen) { return } const textarea = DomEditor.getTextarea(e) const $textAreaBox = textarea.$box @@ -249,6 +272,7 @@ export const withDOM = <T extends Editor>(editor: T) => { */ e.getEditableContainer = () => { const textarea = DomEditor.getTextarea(e) + return textarea.$textAreaContainer[0] } diff --git a/packages/core/src/editor/plugins/with-emitter.ts b/packages/core/src/editor/plugins/with-emitter.ts index cb77cbabc..c175290aa 100644 --- a/packages/core/src/editor/plugins/with-emitter.ts +++ b/packages/core/src/editor/plugins/with-emitter.ts @@ -5,8 +5,9 @@ import ee, { Emitter } from 'event-emitter' import { Editor } from 'slate' -import { IDomEditor } from '../interface' + import { EDITOR_TO_EMITTER } from '../../utils/weak-maps' +import { IDomEditor } from '../interface' /** * 获取 editor 的 emitter 实例 @@ -14,6 +15,7 @@ import { EDITOR_TO_EMITTER } from '../../utils/weak-maps' */ function getEmitter(editor: IDomEditor): Emitter { let emitter = EDITOR_TO_EMITTER.get(editor) + if (emitter == null) { emitter = ee() EDITOR_TO_EMITTER.set(editor, emitter) @@ -23,8 +25,10 @@ function getEmitter(editor: IDomEditor): Emitter { // 记录下当前 editor 的 destroy listeners const EDITOR_TO_DESTROY_LISTENERS: WeakMap<IDomEditor, Set<Function>> = new WeakMap() + function recordDestroyListeners(editor: IDomEditor, fn: Function) { let listeners = EDITOR_TO_DESTROY_LISTENERS.get(editor) + if (listeners == null) { listeners = new Set<Function>() EDITOR_TO_DESTROY_LISTENERS.set(editor, listeners) @@ -49,30 +53,35 @@ export const withEmitter = <T extends Editor>(editor: T) => { emitter.on(type, listener) // destroyed 事件需要记录下来,以便最后统一 off 掉 - if (type === 'destroyed') recordDestroyListeners(e, listener) + if (type === 'destroyed') { recordDestroyListeners(e, listener) } // editor 销毁时,取消绑定 - 重要 if (type !== 'destroyed') { const fn = () => emitter.off(type, listener) + emitter.on('destroyed', fn) recordDestroyListeners(e, fn) // 记录下来 } } e.once = (type, listener) => { const emitter = getEmitter(e) + emitter.once(type, listener) } e.off = (type, listener) => { const emitter = getEmitter(e) + emitter.off(type, listener) } e.emit = (type, ...args: any[]) => { const emitter = getEmitter(e) + emitter.emit(type, ...args) // editor 销毁时,off 掉 destroyed listeners if (type === 'destroyed') { const listeners = getDestroyListeners(e) + listeners.forEach(fn => emitter.off('destroyed', fn as ee.EventListener)) clearDestroyListeners(e) // 清空 destroyed listeners } diff --git a/packages/core/src/editor/plugins/with-event-data.ts b/packages/core/src/editor/plugins/with-event-data.ts index a3c7e8bae..356b114b0 100644 --- a/packages/core/src/editor/plugins/with-event-data.ts +++ b/packages/core/src/editor/plugins/with-event-data.ts @@ -3,11 +3,13 @@ * @author wangfupeng */ -import { Editor, Node, Transforms, Range } from 'slate' -import { DomEditor } from '../dom-editor' -import { IDomEditor } from '../..' +import { + Editor, Node, Range, Transforms, +} from 'slate' -import { isDOMText, getPlainText } from '../../utils/dom' +import { IDomEditor } from '../..' +import { getPlainText, isDOMText } from '../../utils/dom' +import { DomEditor } from '../dom-editor' export const withEventData = <T extends Editor>(editor: T) => { const e = editor as T & IDomEditor @@ -49,6 +51,7 @@ export const withEventData = <T extends Editor>(editor: T) => { const [voidNode] = endVoid const r = domRange.cloneRange() const domNode = DomEditor.toDOMNode(e, voidNode) + r.setEndAfter(domNode) contents = r.cloneContents() } @@ -65,6 +68,7 @@ export const withEventData = <T extends Editor>(editor: T) => { // show up elsewhere when pasted. Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => { const isNewline = zw.getAttribute('data-slate-zero-width') === 'n' + zw.textContent = isNewline ? '\n' : '' }) @@ -75,6 +79,7 @@ export const withEventData = <T extends Editor>(editor: T) => { const span = attach.ownerDocument.createElement('span') // COMPAT: In Chrome and Safari, if we don't add the `white-space` style // then leading and trailing spaces will be ignored. (2017/09/21) + span.style.whiteSpace = 'pre' span.appendChild(attach) contents.appendChild(span) @@ -84,11 +89,13 @@ export const withEventData = <T extends Editor>(editor: T) => { const fragment = e.getFragment() const string = JSON.stringify(fragment) const encoded = window.btoa(encodeURIComponent(string)) + attach.setAttribute('data-slate-fragment', encoded) data.setData('application/x-slate-fragment', encoded) // Add the content to a <div> so that we can get its inner HTML. const div = contents.ownerDocument.createElement('div') + div.appendChild(contents) div.setAttribute('hidden', 'true') contents.ownerDocument.body.appendChild(div) @@ -102,9 +109,11 @@ export const withEventData = <T extends Editor>(editor: T) => { e.insertData = (data: DataTransfer) => { const fragment = data.getData('application/x-slate-fragment') // 只有从编辑器中内复制的内容,才会获取 fragment,从其他地方粘贴到编辑器中,不会获取 fragment + if (fragment) { const decoded = decodeURIComponent(window.atob(fragment)) const parsed = JSON.parse(decoded) as Node[] + e.insertFragment(parsed) return } @@ -119,6 +128,7 @@ export const withEventData = <T extends Editor>(editor: T) => { } const leftLength = DomEditor.getLeftLengthOfMaxLength(e) + if (text) { const lines = text.split(/\r\n|\r|\n/) let split = false @@ -132,7 +142,7 @@ export const withEventData = <T extends Editor>(editor: T) => { e.insertText(line) split = true } - return + } } diff --git a/packages/core/src/editor/plugins/with-max-length.ts b/packages/core/src/editor/plugins/with-max-length.ts index ae50593f6..4ebb7913a 100644 --- a/packages/core/src/editor/plugins/with-max-length.ts +++ b/packages/core/src/editor/plugins/with-max-length.ts @@ -3,26 +3,31 @@ * @author wangfupeng */ -//【注意】拼音输入时 maxLength 限制在 CompositionEnd 事件中处理 +// 【注意】拼音输入时 maxLength 限制在 CompositionEnd 事件中处理 import { Editor, Node } from 'slate' -import { IDomEditor, DomEditor } from '../..' + +import { DomEditor, IDomEditor } from '../..' import { IGNORE_TAGS } from '../../constants' import { NodeType } from '../../utils/dom' export const withMaxLength = <T extends Editor>(editor: T) => { const e = editor as T & IDomEditor - const { insertText, insertNode, insertFragment, dangerouslyInsertHtml } = e + const { + insertText, insertNode, insertFragment, dangerouslyInsertHtml, + } = e // 处理 text e.insertText = (text: string) => { const { maxLength } = e.getConfig() + if (!maxLength) { insertText(text) return } const leftLength = DomEditor.getLeftLengthOfMaxLength(e) + if (leftLength <= 0) { // 已经触发 maxLength ,不再输入文字 return @@ -40,18 +45,21 @@ export const withMaxLength = <T extends Editor>(editor: T) => { // 处理 node e.insertNode = (node: Node) => { const { maxLength } = e.getConfig() + if (!maxLength) { insertNode(node) return } const leftLength = DomEditor.getLeftLengthOfMaxLength(e) + if (leftLength <= 0) { // 已经触发 maxLength ,不再插入 return } const text = Node.string(node) + if (leftLength < text.length) { // 剩余长度,不够 node text 长度,不再插入 return @@ -63,6 +71,7 @@ export const withMaxLength = <T extends Editor>(editor: T) => { // 处理 fragment e.insertFragment = (fragment: Node[]) => { const { maxLength } = e.getConfig() + if (!maxLength) { // 无 maxLength insertFragment(fragment) @@ -90,16 +99,18 @@ export const withMaxLength = <T extends Editor>(editor: T) => { } } - e.dangerouslyInsertHtml = (html: string = '', isRecursive = false) => { - if (!html) return + e.dangerouslyInsertHtml = (html = '', isRecursive = false) => { + if (!html) { return } const { maxLength } = e.getConfig() + if (!maxLength) { // 无 maxLength dangerouslyInsertHtml(html, isRecursive) return } const leftLength = DomEditor.getLeftLengthOfMaxLength(e) + if (leftLength <= 0) { // 已经触发 maxLength ,不再输入文字 return @@ -107,20 +118,22 @@ export const withMaxLength = <T extends Editor>(editor: T) => { // ------------- 把 html 转换为 DOM nodes ------------- const div = document.createElement('div') + div.innerHTML = html const text = Array.from(div.childNodes).reduce<string>((acc, node) => { const { nodeType, nodeName } = node + if (!node) { return acc } // Text Node - if (nodeType === NodeType.TEXT_NODE) return acc + (node.textContent || '') + if (nodeType === NodeType.TEXT_NODE) { return acc + (node.textContent || '') } // Element Node if (nodeType === NodeType.ELEMENT_NODE) { // 过滤掉忽略的 tag - if (IGNORE_TAGS.has(nodeName.toLowerCase())) return acc - else return acc + (node.textContent || '') + if (IGNORE_TAGS.has(nodeName.toLowerCase())) { return acc } + return acc + (node.textContent || '') } return acc }, '') diff --git a/packages/core/src/editor/plugins/with-selection.ts b/packages/core/src/editor/plugins/with-selection.ts index 7b53902fe..b855369fb 100644 --- a/packages/core/src/editor/plugins/with-selection.ts +++ b/packages/core/src/editor/plugins/with-selection.ts @@ -3,11 +3,14 @@ * @author wangfupeng */ -import { Editor, Transforms, Location, Node, Range, Point } from 'slate' -import { IDomEditor } from '../interface' -import { DomEditor } from '../dom-editor' +import { + Editor, Location, Node, Point, Range, Transforms, +} from 'slate' + import { getPositionByNode, getPositionBySelection } from '../../menus/helpers/position' import { EDITOR_TO_SELECTION } from '../../utils/weak-maps' +import { DomEditor } from '../dom-editor' +import { IDomEditor } from '../interface' export const withSelection = <T extends Editor>(editor: T) => { const e = editor as T & IDomEditor @@ -34,8 +37,8 @@ export const withSelection = <T extends Editor>(editor: T) => { // 移动光标 e.move = (distance: number, reverse = false) => { - if (!distance) return - if (distance < 0) return + if (!distance) { return } + if (distance < 0) { return } Transforms.move(editor, { distance, @@ -54,7 +57,8 @@ export const withSelection = <T extends Editor>(editor: T) => { */ e.restoreSelection = () => { const selection = EDITOR_TO_SELECTION.get(e) - if (selection == null) return + + if (selection == null) { return } e.focus() Transforms.select(e, selection) @@ -79,7 +83,8 @@ export const withSelection = <T extends Editor>(editor: T) => { */ e.isSelectedAll = () => { const { selection } = e - if (selection == null) return false + + if (selection == null) { return false } const [start1, end1] = Range.edges(selection) // 获取当前选取的开始、结束 point const [start2, end2] = Editor.edges(e, []) // 获取编辑器全部的开始、结束 point diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 600732794..4033c16c7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -5,11 +5,11 @@ import './assets/index.less' -import { RenderStyleFnType, IRenderElemConf } from './render/index' -import { styleToHtmlFnType, IElemToHtmlConf } from './to-html/index' -import { IPreParseHtmlConf, ParseStyleHtmlFnType, IParseElemHtmlConf } from './parse-html/index' -import { IRegisterMenuConf } from './menus/index' import { IDomEditor } from './editor/interface' +import { IRegisterMenuConf } from './menus/index' +import { IParseElemHtmlConf, IPreParseHtmlConf, ParseStyleHtmlFnType } from './parse-html/index' +import { IRenderElemConf, RenderStyleFnType } from './render/index' +import { IElemToHtmlConf, styleToHtmlFnType } from './to-html/index' // 创建 export * from './create/index' @@ -18,8 +18,8 @@ export * from './create/index' export { IEditorConfig, IToolbarConfig } from './config/interface' // editor 接口和 command -export * from './editor/interface' export * from './editor/dom-editor' +export * from './editor/interface' // 注册 render export * from './render/index' diff --git a/packages/core/src/menus/bar-item/BaseButton.ts b/packages/core/src/menus/bar-item/BaseButton.ts index 94f842b93..05922ab46 100644 --- a/packages/core/src/menus/bar-item/BaseButton.ts +++ b/packages/core/src/menus/bar-item/BaseButton.ts @@ -3,17 +3,20 @@ * @author wangfupeng */ -import { IButtonMenu, IDropPanelMenu, IModalMenu } from '../interface' import $, { Dom7Array } from '../../utils/dom' -import { IBarItem, getEditorInstance } from './index' -import { clearSvgStyle } from '../helpers/helpers' import { promiseResolveThen } from '../../utils/util' +import { clearSvgStyle } from '../helpers/helpers' +import { IButtonMenu, IDropPanelMenu, IModalMenu } from '../interface' +import { getEditorInstance, IBarItem } from './index' import { addTooltip } from './tooltip' abstract class BaseButton implements IBarItem { - readonly $elem: Dom7Array = $(`<div class="w-e-bar-item"></div>`) - protected readonly $button: Dom7Array = $(`<button type="button"></button>`) + readonly $elem: Dom7Array = $('<div class="w-e-bar-item"></div>') + + protected readonly $button: Dom7Array = $('<button type="button"></button>') + menu: IButtonMenu | IDropPanelMenu | IModalMenu + private disabled = false constructor(key: string, menu: IButtonMenu | IDropPanelMenu | IModalMenu, inGroup = false) { @@ -21,13 +24,16 @@ abstract class BaseButton implements IBarItem { // 验证 tag const { tag, width } = menu - if (tag !== 'button') throw new Error(`Invalid tag '${tag}', expected 'button'`) + + if (tag !== 'button') { throw new Error(`Invalid tag '${tag}', expected 'button'`) } // ----------------- 初始化 dom ----------------- const { title, hotkey = '', iconSvg = '' } = menu const { $button } = this + if (iconSvg) { const $svg = $(iconSvg) + clearSvgStyle($svg) // 清理 svg 样式(扩展的菜单,svg 是不可控的,所以要清理一下) $button.append($svg) } else { @@ -62,7 +68,7 @@ abstract class BaseButton implements IBarItem { editor.hidePanelOrModal() // 隐藏当前的各种 panel - if (this.disabled) return + if (this.disabled) { return } this.exec() // 执行 menu.exec this.onButtonClick() // 执行其他的逻辑 @@ -76,6 +82,7 @@ abstract class BaseButton implements IBarItem { const editor = getEditorInstance(this) const menu = this.menu const value = menu.getValue(editor) + this.setIcon() this.setTooltip() menu.exec(editor, value) @@ -90,6 +97,7 @@ abstract class BaseButton implements IBarItem { const active = this.menu.isActive(editor) const className = 'active' + if (active) { // 设置为 active $button.addClass(className) @@ -110,9 +118,10 @@ abstract class BaseButton implements IBarItem { } // 永远 enable - if (this.menu.alwaysEnable) disabled = false + if (this.menu.alwaysEnable) { disabled = false } const className = 'disabled' + if (disabled) { // 设置为 disabled $button.addClass(className) @@ -127,12 +136,14 @@ abstract class BaseButton implements IBarItem { private setIcon() { const editor = getEditorInstance(this) const { $button } = this - if (!this.menu.getIcon) return + + if (!this.menu.getIcon) { return } const iconSvg = this.menu.getIcon(editor) if (iconSvg) { $button.find('svg').remove() const $svg = $(iconSvg) + clearSvgStyle($svg) $button.append($svg) } @@ -141,9 +152,11 @@ abstract class BaseButton implements IBarItem { private setTooltip() { const editor = getEditorInstance(this) const { $button } = this - if (!this.menu.getTitle) return + + if (!this.menu.getTitle) { return } const title = this.menu.getTitle(editor) const iconSvg = this.menu.iconSvg + if (title && iconSvg) { addTooltip($button, iconSvg, title) } diff --git a/packages/core/src/menus/bar-item/DropPanelButton.ts b/packages/core/src/menus/bar-item/DropPanelButton.ts index 10c126193..745377392 100644 --- a/packages/core/src/menus/bar-item/DropPanelButton.ts +++ b/packages/core/src/menus/bar-item/DropPanelButton.ts @@ -3,14 +3,15 @@ * @author wangfupeng */ +import { gen$downArrow } from '../helpers/helpers' import { IDropPanelMenu } from '../interface' -import BaseButton from './BaseButton' import DropPanel from '../panel-and-modal/DropPanel' -import { gen$downArrow } from '../helpers/helpers' +import BaseButton from './BaseButton' import { getEditorInstance } from './index' class DropPanelButton extends BaseButton { private dropPanel: DropPanel | null = null + menu: IDropPanelMenu constructor(key: string, menu: IDropPanelMenu, inGroup = false) { @@ -19,6 +20,7 @@ class DropPanelButton extends BaseButton { if (menu.showDropPanel) { const $arrow = gen$downArrow() + this.$button.append($arrow) } } @@ -33,13 +35,15 @@ class DropPanelButton extends BaseButton { // 显示/隐藏 dropPanel private handleDropPanel() { const menu = this.menu - if (menu.getPanelContentElem == null) return + + if (menu.getPanelContentElem == null) { return } const editor = getEditorInstance(this) if (this.dropPanel == null) { // 初次创建 const dropPanel = new DropPanel(editor) const contentElem = menu.getPanelContentElem(editor) + dropPanel.renderContent(contentElem) dropPanel.appendTo(this.$elem) dropPanel.show() @@ -49,12 +53,14 @@ class DropPanelButton extends BaseButton { } else { // 不是初次创建 const dropPanel = this.dropPanel + if (dropPanel.isShow) { // 当前处于显示状态,则隐藏 dropPanel.hide() } else { // 当前未处于显示状态,则重新渲染内容 ,并显示 const contentElem = menu.getPanelContentElem(editor) + dropPanel.renderContent(contentElem) dropPanel.show() } @@ -62,6 +68,7 @@ class DropPanelButton extends BaseButton { // 判断 dropPanel 的位置:在菜单右侧/左侧 const dropPanel = this.dropPanel + if (dropPanel.isShow) { const $menu = this.$elem const { left } = $menu.offset() // 菜单元素 left diff --git a/packages/core/src/menus/bar-item/GroupButton.ts b/packages/core/src/menus/bar-item/GroupButton.ts index a9d8d3cb1..905233739 100644 --- a/packages/core/src/menus/bar-item/GroupButton.ts +++ b/packages/core/src/menus/bar-item/GroupButton.ts @@ -3,23 +3,26 @@ * @author wangfupeng */ -import { gen$downArrow } from '../helpers/helpers' import $, { Dom7Array } from '../../utils/dom' +import { clearSvgStyle, gen$downArrow } from '../helpers/helpers' import { IMenuGroup } from '../interface' -import { clearSvgStyle } from '../helpers/helpers' import { IBarItem } from './index' + class GroupButton { - readonly $elem: Dom7Array = $(`<div class="w-e-bar-item w-e-bar-item-group"></div>`) + readonly $elem: Dom7Array = $('<div class="w-e-bar-item w-e-bar-item-group"></div>') + private readonly $container: Dom7Array = $('<div class="w-e-bar-item-menus-container"></div>') - readonly $button = $(`<button type="button"></button>`) + + readonly $button = $('<button type="button"></button>') constructor(menu: IMenuGroup) { - const { key, iconSvg, title /*, menuKeys = [] */ } = menu + const { key, iconSvg, title /* , menuKeys = [] */ } = menu const { $elem, $button } = this // button if (iconSvg) { const $svg = $(iconSvg) + clearSvgStyle($svg) // 清理 svg 样式(扩展的菜单,svg 是不可控的,所以要清理一下) $button.append($svg) } else { @@ -29,25 +32,30 @@ class GroupButton { $button.attr('data-menu-key', key) // menu key const $arrow = gen$downArrow() + $button.append($arrow) $elem.append($button) // menu container const { $container } = this + $elem.append($container) // 监听 container 内容变化,以判断 $button 是否应该禁用 const observer = this.createObserver() + this.observe(observer) } appendBarItem(barItem: IBarItem) { const { $elem } = barItem + this.$container.append($elem) } private observe(observer: MutationObserver) { const { $container } = this + observer.observe($container[0], { childList: true, subtree: true, attributes: true }) } @@ -58,12 +66,15 @@ class GroupButton { // 找出 container 下所有的 button const $buttons = $container.find('button') const buttonsLength = $buttons.length - if (buttonsLength === 0) return + + if (buttonsLength === 0) { return } // 找出所有 disabled 的 button let disabledButtonsLength = 0 + $buttons.each(btn => { const $btn = $(btn) + if ($btn.hasClass('disabled')) { disabledButtonsLength++ } diff --git a/packages/core/src/menus/bar-item/ModalButton.ts b/packages/core/src/menus/bar-item/ModalButton.ts index 999032152..4d0afd8a5 100644 --- a/packages/core/src/menus/bar-item/ModalButton.ts +++ b/packages/core/src/menus/bar-item/ModalButton.ts @@ -4,17 +4,20 @@ */ import { Element } from 'slate' + +import { DomEditor } from '../../editor/dom-editor' +import $ from '../../utils/dom' +import { correctPosition, getPositionByNode, getPositionBySelection } from '../helpers/position' import { IModalMenu, IPositionStyle } from '../interface' -import BaseButton from './BaseButton' import Modal from '../panel-and-modal/Modal' +import BaseButton from './BaseButton' import { getEditorInstance } from './index' -import { getPositionBySelection, getPositionByNode, correctPosition } from '../helpers/position' -import { DomEditor } from '../../editor/dom-editor' -import $ from '../../utils/dom' class ModalButton extends BaseButton { private $body = $('body') + private modal: Modal | null = null + menu: IModalMenu constructor(key: string, menu: IModalMenu, inGroup = false) { @@ -52,6 +55,7 @@ class ModalButton extends BaseButton { if (this.modal == null) { // 初次创建 const modal = new Modal(editor, menu.modalWidth) + this.renderAndShowModal(modal, true) // 记录下来,防止重复创建 @@ -59,6 +63,7 @@ class ModalButton extends BaseButton { } else { // 不是初次创建 const modal = this.modal + if (modal.isShow) { // 当前处于显示状态,则隐藏 modal.hide() @@ -74,16 +79,18 @@ class ModalButton extends BaseButton { * @param modal modal * @param firstTime 是否第一次显示 modal */ - private renderAndShowModal(modal: Modal, firstTime: boolean = false) { + private renderAndShowModal(modal: Modal, firstTime = false) { const editor = getEditorInstance(this) const menu = this.menu - if (menu.getModalContentElem == null) return + + if (menu.getModalContentElem == null) { return } const textarea = DomEditor.getTextarea(editor) const toolbar = DomEditor.getToolbar(editor) const { modalAppendToBody } = toolbar?.getConfig() || {} const contentElem = menu.getModalContentElem(editor) + modal.renderContent(contentElem) if (modalAppendToBody) { @@ -92,6 +99,7 @@ class ModalButton extends BaseButton { } else { // 计算并设置 modal position const positionStyle = this.getPosition() + modal.setStyle(positionStyle) } diff --git a/packages/core/src/menus/bar-item/Select.ts b/packages/core/src/menus/bar-item/Select.ts index f5d3f1d0e..5c00e7f89 100644 --- a/packages/core/src/menus/bar-item/Select.ts +++ b/packages/core/src/menus/bar-item/Select.ts @@ -4,19 +4,21 @@ */ import $, { Dom7Array } from '../../utils/dom' -import { IBarItem, getEditorInstance } from './index' +import { promiseResolveThen } from '../../utils/util' +import { gen$downArrow } from '../helpers/helpers' import { IOption, ISelectMenu } from '../interface' import SelectList from '../panel-and-modal/SelectList' -import { gen$downArrow } from '../helpers/helpers' -import { promiseResolveThen } from '../../utils/util' +import { getEditorInstance, IBarItem } from './index' import { addTooltip } from './tooltip' // 根据 option value 获取 text function getOptionText(options: IOption[], value: string): string { const length = options.length let text = '' + for (let i = 0; i < length; i++) { const opt = options[i] + if (opt.value === value) { text = opt.text break @@ -26,19 +28,27 @@ function getOptionText(options: IOption[], value: string): string { } class BarItemSelect implements IBarItem { - readonly $elem: Dom7Array = $(`<div class="w-e-bar-item"></div>`) - private readonly $button: Dom7Array = $(`<button type="button" class="select-button"></button>`) + readonly $elem: Dom7Array = $('<div class="w-e-bar-item"></div>') + + private readonly $button: Dom7Array = $('<button type="button" class="select-button"></button>') + menu: ISelectMenu + private disabled = false + private selectList: SelectList | null = null constructor(key: string, menu: ISelectMenu, inGroup = false) { // 验证 tag - const { tag, title, width, iconSvg = '', hotkey = '' } = menu - if (tag !== 'select') throw new Error(`Invalid tag '${tag}', expected 'select'`) + const { + tag, title, width, iconSvg = '', hotkey = '', + } = menu + + if (tag !== 'select') { throw new Error(`Invalid tag '${tag}', expected 'select'`) } // 初始化 dom const $button = this.$button + if (width) { $button.css('width', `${width}px`) } @@ -60,6 +70,7 @@ class BarItemSelect implements IBarItem { this.$button.on('click', (e: Event) => { e.preventDefault() const editor = getEditorInstance(this) + editor.hidePanelOrModal() // 隐藏当前的各种 panel this.trigger() }) @@ -68,8 +79,8 @@ class BarItemSelect implements IBarItem { private trigger() { const editor = getEditorInstance(this) - if (editor.isDisabled()) return - if (this.disabled) return + if (editor.isDisabled()) { return } + if (this.disabled) { return } const menu = this.menu @@ -79,6 +90,7 @@ class BarItemSelect implements IBarItem { this.selectList = new SelectList(editor, menu.selectPanelWidth) const selectList = this.selectList const options = menu.getOptions(editor) + selectList.renderList(options) selectList.appendTo(this.$elem) selectList.show() @@ -86,22 +98,26 @@ class BarItemSelect implements IBarItem { // 初次创建,绑定事件 selectList.$elem.on('click', 'li', (e: Event) => { const { target } = e - if (target == null) return + + if (target == null) { return } e.preventDefault() const $li = $(target) const val = $li.attr('data-value') + this.onChange(val) }) } else { // 不是初次创建 const selectList = this.selectList + if (selectList.isShow) { // 当前处于显示状态,则隐藏 selectList.hide() } else { // 当前未处于显示状态,则重新渲染 list ,并显示 const options = menu.getOptions(editor) // 每次都要重新获取 options ,因为选中项可能会变化 + selectList.renderList(options) selectList.show() } @@ -111,6 +127,7 @@ class BarItemSelect implements IBarItem { private onChange(value: string) { const editor = getEditorInstance(this) const menu = this.menu + menu.exec && menu.exec(editor, value) } @@ -124,6 +141,7 @@ class BarItemSelect implements IBarItem { const $button = this.$button const $downArrow = gen$downArrow() // 向下的箭头图标 + $button.empty() $button.text(optText) $button.append($downArrow) @@ -141,6 +159,7 @@ class BarItemSelect implements IBarItem { } const className = 'disabled' + if (disabled) { // 设置为 disabled $button.addClass(className) diff --git a/packages/core/src/menus/bar-item/SimpleButton.ts b/packages/core/src/menus/bar-item/SimpleButton.ts index 0ee7180a3..99e40798c 100644 --- a/packages/core/src/menus/bar-item/SimpleButton.ts +++ b/packages/core/src/menus/bar-item/SimpleButton.ts @@ -10,6 +10,7 @@ class SimpleButton extends BaseButton { constructor(key: string, menu: IButtonMenu, inGroup = false) { super(key, menu, inGroup) } + onButtonClick() { // menu.exec 已经在 BaseButton 实现了 // 所以,此处不用做任何逻辑 diff --git a/packages/core/src/menus/bar-item/index.ts b/packages/core/src/menus/bar-item/index.ts index 2c892475e..23614d8dc 100644 --- a/packages/core/src/menus/bar-item/index.ts +++ b/packages/core/src/menus/bar-item/index.ts @@ -3,15 +3,17 @@ * @author wangfupeng */ -import { Dom7Array } from '../../utils/dom' -import { IButtonMenu, ISelectMenu, IDropPanelMenu, IModalMenu, IMenuGroup } from '../interface' import { IDomEditor } from '../../editor/interface' +import { Dom7Array } from '../../utils/dom' import { BAR_ITEM_TO_EDITOR } from '../../utils/weak-maps' -import SimpleButton from './SimpleButton' +import { + IButtonMenu, IDropPanelMenu, IMenuGroup, IModalMenu, ISelectMenu, +} from '../interface' import DropPanelButton from './DropPanelButton' +import GroupButton from './GroupButton' import ModalButton from './ModalButton' import Select from './Select' -import GroupButton from './GroupButton' +import SimpleButton from './SimpleButton' type MenuType = IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu @@ -26,7 +28,8 @@ const MENU_TO_BAR_ITEM = new WeakMap<MenuType, IBarItem>() export function getEditorInstance(item: IBarItem): IDomEditor { const editor = BAR_ITEM_TO_EDITOR.get(item) - if (editor == null) throw new Error('Can not get editor instance') + + if (editor == null) { throw new Error('Can not get editor instance') } return editor } @@ -36,16 +39,19 @@ export function getEditorInstance(item: IBarItem): IDomEditor { * @param menu menu * @param inGroup 在 groupButton 中 */ -export function createBarItem(key: string, menu: MenuType, inGroup: boolean = false): IBarItem { +export function createBarItem(key: string, menu: MenuType, inGroup = false): IBarItem { // 尝试从缓存获取 let barItem = MENU_TO_BAR_ITEM.get(menu) - if (barItem) return barItem + + if (barItem) { return barItem } // 缓存没有则创建 const { tag } = menu + if (tag === 'button') { // @ts-ignore const { showDropPanel, showModal } = menu + if (showDropPanel) { barItem = new DropPanelButton(key, menu as IDropPanelMenu, inGroup) } else if (showModal) { @@ -58,7 +64,7 @@ export function createBarItem(key: string, menu: MenuType, inGroup: boolean = fa barItem = new Select(key, menu as ISelectMenu, inGroup) } - if (barItem == null) throw new Error(`Invalid tag in menu ${JSON.stringify(menu)}`) + if (barItem == null) { throw new Error(`Invalid tag in menu ${JSON.stringify(menu)}`) } // 记录缓存 MENU_TO_BAR_ITEM.set(menu, barItem) diff --git a/packages/core/src/menus/bar-item/tooltip.ts b/packages/core/src/menus/bar-item/tooltip.ts index 585b5301f..ccdfe0602 100644 --- a/packages/core/src/menus/bar-item/tooltip.ts +++ b/packages/core/src/menus/bar-item/tooltip.ts @@ -10,8 +10,8 @@ export function addTooltip( $button: Dom7Array, iconSvg: string, title: string, - hotkey: string = '', - inGroup = false + hotkey = '', + inGroup = false, ) { if (!iconSvg) { // 没有 icon 直接显示 title ,不用 tooltip @@ -20,6 +20,7 @@ export function addTooltip( if (hotkey) { const fnKey = IS_APPLE ? 'cmd' : 'ctrl' // mac OS 转换为 cmd ,windows 转换为 ctrl + hotkey = hotkey.replace('mod', fnKey) } @@ -33,6 +34,7 @@ export function addTooltip( } else { // 非 in groupButton ,正常实现 tooltip const tooltip = hotkey ? `${title}\n${hotkey}` : title + $button.attr('data-tooltip', tooltip) $button.addClass('w-e-menu-tooltip-v5') } diff --git a/packages/core/src/menus/bar/HoverBar.ts b/packages/core/src/menus/bar/HoverBar.ts index 694bed14f..a44b83156 100644 --- a/packages/core/src/menus/bar/HoverBar.ts +++ b/packages/core/src/menus/bar/HoverBar.ts @@ -4,19 +4,24 @@ */ import debounce from 'lodash.debounce' -import { Editor, Node, Element, Text, Path, Range } from 'slate' +import { + Editor, Element, Node, Path, Range, Text, +} from 'slate' + +import { CustomElement } from '../../../../custom-types' +import { DomEditor } from '../../editor/dom-editor' +import { IDomEditor } from '../../editor/interface' +import { i18nListenLanguage } from '../../i18n' import $ from '../../utils/dom' -import { MENU_ITEM_FACTORIES } from '../register' import { promiseResolveThen } from '../../utils/util' -import { IDomEditor } from '../../editor/interface' -import { DomEditor } from '../../editor/dom-editor' -import { HOVER_BAR_TO_EDITOR, BAR_ITEM_TO_EDITOR } from '../../utils/weak-maps' -import { IBarItem, createBarItem } from '../bar-item/index' +import { BAR_ITEM_TO_EDITOR, HOVER_BAR_TO_EDITOR } from '../../utils/weak-maps' +import { createBarItem, IBarItem } from '../bar-item/index' import { gen$barItemDivider } from '../helpers/helpers' -import { getPositionBySelection, getPositionByNode, correctPosition } from '../helpers/position' -import { IButtonMenu, ISelectMenu, IDropPanelMenu, IModalMenu } from '../interface' -import { CustomElement } from '../../../../custom-types' -import { i18nListenLanguage } from '../../i18n' +import { correctPosition, getPositionByNode, getPositionBySelection } from '../helpers/position' +import { + IButtonMenu, IDropPanelMenu, IModalMenu, ISelectMenu, +} from '../interface' +import { MENU_ITEM_FACTORIES } from '../register' type MenuType = IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu @@ -27,28 +32,36 @@ type MenuType = IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu */ function isSelectedText(editor: IDomEditor, n: Node) { const { selection } = editor - if (selection == null) return false // 无选区 - if (Range.isCollapsed(selection)) return false // 未选中文字,选区的是折叠的 + + if (selection == null) { return false } // 无选区 + if (Range.isCollapsed(selection)) { return false } // 未选中文字,选区的是折叠的 const selectedElems = DomEditor.getSelectedElems(editor) const notMatch = selectedElems.some((elem: CustomElement) => { - if (editor.isVoid(elem)) return true + if (editor.isVoid(elem)) { return true } const { type } = elem - if (['pre', 'code', 'table'].includes(type)) return true + + if (['pre', 'code', 'table'].includes(type)) { return true } }) - if (notMatch) return false - if (Text.isText(n)) return true // 匹配 text node + if (notMatch) { return false } + + if (Text.isText(n)) { return true } // 匹配 text node return false } class HoverBar { private readonly $elem = $('<div class="w-e-bar w-e-bar-hidden w-e-hover-bar"></div>') + private menus: { [key: string]: MenuType } = {} + private hoverbarItems: IBarItem[] = [] + private prevSelectedNode: Node | null = null // 上一次选中的 node + private isShow = false + private lngListen: () => void = () => {} constructor() { @@ -59,8 +72,10 @@ class HoverBar { // 将 elem 渲染为 DOM const $elem = this.$elem // @ts-ignore + $elem.on('mousedown', e => e.preventDefault(), { passive: false }) // 防止点击失焦 const textarea = DomEditor.getTextarea(editor) + textarea.$textAreaContainer.append($elem) // 绑定 editor onchange @@ -68,6 +83,7 @@ class HoverBar { // 滚动时隐藏 const hideAndClean = this.hideAndClean.bind(this) + editor.on('scroll', hideAndClean) // fullScreen 时隐藏 @@ -83,6 +99,7 @@ class HoverBar { this.hideAndClean() // xxx const editor = this.getEditorInstance() + editor.deselect() }) } @@ -93,6 +110,7 @@ class HoverBar { hideAndClean() { const $elem = this.$elem + $elem.removeClass('w-e-bar-show').addClass('w-e-bar-hidden') // 及时先清空内容,否则影响下次 @@ -112,8 +130,10 @@ class HoverBar { let isBottom = false const { innerHeight } = window const minDistance = 360 // 距离底部最小 360px + if (innerHeight && innerHeight >= minDistance) { const { bottom } = $elem[0].getBoundingClientRect() + if (innerHeight - bottom < minDistance) { // hoverbar 距离底部不足 360 isBottom = true @@ -149,6 +169,7 @@ class HoverBar { if (key === '|') { // 分割线 const $divider = gen$barItemDivider() + $elem.append($divider) return } @@ -169,6 +190,7 @@ class HoverBar { if (menu == null) { // 缓存获取失败,则重新创建 const factory = MENU_ITEM_FACTORIES[key] + if (factory == null) { throw new Error(`Not found menu item factory by key '${key}'`) } @@ -181,13 +203,15 @@ class HoverBar { menus[key] = menu } - //替换 icon svg + // 替换 icon svg const menuConf = editor.getMenuConfig(key) + if (menuConf && menuConf.iconSvg !== undefined) { menu.iconSvg = menuConf.iconSvg } const barItem = createBarItem(key, menu) + this.hoverbarItems.push(barItem) // 保存 barItem 和 editor 的关系 @@ -195,17 +219,20 @@ class HoverBar { // 添加 DOM const $elem = this.$elem + $elem.append(barItem.$elem) } private setPosition(node: Node) { const editor = this.getEditorInstance() const $elem = this.$elem + $elem.attr('style', '') // 先清空 style ,再重新设置 if (Element.isElement(node)) { // 根据 elem node 定位 const positionStyle = getPositionByNode(editor, node, 'bar') + $elem.css(positionStyle) correctPosition(editor, $elem) // 修正 position 避免超出 textContainer 边界 return @@ -213,6 +240,7 @@ class HoverBar { if (Text.isText(node)) { // text node ,根据选区定位 const positionStyle = getPositionBySelection(editor) + $elem.css(positionStyle) correctPosition(editor, $elem) // 修正 position 避免超出 textContainer 边界 return @@ -242,9 +270,7 @@ class HoverBar { const { match, menuKeys = [] } = conf // 定义了 match 则用 match 。未定义 match 则用 elemType - const matchFn = match - ? match - : (editor: IDomEditor, n: Node) => DomEditor.checkNodeType(n, elemType) + const matchFn = match || ((editor: IDomEditor, n: Node) => DomEditor.checkNodeType(n, elemType)) const [nodeEntry] = Editor.nodes(editor, { match: n => matchFn(editor, n), @@ -260,7 +286,7 @@ class HoverBar { } // 未匹配成功 - if (matchNode == null || matchMenuKeys.length === 0) return null + if (matchNode == null || matchMenuKeys.length === 0) { return null } // 匹配成功 return { @@ -286,6 +312,7 @@ class HoverBar { if (isShow) { // hoverbar 当前已显示 const samePath = this.isSamePath(node, this.prevSelectedNode) + if (samePath) { // 和之前选中的 node path 相同 —— 满足这些条件,即终止 return @@ -309,7 +336,8 @@ class HoverBar { private getEditorInstance(): IDomEditor { const editor = HOVER_BAR_TO_EDITOR.get(this) - if (editor == null) throw new Error('Can not get editor instance') + + if (editor == null) { throw new Error('Can not get editor instance') } return editor } @@ -318,6 +346,7 @@ class HoverBar { const { hoverbarKeys = {} } = editor.getConfig() const textHoverbarKeys = hoverbarKeys.text + if (textHoverbarKeys && textHoverbarKeys.match == null) { // 对 text hoverbarKeys 增加 match 函数(否则无法判断是否选中了 text) textHoverbarKeys.match = isSelectedText @@ -337,6 +366,7 @@ class HoverBar { const path1 = DomEditor.findPath(null, node1) const path2 = DomEditor.findPath(null, node2) const res = Path.equals(path1, path2) + return res } diff --git a/packages/core/src/menus/bar/Toolbar.ts b/packages/core/src/menus/bar/Toolbar.ts index 7f886a961..76bf1c73c 100644 --- a/packages/core/src/menus/bar/Toolbar.ts +++ b/packages/core/src/menus/bar/Toolbar.ts @@ -3,28 +3,36 @@ * @author wangfupeng */ -import debounce from 'lodash.debounce' import clonedeep from 'lodash.clonedeep' +import debounce from 'lodash.debounce' + +import { IToolbarConfig } from '../../config/interface' +import { IDomEditor } from '../../editor/interface' +import { i18nListenLanguage } from '../../i18n' import $, { Dom7Array, DOMElement } from '../../utils/dom' -import { MENU_ITEM_FACTORIES } from '../register' import { promiseResolveThen } from '../../utils/util' -import { TOOLBAR_TO_EDITOR, BAR_ITEM_TO_EDITOR } from '../../utils/weak-maps' -import { IDomEditor } from '../../editor/interface' -import { IBarItem, createBarItem, createBarItemGroup } from '../bar-item/index' -import { gen$barItemDivider } from '../helpers/helpers' -import { IMenuGroup, IButtonMenu, ISelectMenu, IDropPanelMenu, IModalMenu } from '../interface' +import { BAR_ITEM_TO_EDITOR, TOOLBAR_TO_EDITOR } from '../../utils/weak-maps' import GroupButton from '../bar-item/GroupButton' -import { IToolbarConfig } from '../../config/interface' -import { i18nListenLanguage } from '../../i18n' +import { createBarItem, createBarItemGroup, IBarItem } from '../bar-item/index' +import { gen$barItemDivider } from '../helpers/helpers' +import { + IButtonMenu, IDropPanelMenu, IMenuGroup, IModalMenu, ISelectMenu, +} from '../interface' +import { MENU_ITEM_FACTORIES } from '../register' type MenuType = IButtonMenu | ISelectMenu | IDropPanelMenu | IModalMenu class Toolbar { $box: Dom7Array - private readonly $toolbar: Dom7Array = $(`<div class="w-e-bar w-e-bar-show w-e-toolbar"></div>`) + + private readonly $toolbar: Dom7Array = $('<div class="w-e-bar w-e-bar-show w-e-toolbar"></div>') + private menus: { [key: string]: MenuType } = {} + private toolbarItems: IBarItem[] = [] + private config: Partial<IToolbarConfig> = {} + private lngListen: () => void = () => {} constructor(boxSelector: string | DOMElement, config: Partial<IToolbarConfig>) { @@ -32,12 +40,14 @@ class Toolbar { // @ts-ignore 初始化 DOM const $box = $(boxSelector) + if ($box.length === 0) { throw new Error(`Cannot find toolbar DOM by selector '${boxSelector}'`) } this.$box = $box const $toolbar = this.$toolbar // @ts-ignore + $toolbar.on('mousedown', e => e.preventDefault(), { passive: false }) // 防止点击失焦 $box.append($toolbar) @@ -51,6 +61,7 @@ class Toolbar { // 监听 editor onchange const editor = this.getEditorInstance() + editor.on('change', this.changeToolbarState) }) } @@ -69,6 +80,7 @@ class Toolbar { this.menus = {} // 清空elem const $toolbar = this.$toolbar + $toolbar?.empty() // 注册 items @@ -86,6 +98,7 @@ class Toolbar { // 新插入菜单 const toolbarKeysWithInsertedKeys = clonedeep(toolbarKeys) + if (insertKeys.keys.length > 0) { if (typeof insertKeys.keys === 'string') { insertKeys.keys = [insertKeys.keys] @@ -100,10 +113,10 @@ class Toolbar { const filteredKeys = toolbarKeysWithInsertedKeys.filter(key => { if (typeof key === 'string') { // 普通菜单 - if (excludeKeys.includes(key)) return false + if (excludeKeys.includes(key)) { return false } } else { // group - if (excludeKeys.includes(key.key)) return false + if (excludeKeys.includes(key.key)) { return false } } return true }) @@ -113,16 +126,17 @@ class Toolbar { filteredKeys.forEach((key, index) => { if (key === '|') { // 第一个就是 `|` ,忽略 - if (index === 0) return + if (index === 0) { return } // 最后一个是 `|` ,忽略 - if (index + 1 === filteredKeysLength) return + if (index + 1 === filteredKeysLength) { return } // 多个紧挨着的 `|` ,只显示一个 - if (prevKey === '|') return + if (prevKey === '|') { return } // 分割线 const $divider = gen$barItemDivider() + $toolbar.append($divider) prevKey = key return @@ -150,10 +164,10 @@ class Toolbar { // 注册子菜单 menuKeys.forEach(key => { - if (excludeKeys.includes(key)) return + if (excludeKeys.includes(key)) { return } this.registerSingleItem( key, - group // 将子菜单,添加到 group + group, // 将子菜单,添加到 group ) }) @@ -173,6 +187,7 @@ class Toolbar { if (menu == null) { // 缓存中没有,则创建 const factory = MENU_ITEM_FACTORIES[key] + if (factory == null) { throw new Error(`Not found menu item factory by key '${key}'`) } @@ -189,11 +204,13 @@ class Toolbar { // 替换 icon svg const menuConf = editor.getMenuConfig(key) + if (menuConf && menuConf.iconSvg !== undefined) { menu.iconSvg = menuConf.iconSvg } const toolbarItem = createBarItem(key, menu, inGroup) + this.toolbarItems.push(toolbarItem) // 保存 toolbarItem 和 editor 的关系 @@ -203,17 +220,20 @@ class Toolbar { if (inGroup) { // barItem 是 groupButton const group = container as GroupButton + group.appendBarItem(toolbarItem) } else { // barItem 添加到 toolbar const toolbar = container as Toolbar + toolbar.$toolbar.append(toolbarItem.$elem) } } private getEditorInstance(): IDomEditor { const editor = TOOLBAR_TO_EDITOR.get(this) - if (editor == null) throw new Error('Can not get editor instance') + + if (editor == null) { throw new Error('Can not get editor instance') } return editor } diff --git a/packages/core/src/menus/helpers/helpers.ts b/packages/core/src/menus/helpers/helpers.ts index 9fd4c4a32..12d3a926a 100644 --- a/packages/core/src/menus/helpers/helpers.ts +++ b/packages/core/src/menus/helpers/helpers.ts @@ -3,15 +3,15 @@ * @author wangfupeng */ -import $, { Dom7Array } from '../../utils/dom' import { SVG_DOWN_ARROW } from '../../constants/svg' +import $, { Dom7Array } from '../../utils/dom' /** * 清理 svg 的样式 * @param $elem svg elem */ export function clearSvgStyle($elem: Dom7Array) { - if (!$elem.removeAttr) return + if (!$elem.removeAttr) { return } $elem.removeAttr('width') $elem.removeAttr('height') $elem.removeAttr('fill') @@ -20,6 +20,7 @@ export function clearSvgStyle($elem: Dom7Array) { $elem.removeAttr('p-id') const children = $elem.children() + if (children.length) { clearSvgStyle(children) } @@ -30,6 +31,7 @@ export function clearSvgStyle($elem: Dom7Array) { */ export function gen$downArrow() { const $downArrow = $(SVG_DOWN_ARROW) + return $downArrow } diff --git a/packages/core/src/menus/helpers/position.ts b/packages/core/src/menus/helpers/position.ts index ae8d1efeb..fe7e0b96e 100644 --- a/packages/core/src/menus/helpers/position.ts +++ b/packages/core/src/menus/helpers/position.ts @@ -3,13 +3,14 @@ * @author wangfupeng */ -import { Node, Element } from 'slate' -import { Dom7Array, getFirstVoidChild } from '../../utils/dom' -import { IDomEditor } from '../../editor/interface' +import { Element, Node } from 'slate' + import { DomEditor } from '../../editor/dom-editor' +import { IDomEditor } from '../../editor/interface' +import { Dom7Array, getFirstVoidChild } from '../../utils/dom' +import { promiseResolveThen } from '../../utils/util' import { NODE_TO_ELEMENT } from '../../utils/weak-maps' import { IPositionStyle } from '../interface' -import { promiseResolveThen } from '../../utils/util' /** * 获取 textContainer 尺寸和定位 @@ -29,7 +30,9 @@ export function getTextContainerRect(editor: IDomEditor): { const height = $textareaContainer.height() const { top, left } = $textareaContainer.offset() - return { top, left, width, height } + return { + top, left, width, height, + } } /** @@ -41,11 +44,13 @@ export function getPositionBySelection(editor: IDomEditor): Partial<IPositionSty const defaultStyle = { top: '0', left: '0' } const { selection } = editor - if (selection == null) return defaultStyle // 默认 position + + if (selection == null) { return defaultStyle } // 默认 position // 获取 textContainer rect const containerRect = getTextContainerRect(editor) - if (containerRect == null) return defaultStyle // 默认 position + + if (containerRect == null) { return defaultStyle } // 默认 position const { top: containerTop, left: containerLeft, @@ -56,20 +61,24 @@ export function getPositionBySelection(editor: IDomEditor): Partial<IPositionSty // 获取当前选区的 rect const range = DomEditor.toDOMRange(editor, selection) const rangeRect = range.getClientRects()[0] - if (rangeRect == null) return defaultStyle // 默认 position - const { width: rangeWidth, height: rangeHeight, top: rangeTop, left: rangeLeft } = rangeRect + + if (rangeRect == null) { return defaultStyle } // 默认 position + const { + width: rangeWidth, height: rangeHeight, top: rangeTop, left: rangeLeft, + } = rangeRect // 存储计算结构 const positionStyle: Partial<IPositionStyle> = {} // 获取 选区 top left 和 container top left 的差值(< 0 则使用 0) - let relativeTop = rangeTop - containerTop - let relativeLeft = rangeLeft - containerLeft + const relativeTop = rangeTop - containerTop + const relativeLeft = rangeLeft - containerLeft // 判断水平位置: modal/bar 显示在选区左侧,还是右侧? if (relativeLeft > containerWidth / 2) { // 选区 left 大于 containerWidth/2 (选区在 container 的右侧),则 modal/bar 显示在选区左侧 - let r = containerWidth - relativeLeft + const r = containerWidth - relativeLeft + positionStyle.right = `${r + 5}px` // 5px 间隔 } else { // 否则(选区在 container 的左侧),modal/bar 显示在选区右侧 @@ -79,12 +88,14 @@ export function getPositionBySelection(editor: IDomEditor): Partial<IPositionSty // 判断垂直的位置: modal/bar 显示在选区上面,还是下面? if (relativeTop > containerHeight / 2) { // 选区 top > containerHeight/2 (选区在 container 的下半部分),则 modal/bar 显示在选区的上面 - let b = containerHeight - relativeTop + const b = containerHeight - relativeTop + positionStyle.bottom = `${b + 5}px` // 5px 间隔 } else { // 否则(选区在 container 的上半部分),则 modal/bar 显示在选区的下面 let t = relativeTop + rangeHeight - if (t < 0) t = 0 + + if (t < 0) { t = 0 } positionStyle.top = `${t + 5}px` // 5px 间隔 } @@ -100,30 +111,35 @@ export function getPositionBySelection(editor: IDomEditor): Partial<IPositionSty export function getPositionByNode( editor: IDomEditor, node: Node, - type: string = 'modal' + type = 'modal', ): Partial<IPositionStyle> { // 默认情况下 { top: 0, left: 0 } const defaultStyle = { top: '0', left: '0' } const { selection } = editor - if (selection == null) return defaultStyle // 默认 position + + if (selection == null) { return defaultStyle } // 默认 position // 根据 node 获取 elem const isVoidElem = Element.isElement(node) && editor.isVoid(node) const isInlineElem = Element.isElement(node) && editor.isInline(node) const elem = NODE_TO_ELEMENT.get(node) - if (elem == null) return defaultStyle // 默认 position + + if (elem == null) { return defaultStyle } // 默认 position let { top: elemTop, left: elemLeft, height: elemHeight, width: elemWidth, } = elem.getBoundingClientRect() + if (isVoidElem) { // void node ,重新计算 top 和 height const voidElem = getFirstVoidChild(elem) + if (voidElem != null) { const { top, height } = voidElem.getBoundingClientRect() + elemTop = top elemHeight = height } @@ -131,7 +147,8 @@ export function getPositionByNode( // 获取 textContainer rect const containerRect = getTextContainerRect(editor) - if (containerRect == null) return defaultStyle // 默认 position + + if (containerRect == null) { return defaultStyle } // 默认 position const { top: containerTop, left: containerLeft, @@ -143,8 +160,8 @@ export function getPositionByNode( const positionStyle: Partial<IPositionStyle> = {} // 获取 elem top left 和 container top left 的差值(< 0 则使用 0) - let relativeTop = elemTop - containerTop - let relativeLeft = elemLeft - containerLeft + const relativeTop = elemTop - containerTop + const relativeLeft = elemLeft - containerLeft if (type === 'bar') { // bar - 1. left 对齐 elem.left ;2. 尽量显示在 elem 上方 @@ -167,27 +184,26 @@ export function getPositionByNode( if (!isVoidElem) { // 非 void node - left 和 elem left 对齐 positionStyle.left = `${relativeLeft}px` - } else { - if (isInlineElem) { - // inline void node 需要计算 - if (relativeLeft > (containerWidth - elemWidth) / 2) { - // elem 在 container 的右侧,则 modal 显示在 elem 左侧 - positionStyle.right = `${containerWidth - relativeLeft + 5}px` - } else { - // 否则 elem 在 container 左侧,则 modal 显示在 elem 右侧 - positionStyle.left = `${relativeLeft + elemWidth + 5}px` - } + } else if (isInlineElem) { + // inline void node 需要计算 + if (relativeLeft > (containerWidth - elemWidth) / 2) { + // elem 在 container 的右侧,则 modal 显示在 elem 左侧 + positionStyle.right = `${containerWidth - relativeLeft + 5}px` } else { - // block void node 水平靠左即可 - positionStyle.left = `20px` + // 否则 elem 在 container 左侧,则 modal 显示在 elem 右侧 + positionStyle.left = `${relativeLeft + elemWidth + 5}px` } + } else { + // block void node 水平靠左即可 + positionStyle.left = '20px' } // 垂直 if (isVoidElem) { // void node - top 和 elem top 对齐 let t = relativeTop - if (t < 0) t = 0 // top 不能小于 0 + + if (t < 0) { t = 0 } // top 不能小于 0 positionStyle.top = `${t}px` } else { // 非 void node ,计算 top @@ -197,7 +213,8 @@ export function getPositionByNode( } else { // elem 在 container 的上半部分,则 modal 显示在 elem 下方 let t = relativeTop + elemHeight - if (t < 0) t = 0 + + if (t < 0) { t = 0 } positionStyle.top = `${t + 5}px` } } @@ -218,7 +235,8 @@ export function correctPosition(editor: IDomEditor, $positionElem: Dom7Array) { promiseResolveThen(() => { // 获取 textContainer rect const containerRect = getTextContainerRect(editor) - if (containerRect == null) return + + if (containerRect == null) { return } const { top: containerTop, left: containerLeft, @@ -239,12 +257,14 @@ export function correctPosition(editor: IDomEditor, $positionElem: Dom7Array) { if (styleStr.indexOf('top') >= 0) { // 设置了 top ,则有可能超过 textContainer 的下边界 const d = relativeTop + positionElemHeight - containerHeight + if (d > 0) { // 已超过 textContainer 的下边界,则上移 const curTopStr = $positionElem.css('top') const curTop = parseInt(curTopStr.toString()) let newTop = curTop - d - if (newTop < 0) newTop = 0 // 不能超过 textContainer 上边界 + + if (newTop < 0) { newTop = 0 } // 不能超过 textContainer 上边界 $positionElem.css('top', `${newTop}px`) } } @@ -256,6 +276,7 @@ export function correctPosition(editor: IDomEditor, $positionElem: Dom7Array) { const curBottomStr = $positionElem.css('bottom') const curBottom = parseInt(curBottomStr.toString()) const newBottom = curBottom - Math.abs(positionElemTop) // 保证上边界和 textContainer 对齐即可,下边界不管 + $positionElem.css('bottom', `${newBottom}px`) } } @@ -263,12 +284,14 @@ export function correctPosition(editor: IDomEditor, $positionElem: Dom7Array) { if (styleStr.indexOf('left') >= 0) { // 设置了 left ,则有可能超过 textContainer 的右边界 const d = relativeLeft + positionElemWidth - containerWidth + if (d > 0) { // 已超过 textContainer 的右边界,需左移 const curLeftStr = $positionElem.css('left') const curLeft = parseInt(curLeftStr.toString()) let newLeft = curLeft - d - if (newLeft < 0) newLeft = 0 // 不能超过 textContainer 左边界 + + if (newLeft < 0) { newLeft = 0 } // 不能超过 textContainer 左边界 $positionElem.css('left', `${newLeft}px`) } } @@ -280,6 +303,7 @@ export function correctPosition(editor: IDomEditor, $positionElem: Dom7Array) { const curRightStr = $positionElem.css('right') const curRight = parseInt(curRightStr.toString()) const newRight = curRight - Math.abs(positionElemLeft) // 保证左边界和 textContainer 对齐即可,右边界不管 + $positionElem.css('right', `${newRight}px`) } } diff --git a/packages/core/src/menus/index.ts b/packages/core/src/menus/index.ts index 6a48b239b..64f85414a 100644 --- a/packages/core/src/menus/index.ts +++ b/packages/core/src/menus/index.ts @@ -11,17 +11,17 @@ export { registerMenu } from './register' // menu 相关接口 export { IButtonMenu, - ISelectMenu, IDropPanelMenu, IModalMenu, - IRegisterMenuConf, IOption, + IRegisterMenuConf, + ISelectMenu, } from './interface' // 输出 modal 相关方法 export { - genModalInputElems, genModalButtonElems, + genModalInputElems, genModalTextareaElems, } from './panel-and-modal/Modal' diff --git a/packages/core/src/menus/interface.ts b/packages/core/src/menus/interface.ts index 9c8776289..9d91b2267 100644 --- a/packages/core/src/menus/interface.ts +++ b/packages/core/src/menus/interface.ts @@ -4,6 +4,7 @@ */ import { Node } from 'slate' + import { IDomEditor } from '../editor/interface' import { DOMElement } from '../utils/dom' diff --git a/packages/core/src/menus/panel-and-modal/BaseClass.ts b/packages/core/src/menus/panel-and-modal/BaseClass.ts index 72327ee7b..4ac6657e6 100644 --- a/packages/core/src/menus/panel-and-modal/BaseClass.ts +++ b/packages/core/src/menus/panel-and-modal/BaseClass.ts @@ -9,9 +9,12 @@ import { EDITOR_TO_PANEL_AND_MODAL, PANEL_OR_MODAL_TO_EDITOR } from '../../utils abstract class PanelAndModal { abstract readonly type: string + abstract readonly $elem: Dom7Array - isShow: boolean = false - private showTime: number = 0 // 显示时的时间戳 + + isShow = false + + private showTime = 0 // 显示时的时间戳 constructor(editor: IDomEditor) { this.record(editor) @@ -22,6 +25,7 @@ abstract class PanelAndModal { */ private record(editor: IDomEditor) { let set = EDITOR_TO_PANEL_AND_MODAL.get(editor) + if (set == null) { set = new Set() EDITOR_TO_PANEL_AND_MODAL.set(editor, set) @@ -38,11 +42,13 @@ abstract class PanelAndModal { renderContent(contentElem: DOMElement) { const { $elem } = this + $elem.empty() // 先清空,再填充内容 $elem.append(contentElem) // 添加自己额外的 elem const $selfElem = this.genSelfElem() + if ($selfElem) { $elem.append($selfElem) } @@ -50,38 +56,44 @@ abstract class PanelAndModal { appendTo($menuElem: Dom7Array) { const { $elem } = this + $menuElem.append($elem) } show() { - if (this.isShow) return + if (this.isShow) { return } this.showTime = Date.now() const { $elem } = this + $elem.show() this.isShow = true // 触发事件 const editor = PANEL_OR_MODAL_TO_EDITOR.get(this) - if (editor) editor.emit('modalOrPanelShow', this) + + if (editor) { editor.emit('modalOrPanelShow', this) } } hide() { - if (!this.isShow) return + if (!this.isShow) { return } const now = Date.now() + if (now - this.showTime < 200) { // 刚显示的,不要立刻隐藏(避免频繁触发 show/hide ) return } const { $elem } = this + $elem.hide() this.isShow = false // 触发事件 const editor = PANEL_OR_MODAL_TO_EDITOR.get(this) - if (editor) editor.emit('modalOrPanelHide') + + if (editor) { editor.emit('modalOrPanelHide') } } } diff --git a/packages/core/src/menus/panel-and-modal/DropPanel.ts b/packages/core/src/menus/panel-and-modal/DropPanel.ts index 024b4a073..2fde39139 100644 --- a/packages/core/src/menus/panel-and-modal/DropPanel.ts +++ b/packages/core/src/menus/panel-and-modal/DropPanel.ts @@ -9,7 +9,8 @@ import PanelAndModal from './BaseClass' class DropPanel extends PanelAndModal { type = 'dropPanel' - readonly $elem: Dom7Array = $(`<div class="w-e-drop-panel"></div>`) + + readonly $elem: Dom7Array = $('<div class="w-e-drop-panel"></div>') constructor(editor: IDomEditor) { super(editor) diff --git a/packages/core/src/menus/panel-and-modal/Modal.ts b/packages/core/src/menus/panel-and-modal/Modal.ts index 0415e6eb5..57f15133b 100644 --- a/packages/core/src/menus/panel-and-modal/Modal.ts +++ b/packages/core/src/menus/panel-and-modal/Modal.ts @@ -3,22 +3,24 @@ * @author wangfupeng */ -import $, { Dom7Array, DOMElement } from '../../utils/dom' -import { IPositionStyle } from '../interface' -import PanelAndModal from './BaseClass' -import { IDomEditor } from '../../editor/interface' // import { DomEditor } from '../../editor/dom-editor' import { SVG_CLOSE } from '../../constants/svg' +import { IDomEditor } from '../../editor/interface' +import $, { Dom7Array, DOMElement } from '../../utils/dom' import { PANEL_OR_MODAL_TO_EDITOR } from '../../utils/weak-maps' +import { IPositionStyle } from '../interface' +import PanelAndModal from './BaseClass' class Modal extends PanelAndModal { type = 'modal' - readonly $elem: Dom7Array = $(`<div class="w-e-modal"></div>`) - private width: number = 0 - constructor(editor: IDomEditor, width: number = 0) { + readonly $elem: Dom7Array = $('<div class="w-e-modal"></div>') + + private width = 0 + + constructor(editor: IDomEditor, width = 0) { super(editor) - if (width) this.width = width + if (width) { this.width = width } const { $elem } = this @@ -28,6 +30,7 @@ class Modal extends PanelAndModal { // esc 关闭 modal $elem.on('keyup', e => { const event = e as KeyboardEvent + if (event.code === 'Escape') { this.hide() editor.restoreSelection() // 还原选区 @@ -56,7 +59,7 @@ class Modal extends PanelAndModal { $elem.attr('style', '') // 先清空 style ,再重新设置 - if (width) $elem.css('width', `${width}px`) + if (width) { $elem.css('width', `${width}px`) } $elem.css(positionStyle) } } @@ -75,11 +78,13 @@ export default Modal export function genModalInputElems( labelText: string, inputId: string, - placeholder?: string + placeholder?: string, ): DOMElement[] { const $container = $('<label class="babel-container"></label>') + $container.append(`<span>${labelText}</span>`) const $input = $(`<input type="text" id="${inputId}" placeholder="${placeholder || ''}">`) + $container.append($input) return [$container[0], $input[0]] @@ -95,13 +100,15 @@ export function genModalInputElems( export function genModalTextareaElems( labelText: string, textareaId: string, - placeholder?: string + placeholder?: string, ): DOMElement[] { const $container = $('<label class="babel-container"></label>') + $container.append(`<span>${labelText}</span>`) const $textarea = $( - `<textarea type="text" id="${textareaId}" placeholder="${placeholder || ''}"></textarea>` + `<textarea type="text" id="${textareaId}" placeholder="${placeholder || ''}"></textarea>`, ) + $container.append($textarea) return [$container[0], $textarea[0]] @@ -116,6 +123,7 @@ export function genModalTextareaElems( export function genModalButtonElems(buttonId: string, buttonText: string): DOMElement[] { const $buttonContainer = $('<div class="button-container"></div>') const $button = $(`<button type="button" id="${buttonId}">${buttonText}</button>`) + $buttonContainer.append($button) return [$buttonContainer[0], $button[0]] diff --git a/packages/core/src/menus/panel-and-modal/SelectList.ts b/packages/core/src/menus/panel-and-modal/SelectList.ts index 73aea2148..b8c66843e 100644 --- a/packages/core/src/menus/panel-and-modal/SelectList.ts +++ b/packages/core/src/menus/panel-and-modal/SelectList.ts @@ -3,11 +3,11 @@ * @author wangfupeng */ +import { SVG_CHECK_MARK } from '../../constants/svg' +import { IDomEditor } from '../../editor/interface' import $, { Dom7Array } from '../../utils/dom' import { IOption } from '../interface' import PanelAndModal from './BaseClass' -import { IDomEditor } from '../../editor/interface' -import { SVG_CHECK_MARK } from '../../constants/svg' // “对号”icon function gen$SelectedIcon() { @@ -16,7 +16,8 @@ function gen$SelectedIcon() { class SelectList extends PanelAndModal { type = 'selectList' - readonly $elem: Dom7Array = $(`<div class="w-e-select-list"></div>`) + + readonly $elem: Dom7Array = $('<div class="w-e-select-list"></div>') constructor(editor: IDomEditor, width?: number) { super(editor) @@ -38,11 +39,15 @@ class SelectList extends PanelAndModal { */ renderList(options: IOption[]) { const $elem = this.$elem + $elem.empty() // 先清空内容,再重新渲染 - const $list = $(`<ul></ul>`) + const $list = $('<ul></ul>') + options.forEach(opt => { - const { value, text, selected, styleForRenderMenuList } = opt + const { + value, text, selected, styleForRenderMenuList, + } = opt const $li = $(`<li data-value="${value}"></li>`) // 【注意】必须用 <li> 必须用 data-value!!! if (styleForRenderMenuList) { @@ -51,6 +56,7 @@ class SelectList extends PanelAndModal { if (selected) { const $selectedIcon = gen$SelectedIcon() + $li.append($selectedIcon) $li.addClass('selected') } diff --git a/packages/core/src/menus/register.ts b/packages/core/src/menus/register.ts index 423673009..3fa59c8e6 100644 --- a/packages/core/src/menus/register.ts +++ b/packages/core/src/menus/register.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import { MenuFactoryType, IRegisterMenuConf } from './interface' import { registerGlobalMenuConf } from '../config/register' +import { IRegisterMenuConf, MenuFactoryType } from './interface' // menu item 的工厂函数 - 集合 export const MENU_ITEM_FACTORIES: { @@ -18,7 +18,7 @@ export const MENU_ITEM_FACTORIES: { */ export function registerMenu( registerMenuConf: IRegisterMenuConf, - customConfig?: { [key: string]: any } + customConfig?: { [key: string]: any }, ) { const { key, factory, config } = registerMenuConf diff --git a/packages/core/src/parse-html/helper.ts b/packages/core/src/parse-html/helper.ts index 00984bb35..85b86aa04 100644 --- a/packages/core/src/parse-html/helper.ts +++ b/packages/core/src/parse-html/helper.ts @@ -12,5 +12,6 @@ const REPLACE_SPACE_160_REG = new RegExp(String.fromCharCode(160), 'g') */ export function replaceSpace160(str: string): string { const res = str.replace(REPLACE_SPACE_160_REG, ' ') + return res } diff --git a/packages/core/src/parse-html/index.ts b/packages/core/src/parse-html/index.ts index ee1ad7656..c3b3f0558 100644 --- a/packages/core/src/parse-html/index.ts +++ b/packages/core/src/parse-html/index.ts @@ -3,9 +3,10 @@ * @author wangfupeng */ -import { DOMElement } from '../utils/dom' -import { Element as SlateElement, Descendant } from 'slate' +import { Descendant, Element as SlateElement } from 'slate' + import { IDomEditor } from '../editor/interface' +import { DOMElement } from '../utils/dom' // 常见的 text tag export const TEXT_TAGS = [ @@ -77,5 +78,6 @@ export interface IParseElemHtmlConf { export function registerParseElemHtmlConf(conf: IParseElemHtmlConf) { const { selector, parseElemHtml } = conf + PARSE_ELEM_HTML_CONF[selector] = parseElemHtml } diff --git a/packages/core/src/parse-html/parse-common-elem-html.ts b/packages/core/src/parse-html/parse-common-elem-html.ts index c72b84020..7a92d7060 100644 --- a/packages/core/src/parse-html/parse-common-elem-html.ts +++ b/packages/core/src/parse-html/parse-common-elem-html.ts @@ -4,12 +4,15 @@ */ import $, { Dom7Array } from 'dom7' -import { Editor, Element, Descendant, Text } from 'slate' +import { + Descendant, Editor, Element, Text, +} from 'slate' + import { IDomEditor } from '../editor/interface' -import parseElemHtml from './parse-elem-html' -import { PARSE_ELEM_HTML_CONF, ParseElemHtmlFnType, PARSE_STYLE_HTML_FN_LIST } from './index' -import { NodeType, DOMElement } from '../utils/dom' +import { DOMElement, NodeType } from '../utils/dom' import { replaceSpace160 } from './helper' +import { PARSE_ELEM_HTML_CONF, PARSE_STYLE_HTML_FN_LIST, ParseElemHtmlFnType } from './index' +import parseElemHtml from './parse-elem-html' /** * 往 children 最后一个 item(如果是 text node) 插入文字 @@ -19,13 +22,16 @@ import { replaceSpace160 } from './helper' */ function tryInsertTextToChildrenLastItem(children: Descendant[], str: string): boolean { const len = children.length + if (len) { const lastItem = children[len - 1] + if (Text.isText(lastItem)) { const keys = Object.keys(lastItem) + if (keys.length === 1 && keys[0] === 'text') { // lastItem 必须是纯文本,没有 marks - lastItem.text = lastItem.text + str + lastItem.text += str return true } } @@ -43,6 +49,7 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { // void node( html 中编辑的,如 video 的 html 中会有 data-w-e-is-void 属性 ),不需要生成 children const isVoid = $elem.attr('data-w-e-is-void') != null + if (isVoid) { return children } @@ -64,6 +71,7 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { if (child.nodeName === 'BR') { // 尝试把 text 插入到最后一个 children const res = tryInsertTextToChildrenLastItem(children, '\n') + if (!res) { // 若插入失败,则新建 item children.push({ text: '\n' }) @@ -74,6 +82,7 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { // 其他 elem const $child = $(child) const parsedRes = parseElemHtml($child, editor) + if (Array.isArray(parsedRes)) { parsedRes.forEach(el => children.push(el)) } else { @@ -84,6 +93,7 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { if (child.nodeType === NodeType.TEXT_NODE) { // text let text = child.textContent || '' + if (text.trim() === '' && text.indexOf('\n') >= 0) { // 有换行,但无实际内容 return @@ -95,12 +105,13 @@ function genChildren($elem: Dom7Array, editor: IDomEditor): Descendant[] { // 尝试把 text 插入到最后一个 children const res = tryInsertTextToChildrenLastItem(children, text) + if (!res) { // 若插入失败,则新建 item children.push({ text }) } } - return + } }) return children @@ -123,7 +134,7 @@ function defaultParser(elem: DOMElement, children: Descendant[], editor: IDomEdi * @param $elem $elem */ function getParser($elem: Dom7Array): ParseElemHtmlFnType { - for (let selector in PARSE_ELEM_HTML_CONF) { + for (const selector in PARSE_ELEM_HTML_CONF) { if ($elem[0].matches(selector)) { return PARSE_ELEM_HTML_CONF[selector] } @@ -144,10 +155,11 @@ function parseCommonElemHtml($elem: Dom7Array, editor: IDomEditor): Element[] { const parser = getParser($elem) let parsedRes = parser($elem[0], children, editor) - if (!Array.isArray(parsedRes)) parsedRes = [parsedRes] // 临时处理为数组 + if (!Array.isArray(parsedRes)) { parsedRes = [parsedRes] } // 临时处理为数组 parsedRes.forEach(elem => { const isVoid = Editor.isVoid(editor, elem) + if (!isVoid) { // 非 void ,如果没有 children ,则取纯文本 if (children.length === 0) { diff --git a/packages/core/src/parse-html/parse-elem-html.ts b/packages/core/src/parse-html/parse-elem-html.ts index 5caa6677f..93145565b 100644 --- a/packages/core/src/parse-html/parse-elem-html.ts +++ b/packages/core/src/parse-html/parse-elem-html.ts @@ -5,11 +5,12 @@ import $, { Dom7Array } from 'dom7' import { Descendant } from 'slate' + import { IDomEditor } from '../editor/interface' +import { PRE_PARSE_HTML_CONF_LIST, TEXT_TAGS } from '../index' +import { getTagName } from '../utils/dom' import parseCommonElemHtml from './parse-common-elem-html' import parseTextElemHtml from './parse-text-elem-html' -import { getTagName } from '../utils/dom' -import { PRE_PARSE_HTML_CONF_LIST, TEXT_TAGS } from '../index' /** * 处理 DOM Elem html @@ -21,6 +22,7 @@ function parseElemHtml($elem: Dom7Array, editor: IDomEditor): Descendant | Desce // pre-parse PRE_PARSE_HTML_CONF_LIST.forEach(conf => { const { selector, preParseHtml } = conf + if ($elem[0].matches(selector)) { $elem = $(preParseHtml($elem[0])) } @@ -32,39 +34,41 @@ function parseElemHtml($elem: Dom7Array, editor: IDomEditor): Descendant | Desce if (tagName === 'span') { if ($elem.attr('data-w-e-type')) { return parseCommonElemHtml($elem, editor) - } else { - if ($elem[0].childNodes.length > 1) { - const childNodes = $elem[0].childNodes - return Array.from(childNodes).map(child => { - $($elem[0]).empty() - $($elem[0]).append($(child)) - return parseTextElemHtml($($elem[0]), editor) - }) - } - return parseTextElemHtml($elem, editor) } + if ($elem[0].childNodes.length > 1) { + const childNodes = $elem[0].childNodes + + return Array.from(childNodes).map(child => { + $($elem[0]).empty() + $($elem[0]).append($(child)) + return parseTextElemHtml($($elem[0]), editor) + }) + } + return parseTextElemHtml($elem, editor) + } // <code> 特殊处理 if (tagName === 'code') { const parentTagName = getTagName($elem.parent()) + if (parentTagName === 'pre') { // <code> 在 <pre> 内,则是 elem return parseCommonElemHtml($elem, editor) - } else { - // <code> 不在 <pre> 内,则是 text - return parseTextElemHtml($elem, editor) } + // <code> 不在 <pre> 内,则是 text + return parseTextElemHtml($elem, editor) + } // 非 <code> ,正常处理 if (TEXT_TAGS.includes(tagName)) { // text node return parseTextElemHtml($elem, editor) - } else { - // elem node - return parseCommonElemHtml($elem, editor) } + // elem node + return parseCommonElemHtml($elem, editor) + } export default parseElemHtml diff --git a/packages/core/src/parse-html/parse-text-elem-html.ts b/packages/core/src/parse-html/parse-text-elem-html.ts index f875c9527..1be6ed8cf 100644 --- a/packages/core/src/parse-html/parse-text-elem-html.ts +++ b/packages/core/src/parse-html/parse-text-elem-html.ts @@ -5,10 +5,11 @@ import { Dom7Array } from 'dom7' import { Text } from 'slate' + import { IDomEditor } from '../editor/interface' -import { PARSE_STYLE_HTML_FN_LIST } from './index' import { deReplaceHtmlSpecialSymbols } from '../utils/util' import { replaceSpace160 } from './helper' +import { PARSE_STYLE_HTML_FN_LIST } from './index' /** * 处理 text elem ,如 <span> <strong> <em> 等(并不是 DOM Text Node) @@ -26,7 +27,7 @@ function parseTextElemHtml($text: Dom7Array, editor: IDomEditor): Text { // 用 textContent ,不能用 .text() 。后者无法识别 text 开头和末尾的 &nbsp; let text = $text[0].textContent || '' - //【翻转】替换 html 特殊字符,如 &lt; 替换为 < + // 【翻转】替换 html 特殊字符,如 &lt; 替换为 < text = deReplaceHtmlSpecialSymbols(text) // 把 charCode 160 的空格(`&nbsp` 转换的),替换为 charCode 32 的空格(JS 默认的) diff --git a/packages/core/src/render/element/getRenderElem.tsx b/packages/core/src/render/element/getRenderElem.tsx index a1d274429..7afe73490 100644 --- a/packages/core/src/render/element/getRenderElem.tsx +++ b/packages/core/src/render/element/getRenderElem.tsx @@ -5,6 +5,7 @@ import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' + import { IDomEditor } from '../../editor/interface' import { RENDER_ELEM_CONF, RenderElemFnType } from '../index' @@ -18,7 +19,7 @@ import { RENDER_ELEM_CONF, RenderElemFnType } from '../index' function defaultRender( elemNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { const Tag = editor.isInline(elemNode) ? 'span' : 'div' @@ -33,6 +34,7 @@ function defaultRender( */ function getRenderElem(type: string): RenderElemFnType { const fn = RENDER_ELEM_CONF[type] + return fn || defaultRender } diff --git a/packages/core/src/render/element/renderElement.tsx b/packages/core/src/render/element/renderElement.tsx index 46ccb118d..bd44de158 100644 --- a/packages/core/src/render/element/renderElement.tsx +++ b/packages/core/src/render/element/renderElement.tsx @@ -3,23 +3,24 @@ * @author wangfupeng */ -import { Editor, Node, Element as SlateElement } from 'slate' +import { Editor, Element as SlateElement, Node } from 'slate' import { jsx, VNode } from 'snabbdom' -import { node2Vnode } from '../node2Vnode' + import { DomEditor } from '../../editor/dom-editor' import { IDomEditor } from '../../editor/interface' +import { getElementById } from '../../utils/dom' +import { promiseResolveThen } from '../../utils/util' import { + ELEMENT_TO_NODE, KEY_TO_ELEMENT, NODE_TO_ELEMENT, - ELEMENT_TO_NODE, NODE_TO_INDEX, NODE_TO_PARENT, } from '../../utils/weak-maps' +import { genElemId } from '../helper' +import { node2Vnode } from '../node2Vnode' import getRenderElem from './getRenderElem' import renderStyle from './renderStyle' -import { promiseResolveThen } from '../../utils/util' -import { genElemId } from '../helper' -import { getElementById } from '../../utils/dom' interface IAttrs { id: string @@ -45,9 +46,10 @@ function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode { // 根据 type 生成 vnode 的函数 const { type, children = [] } = elemNode - let renderElem = getRenderElem(type) + const renderElem = getRenderElem(type) let childrenVnode + if (isVoid) { childrenVnode = null // void 节点 render elem 时不传入 children } else { @@ -101,7 +103,7 @@ function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode { } // 添加 element 属性 - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } Object.assign(vnode.data, attrs) // 添加文本相关的样式,如 text-align @@ -114,7 +116,8 @@ function renderElement(elemNode: SlateElement, editor: IDomEditor): VNode { promiseResolveThen(() => { // 异步,否则拿不到 DOM 节点 const dom = getElementById(domId) - if (dom == null) return + + if (dom == null) { return } KEY_TO_ELEMENT.set(key, dom) NODE_TO_ELEMENT.set(elemNode, dom) ELEMENT_TO_NODE.set(dom, elemNode) diff --git a/packages/core/src/render/element/renderStyle.ts b/packages/core/src/render/element/renderStyle.ts index 5aef9e065..06399c9c4 100644 --- a/packages/core/src/render/element/renderStyle.ts +++ b/packages/core/src/render/element/renderStyle.ts @@ -5,6 +5,7 @@ import { Element as SlateElement } from 'slate' import { VNode } from 'snabbdom' + import { RENDER_STYLE_HANDLER_LIST } from '../index' /** diff --git a/packages/core/src/render/index.ts b/packages/core/src/render/index.ts index 90ed07aca..d811913f5 100644 --- a/packages/core/src/render/index.ts +++ b/packages/core/src/render/index.ts @@ -3,8 +3,9 @@ * @author wangfupeng */ -import { Element as SlateElement, Descendant } from 'slate' +import { Descendant, Element as SlateElement } from 'slate' import { VNode } from 'snabbdom' + import { IDomEditor } from '../editor/interface' // ------------------------------------ render style ------------------------------------ diff --git a/packages/core/src/render/node2Vnode.ts b/packages/core/src/render/node2Vnode.ts index ac35ff209..36aa32327 100644 --- a/packages/core/src/render/node2Vnode.ts +++ b/packages/core/src/render/node2Vnode.ts @@ -3,12 +3,15 @@ * @author wangfupeng */ -import { Element, Text, Node, Ancestor } from 'slate' +import { + Ancestor, Element, Node, Text, +} from 'slate' import { VNode } from 'snabbdom' + import { IDomEditor } from '../editor/interface' +import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps' import renderElement from './element/renderElement' import renderText from './text/renderText' -import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps' /** * 根据 slate node 生成 snabbdom vnode @@ -23,6 +26,7 @@ export function node2Vnode(node: Node, index: number, parent: Ancestor, editor: NODE_TO_PARENT.set(node, parent) let vnode: VNode + if (Element.isElement(node)) { // element vnode = renderElement(node as Element, editor) diff --git a/packages/core/src/render/text/genVnode.tsx b/packages/core/src/render/text/genVnode.tsx index 6b34c6690..5f3d47ed7 100644 --- a/packages/core/src/render/text/genVnode.tsx +++ b/packages/core/src/render/text/genVnode.tsx @@ -3,13 +3,16 @@ * @author wangfupeng */ -import { Editor, Path, Node, Text as SlateText, Ancestor } from 'slate' +import { + Ancestor, Editor, Node, Path, Text as SlateText, +} from 'slate' import { jsx, VNode } from 'snabbdom' + import { DomEditor } from '../../editor/dom-editor' import { IDomEditor } from '../../editor/interface' function str(text: string, isTrailing = false): VNode { - return <span data-slate-string>{isTrailing ? text + '\n' : text}</span> + return <span data-slate-string>{isTrailing ? `${text}\n` : text}</span> } function zeroWidthStr(length = 0, isLineBreak = false): VNode { @@ -23,10 +26,10 @@ function zeroWidthStr(length = 0, isLineBreak = false): VNode { function genTextVnode( leafNode: SlateText, - isLast: boolean = false, + isLast = false, textNode: SlateText, parent: Ancestor, - editor: IDomEditor + editor: IDomEditor, ): VNode { const { text } = leafNode const path = DomEditor.findPath(editor, textNode) @@ -46,10 +49,10 @@ function genTextVnode( // width space that will convert into a line break when copying and pasting // to support expected plain text. if ( - text === '' && - parent.children[parent.children.length - 1] === textNode && - !editor.isInline(parent) && - Editor.string(editor, parentPath) === '' + text === '' + && parent.children[parent.children.length - 1] === textNode + && !editor.isInline(parent) + && Editor.string(editor, parentPath) === '' ) { return zeroWidthStr(0, true) } diff --git a/packages/core/src/render/text/renderStyle.ts b/packages/core/src/render/text/renderStyle.ts index 7fe7cc9fc..f91285118 100644 --- a/packages/core/src/render/text/renderStyle.ts +++ b/packages/core/src/render/text/renderStyle.ts @@ -5,6 +5,7 @@ import { Text as SlateText } from 'slate' import { VNode } from 'snabbdom' + import { RENDER_STYLE_HANDLER_LIST } from '../index' /** diff --git a/packages/core/src/render/text/renderText.tsx b/packages/core/src/render/text/renderText.tsx index 2a5b05b59..3445db512 100644 --- a/packages/core/src/render/text/renderText.tsx +++ b/packages/core/src/render/text/renderText.tsx @@ -3,25 +3,26 @@ * @author wangfupeng */ -import { Text as SlateText, Ancestor } from 'slate' +import { Ancestor, Text as SlateText } from 'slate' import { jsx, VNode } from 'snabbdom' + import { DomEditor } from '../../editor/dom-editor' import { IDomEditor } from '../../editor/interface' -import { KEY_TO_ELEMENT, NODE_TO_ELEMENT, ELEMENT_TO_NODE } from '../../utils/weak-maps' -import genTextVnode from './genVnode' -import addTextVnodeStyle from './renderStyle' +import { getElementById } from '../../utils/dom' import { promiseResolveThen } from '../../utils/util' +import { ELEMENT_TO_NODE, KEY_TO_ELEMENT, NODE_TO_ELEMENT } from '../../utils/weak-maps' import { genTextId } from '../helper' -import { getElementById } from '../../utils/dom' +import genTextVnode from './genVnode' +import addTextVnodeStyle from './renderStyle' function renderText(textNode: SlateText, parent: Ancestor, editor: IDomEditor): VNode { - if (textNode.text == null) - throw new Error(`Current node is not slate Text ${JSON.stringify(textNode)}`) + if (textNode.text == null) { throw new Error(`Current node is not slate Text ${JSON.stringify(textNode)}`) } const key = DomEditor.findKey(editor, textNode) // 根据 decorate 将 text 拆分为多个叶子节点 text[] const { decorate } = editor.getConfig() - if (decorate == null) throw new Error(`Can not get config.decorate`) + + if (decorate == null) { throw new Error('Can not get config.decorate') } const path = DomEditor.findPath(editor, textNode) const ds = decorate([textNode, path]) const leaves = SlateText.decorations(textNode, ds) @@ -31,6 +32,7 @@ function renderText(textNode: SlateText, parent: Ancestor, editor: IDomEditor): // 文字和样式 const isLast = index === leaves.length - 1 let strVnode = genTextVnode(leafNode, isLast, textNode, parent, editor) + strVnode = addTextVnodeStyle(leafNode, strVnode) // 生成每一个 leaf 节点 return <span data-slate-leaf>{strVnode}</span> @@ -48,7 +50,8 @@ function renderText(textNode: SlateText, parent: Ancestor, editor: IDomEditor): promiseResolveThen(() => { // 异步,否则拿不到 DOM const dom = getElementById(textId) - if (dom == null) return + + if (dom == null) { return } KEY_TO_ELEMENT.set(key, dom) NODE_TO_ELEMENT.set(textNode, dom) ELEMENT_TO_NODE.set(dom, textNode) diff --git a/packages/core/src/text-area/TextArea.ts b/packages/core/src/text-area/TextArea.ts index 0c84f4664..f285ac245 100644 --- a/packages/core/src/text-area/TextArea.ts +++ b/packages/core/src/text-area/TextArea.ts @@ -3,49 +3,66 @@ * @author wangfupeng */ -import { Range } from 'slate' -import throttle from 'lodash.throttle' import forEach from 'lodash.foreach' -import $, { Dom7Array, DOMElement } from '../utils/dom' -import { TEXTAREA_TO_EDITOR } from '../utils/weak-maps' -import { IDomEditor } from '../editor/interface' +import throttle from 'lodash.throttle' +import { Range } from 'slate' + import { DomEditor } from '../editor/dom-editor' -import updateView from './update-view' -import { handlePlaceholder } from './place-holder' -import { editorSelectionToDOM, DOMSelectionToEditor } from './syncSelection' +import { IDomEditor } from '../editor/interface' +import $, { Dom7Array, DOMElement } from '../utils/dom' import { promiseResolveThen } from '../utils/util' +import { TEXTAREA_TO_EDITOR } from '../utils/weak-maps' import eventHandlerConf from './event-handlers/index' +import { handlePlaceholder } from './place-holder' +import { DOMSelectionToEditor, editorSelectionToDOM } from './syncSelection' +import updateView from './update-view' let ID = 1 class TextArea { readonly id = ID++ + $box: Dom7Array + $textAreaContainer: Dom7Array + $scroll: Dom7Array + $textArea: Dom7Array | null = null + private readonly $progressBar = $('<div class="w-e-progress-bar"></div>') + private readonly $maxLengthInfo = $('<div class="w-e-max-length-info"></div>') - isComposing: boolean = false - isUpdatingSelection: boolean = false - isDraggingInternally: boolean = false + + isComposing = false + + isUpdatingSelection = false + + isDraggingInternally = false + latestElement: DOMElement | null = null + showPlaceholder = false + $placeholder: Dom7Array | null = null + private latestEditorSelection: Range | null = null constructor(boxSelector: string | DOMElement) { // @ts-ignore 初始化 dom const $box = $(boxSelector) + if ($box.length === 0) { throw new Error(`Cannot find textarea DOM by selector '${boxSelector}'`) } this.$box = $box - const $container = $(`<div class="w-e-text-container"></div>`) + const $container = $('<div class="w-e-text-container"></div>') + $container.append(this.$progressBar) // 进度条 $container.append(this.$maxLengthInfo) // max length 提示信息 $box.append($container) - const $scroll = $(`<div class="w-e-scroll"></div>`) + const $scroll = $('<div class="w-e-scroll"></div>') + $container.append($scroll) this.$scroll = $scroll this.$textAreaContainer = $container @@ -70,6 +87,7 @@ class TextArea { // editor onchange 时触发用户配置的 onChange (需要在 changeViewState 后执行) const { onChange, scroll } = editor.getConfig() + if (onChange) { editor.on('change', () => onChange(editor)) } @@ -92,12 +110,14 @@ class TextArea { private get editorInstance(): IDomEditor { const editor = TEXTAREA_TO_EDITOR.get(this) - if (editor == null) throw new Error('Can not get editor instance') + + if (editor == null) { throw new Error('Can not get editor instance') } return editor } private onDOMSelectionChange = throttle(() => { const editor = this.editorInstance + DOMSelectionToEditor(this, editor) }, 100) @@ -108,7 +128,7 @@ class TextArea { const { $textArea, $scroll } = this const editor = this.editorInstance - if ($textArea == null) return + if ($textArea == null) { return } // 遍历所有事件类型,绑定 forEach(eventHandlerConf, (fn, eventType) => { @@ -119,6 +139,7 @@ class TextArea { // 设置 scroll const { scroll } = editor.getConfig() + if (scroll) { $scroll.css('overflow-y', 'auto') // scroll 自定义事件 @@ -126,7 +147,7 @@ class TextArea { 'scroll', throttle(() => { editor.emit('scroll') - }, 100) + }, 100), ) } } @@ -134,6 +155,7 @@ class TextArea { private onFocusAndOnBlur() { const editor = this.editorInstance const { onBlur, onFocus } = editor.getConfig() + this.latestEditorSelection = editor.selection editor.on('change', () => { @@ -155,9 +177,11 @@ class TextArea { private changeMaxLengthInfo() { const editor = this.editorInstance const { maxLength } = editor.getConfig() + if (maxLength) { const leftLength = DomEditor.getLeftLengthOfMaxLength(editor) const curLength = maxLength - leftLength + this.$maxLengthInfo[0].innerHTML = `${curLength}/${maxLength}` } } @@ -168,6 +192,7 @@ class TextArea { */ changeProgress(progress: number) { const $progressBar = this.$progressBar + $progressBar.css('width', `${progress}%`) // 进度 100% 之后,定时隐藏 diff --git a/packages/core/src/text-area/event-handlers/beforeInput.ts b/packages/core/src/text-area/event-handlers/beforeInput.ts index e7adaeec0..988728cae 100644 --- a/packages/core/src/text-area/event-handlers/beforeInput.ts +++ b/packages/core/src/text-area/event-handlers/beforeInput.ts @@ -3,14 +3,15 @@ * @author wangfupeng */ -import { Editor, Transforms, Range } from 'slate' +import { Editor, Range, Transforms } from 'slate' + import { DomEditor } from '../../editor/dom-editor' import { IDomEditor } from '../../editor/interface' -import TextArea from '../TextArea' -import { hasEditableTarget } from '../helpers' import { DOMStaticRange, isDataTransfer } from '../../utils/dom' import { HAS_BEFORE_INPUT_SUPPORT } from '../../utils/ua' import { EDITOR_TO_CAN_PASTE } from '../../utils/weak-maps' +import { hasEditableTarget } from '../helpers' +import TextArea from '../TextArea' // 补充 beforeInput event 的属性 interface BeforeInputEventType { @@ -25,9 +26,9 @@ function handleBeforeInput(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as Event & BeforeInputEventType const { readOnly } = editor.getConfig() - if (!HAS_BEFORE_INPUT_SUPPORT) return // 有些浏览器完全不支持 beforeInput ,会用 keypress 和 keydown 兼容 - if (readOnly) return - if (!hasEditableTarget(editor, event.target)) return + if (!HAS_BEFORE_INPUT_SUPPORT) { return } // 有些浏览器完全不支持 beforeInput ,会用 keypress 和 keydown 兼容 + if (readOnly) { return } + if (!hasEditableTarget(editor, event.target)) { return } const { selection } = editor const { inputType: type } = event @@ -53,6 +54,7 @@ function handleBeforeInput(e: Event, textarea: TextArea, editor: IDomEditor) { exactMatch: false, suppressThrow: false, }) + if (!selection || !Range.equals(selection, range)) { Transforms.select(editor, range) } @@ -63,6 +65,7 @@ function handleBeforeInput(e: Event, textarea: TextArea, editor: IDomEditor) { // a delete forward/backward command it should delete the selection. if (selection && Range.isExpanded(selection) && type.startsWith('delete')) { const direction = type.endsWith('Backward') ? 'backward' : 'forward' + Editor.deleteFragment(editor, { direction }) return } @@ -135,7 +138,7 @@ function handleBeforeInput(e: Event, textarea: TextArea, editor: IDomEditor) { case 'insertReplacementText': case 'insertText': { if (type === 'insertFromPaste') { - if (!EDITOR_TO_CAN_PASTE.get(editor)) break // 不可默认粘贴 + if (!EDITOR_TO_CAN_PASTE.get(editor)) { break } // 不可默认粘贴 } if (isDataTransfer(data)) { diff --git a/packages/core/src/text-area/event-handlers/blur.ts b/packages/core/src/text-area/event-handlers/blur.ts index 9a213f6b9..56c83e3c3 100644 --- a/packages/core/src/text-area/event-handlers/blur.ts +++ b/packages/core/src/text-area/event-handlers/blur.ts @@ -4,13 +4,14 @@ */ import { Element } from 'slate' + import { DomEditor } from '../../editor/dom-editor' import { IDomEditor } from '../../editor/interface' -import TextArea from '../TextArea' -import { hasEditableTarget } from '../helpers' import { isDOMElement, isDOMNode } from '../../utils/dom' -import { IS_FOCUSED } from '../../utils/weak-maps' import { IS_SAFARI } from '../../utils/ua' +import { IS_FOCUSED } from '../../utils/weak-maps' +import { hasEditableTarget } from '../helpers' +import TextArea from '../TextArea' function handleOnBlur(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as FocusEvent @@ -18,16 +19,16 @@ function handleOnBlur(e: Event, textarea: TextArea, editor: IDomEditor) { const { isUpdatingSelection, latestElement } = textarea const { readOnly } = editor.getConfig() - if (readOnly) return - if (isUpdatingSelection) return - if (!hasEditableTarget(editor, event.target)) return + if (readOnly) { return } + if (isUpdatingSelection) { return } + if (!hasEditableTarget(editor, event.target)) { return } const root = DomEditor.findDocumentOrShadowRoot(editor) // COMPAT: If the current `activeElement` is still the previous // one, this is due to the window being blurred when the tab // itself becomes unfocused, so we want to abort early to allow to // editor to stay focused when the tab becomes focused again. - if (latestElement === root.activeElement) return + if (latestElement === root.activeElement) { return } // relatedTarget 即 blur 之后又 focus 到了哪个元素,如果没有则是 null const { relatedTarget } = event @@ -50,11 +51,12 @@ function handleOnBlur(e: Event, textarea: TextArea, editor: IDomEditor) { // non- editable section of an element that isn't a void node (eg. // a list item of the check list example). if ( - relatedTarget != null && - isDOMNode(relatedTarget) && - DomEditor.hasDOMNode(editor, relatedTarget) + relatedTarget != null + && isDOMNode(relatedTarget) + && DomEditor.hasDOMNode(editor, relatedTarget) ) { const node = DomEditor.toSlateNode(editor, relatedTarget) + if (Element.isElement(node) && !editor.isVoid(node)) { return } @@ -66,6 +68,7 @@ function handleOnBlur(e: Event, textarea: TextArea, editor: IDomEditor) { // 修复在 Safari 下,即使 contenteditable 元素非聚焦状态,并不会删除所选内容 if (IS_SAFARI) { const domSelection = root.getSelection() + domSelection?.removeAllRanges() } diff --git a/packages/core/src/text-area/event-handlers/click.ts b/packages/core/src/text-area/event-handlers/click.ts index 2e2e106c9..48934fb38 100644 --- a/packages/core/src/text-area/event-handlers/click.ts +++ b/packages/core/src/text-area/event-handlers/click.ts @@ -3,19 +3,22 @@ * @author wangfupeng */ -import { Editor, Path, Transforms, Node } from 'slate' -import { IDomEditor } from '../../editor/interface' +import { + Editor, Node, Path, Transforms, +} from 'slate' + import { DomEditor } from '../../editor/dom-editor' -import TextArea from '../TextArea' -import { hasTarget } from '../helpers' +import { IDomEditor } from '../../editor/interface' import { isDOMNode } from '../../utils/dom' +import { hasTarget } from '../helpers' +import TextArea from '../TextArea' function handleOnClick(event: Event, textarea: TextArea, editor: IDomEditor) { const { readOnly } = editor.getConfig() - if (readOnly) return - if (!hasTarget(editor, event.target)) return - if (!isDOMNode(event.target)) return + if (readOnly) { return } + if (!hasTarget(editor, event.target)) { return } + if (!isDOMNode(event.target)) { return } const node = DomEditor.toSlateNode(editor, event.target) const path = DomEditor.findPath(editor, node) @@ -26,6 +29,7 @@ function handleOnClick(event: Event, textarea: TextArea, editor: IDomEditor) { // and that it still refers to the same node. if (Editor.hasPath(editor, path)) { const lookupNode = Node.get(editor, path) + if (lookupNode === node) { const start = Editor.start(editor, path) const end = Editor.end(editor, path) @@ -35,6 +39,7 @@ function handleOnClick(event: Event, textarea: TextArea, editor: IDomEditor) { if (startVoid && endVoid && Path.equals(startVoid[1], endVoid[1])) { const range = Editor.range(editor, start) + Transforms.select(editor, range) } } diff --git a/packages/core/src/text-area/event-handlers/composition.ts b/packages/core/src/text-area/event-handlers/composition.ts index 58ce6224e..5569fb73c 100644 --- a/packages/core/src/text-area/event-handlers/composition.ts +++ b/packages/core/src/text-area/event-handlers/composition.ts @@ -3,15 +3,18 @@ * @author wangfupeng */ -import { Editor, Range, Element, Text } from 'slate' -import { IDomEditor } from '../../editor/interface' +import { + Editor, Element, Range, Text, +} from 'slate' + import { DomEditor } from '../../editor/dom-editor' -import TextArea from '../TextArea' -import { hasEditableTarget } from '../helpers' -import { IS_SAFARI, IS_CHROME, IS_FIREFOX } from '../../utils/ua' +import { IDomEditor } from '../../editor/interface' import { DOMNode } from '../../utils/dom' +import { IS_CHROME, IS_FIREFOX, IS_SAFARI } from '../../utils/ua' +import { hasEditableTarget } from '../helpers' import { hidePlaceholder } from '../place-holder' import { editorSelectionToDOM } from '../syncSelection' +import TextArea from '../TextArea' const EDITOR_TO_TEXT: WeakMap<IDomEditor, string> = new WeakMap() const EDITOR_TO_START_CONTAINER: WeakMap<IDomEditor, DOMNode> = new WeakMap() @@ -25,9 +28,10 @@ const EDITOR_TO_START_CONTAINER: WeakMap<IDomEditor, DOMNode> = new WeakMap() export function handleCompositionStart(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as CompositionEvent - if (!hasEditableTarget(editor, event.target)) return + if (!hasEditableTarget(editor, event.target)) { return } const { selection } = editor + if (selection && Range.isExpanded(selection)) { Editor.deleteFragment(editor) @@ -45,6 +49,7 @@ export function handleCompositionStart(e: Event, textarea: TextArea, editor: IDo const domRange = DomEditor.toDOMRange(editor, selection) const startContainer = domRange.startContainer const curText = startContainer.textContent || '' + EDITOR_TO_TEXT.set(editor, curText) // 记录下 dom range startContainer @@ -63,7 +68,7 @@ export function handleCompositionStart(e: Event, textarea: TextArea, editor: IDo * @param editor editor */ export function handleCompositionUpdate(event: Event, textarea: TextArea, editor: IDomEditor) { - if (!hasEditableTarget(editor, event.target)) return + if (!hasEditableTarget(editor, event.target)) { return } textarea.isComposing = true } @@ -77,11 +82,12 @@ export function handleCompositionUpdate(event: Event, textarea: TextArea, editor export function handleCompositionEnd(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as CompositionEvent - if (!hasEditableTarget(editor, event.target)) return + if (!hasEditableTarget(editor, event.target)) { return } textarea.isComposing = false const { selection } = editor - if (selection == null) return + + if (selection == null) { return } // 清理可能暴露的 text 节点 // 例如 chrome 在链接后面,输入拼音,就会出现有暴露出来的 text node @@ -98,6 +104,7 @@ export function handleCompositionEnd(e: Event, textarea: TextArea, editor: IDomE for (let i = 0; i < start.path.length; i++) { const [node] = Editor.node(editor, start.path.slice(0, i + 1)) + if (Element.isElement(node)) { if (((IS_SAFARI || IS_FIREFOX) && node.type === 'link') || node.type === 'code') { DomEditor.setNewKey(paragraph) @@ -107,14 +114,18 @@ export function handleCompositionEnd(e: Event, textarea: TextArea, editor: IDomE } const { data } = event - if (!data) return + + if (!data) { return } // 检查 maxLength -【注意】这里只处理拼音输入的 maxLength 限制。其他限制,在插件 with-max-length.ts 中处理 const { maxLength } = editor.getConfig() + if (maxLength) { const leftLengthOfMaxLength = DomEditor.getLeftLengthOfMaxLength(editor) + if (leftLengthOfMaxLength < data.length) { const domRange = DomEditor.toDOMRange(editor, selection) + domRange.startContainer.textContent = EDITOR_TO_TEXT.get(editor) || '' if (leftLengthOfMaxLength > 0) { // 剩余长度 >0 ,但小于 data 长度,截取一部分插入 @@ -128,6 +139,7 @@ export function handleCompositionEnd(e: Event, textarea: TextArea, editor: IDomE // 拼音输入,当选区的边缘在两个 text node 之间时 需要重置为 domselction 的 选区 const root = DomEditor.findDocumentOrShadowRoot(editor) const domSelection = root.getSelection() + if (domSelection && areBothTextNodes(editor, selection)) { editor.selection = DomEditor.toSlateRange(editor, domSelection, { exactMatch: false, @@ -141,10 +153,13 @@ export function handleCompositionEnd(e: Event, textarea: TextArea, editor: IDomE if (!IS_SAFARI) { setTimeout(() => { const { selection } = editor - if (selection == null) return + + if (selection == null) { return } const oldStartContainer = EDITOR_TO_START_CONTAINER.get(editor) // 拼音输入开始时的 text node - if (oldStartContainer == null) return + + if (oldStartContainer == null) { return } const curStartContainer = DomEditor.toDOMRange(editor, selection).startContainer // 拼音输入结束时的 text node + if (curStartContainer === oldStartContainer) { // 拼音输入的开始和结束,都在同一个 text node ,则不做处理 return @@ -157,14 +172,16 @@ export function handleCompositionEnd(e: Event, textarea: TextArea, editor: IDomE function areBothTextNodes(editor, selection) { if (Range.isCollapsed(selection)) { const { anchor, focus } = selection + if ( - anchor.path.length === 2 && - focus.path.length === 2 && - (anchor.offset === 0 || focus.path.offset === 0) + anchor.path.length === 2 + && focus.path.length === 2 + && (anchor.offset === 0 || focus.path.offset === 0) ) { const nowEntry = Editor.node(editor, anchor.path) const nowPath = anchor.offset === 0 ? anchor.path : focus.path const prePath = [nowPath[0], nowPath[1] - 1] + if (nowPath[1] === 0) { return false } diff --git a/packages/core/src/text-area/event-handlers/copy.ts b/packages/core/src/text-area/event-handlers/copy.ts index 2b1a05271..ab44b6aa9 100644 --- a/packages/core/src/text-area/event-handlers/copy.ts +++ b/packages/core/src/text-area/event-handlers/copy.ts @@ -4,18 +4,19 @@ */ import { IDomEditor } from '../../editor/interface' +import { hasEditableTarget } from '../helpers' // import { DomEditor } from '../../editor/dom-editor' import TextArea from '../TextArea' -import { hasEditableTarget } from '../helpers' function handleOnCopy(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as ClipboardEvent - if (!hasEditableTarget(editor, event.target)) return + if (!hasEditableTarget(editor, event.target)) { return } event.preventDefault() const data = event.clipboardData - if (data == null) return + + if (data == null) { return } editor.setFragmentData(data) } diff --git a/packages/core/src/text-area/event-handlers/cut.ts b/packages/core/src/text-area/event-handlers/cut.ts index f0bff0374..7d3fecfb5 100644 --- a/packages/core/src/text-area/event-handlers/cut.ts +++ b/packages/core/src/text-area/event-handlers/cut.ts @@ -3,23 +3,27 @@ * @author wangfupeng */ -import { Editor, Range, Node, Transforms } from 'slate' +import { + Editor, Node, Range, Transforms, +} from 'slate' + import { IDomEditor } from '../../editor/interface' -import TextArea from '../TextArea' import { hasEditableTarget } from '../helpers' +import TextArea from '../TextArea' function handleOnCut(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as ClipboardEvent const { selection } = editor const { readOnly } = editor.getConfig() - if (readOnly) return - if (!hasEditableTarget(editor, event.target)) return + if (readOnly) { return } + if (!hasEditableTarget(editor, event.target)) { return } event.preventDefault() const data = event.clipboardData - if (data == null) return + + if (data == null) { return } editor.setFragmentData(data) if (selection) { @@ -27,6 +31,7 @@ function handleOnCut(e: Event, textarea: TextArea, editor: IDomEditor) { Editor.deleteFragment(editor) } else { const node = Node.parent(editor, selection.anchor.path) + if (Editor.isVoid(editor, node)) { Transforms.delete(editor) } diff --git a/packages/core/src/text-area/event-handlers/drag.ts b/packages/core/src/text-area/event-handlers/drag.ts index dde3a30c3..ff374495c 100644 --- a/packages/core/src/text-area/event-handlers/drag.ts +++ b/packages/core/src/text-area/event-handlers/drag.ts @@ -4,17 +4,20 @@ */ import { Editor, Transforms } from 'slate' + import { DomEditor } from '../../editor/dom-editor' import { IDomEditor } from '../../editor/interface' -import TextArea from '../TextArea' import { hasTarget } from '../helpers' +import TextArea from '../TextArea' export function handleOnDragstart(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as DragEvent - if (!hasTarget(editor, event.target)) return + + if (!hasTarget(editor, event.target)) { return } const { readOnly } = editor.getConfig() - if (readOnly) return + + if (readOnly) { return } const node = DomEditor.toSlateNode(editor, event.target) const path = DomEditor.findPath(editor, node) @@ -24,11 +27,13 @@ export function handleOnDragstart(e: Event, textarea: TextArea, editor: IDomEdit // so that it shows up in the selection's fragment. if (voidMatch) { const range = Editor.range(editor, path) + Transforms.select(editor, range) } const data = event.dataTransfer - if (data == null) return + + if (data == null) { return } textarea.isDraggingInternally = true @@ -36,12 +41,13 @@ export function handleOnDragstart(e: Event, textarea: TextArea, editor: IDomEdit } export function handleOnDragover(event: Event, textarea: TextArea, editor: IDomEditor) { - if (!hasTarget(editor, event.target)) return + if (!hasTarget(editor, event.target)) { return } // Only when the target is void, call `preventDefault` to signal // that drops are allowed. Editable content is droppable by // default, and calling `preventDefault` hides the cursor. const node = DomEditor.toSlateNode(editor, event.target) + if (Editor.isVoid(editor, node)) { event.preventDefault() } @@ -51,9 +57,9 @@ export function handleOnDragend(e: Event, textarea: TextArea, editor: IDomEditor const event = e as DragEvent const { readOnly } = editor.getConfig() - if (readOnly) return - if (!textarea.isDraggingInternally) return - if (!hasTarget(editor, event.target)) return + if (readOnly) { return } + if (!textarea.isDraggingInternally) { return } + if (!hasTarget(editor, event.target)) { return } textarea.isDraggingInternally = false } diff --git a/packages/core/src/text-area/event-handlers/drop.ts b/packages/core/src/text-area/event-handlers/drop.ts index 107fe11e7..76320bb82 100644 --- a/packages/core/src/text-area/event-handlers/drop.ts +++ b/packages/core/src/text-area/event-handlers/drop.ts @@ -4,25 +4,26 @@ */ import { Transforms } from 'slate' -import { IDomEditor } from '../../editor/interface' + import { DomEditor } from '../../editor/dom-editor' -import TextArea from '../TextArea' -import { hasTarget } from '../helpers' +import { IDomEditor } from '../../editor/interface' import { HAS_BEFORE_INPUT_SUPPORT, IS_SAFARI } from '../../utils/ua' +import { hasTarget } from '../helpers' +import TextArea from '../TextArea' function handleOnDrop(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as DragEvent const data = event.dataTransfer const { readOnly } = editor.getConfig() - if (readOnly) return - if (!hasTarget(editor, event.target)) return - if (data == null) return + if (readOnly) { return } + if (!hasTarget(editor, event.target)) { return } + if (data == null) { return } if (HAS_BEFORE_INPUT_SUPPORT) { if (IS_SAFARI) { // safari 不支持拖拽文件 - if (data.files.length > 0) return + if (data.files.length > 0) { return } } } @@ -31,6 +32,7 @@ function handleOnDrop(e: Event, textarea: TextArea, editor: IDomEditor) { // Keep a reference to the dragged range before updating selection const draggedRange = editor.selection const range = DomEditor.findEventRange(editor, event) + Transforms.select(editor, range) if (textarea.isDraggingInternally) { diff --git a/packages/core/src/text-area/event-handlers/focus.ts b/packages/core/src/text-area/event-handlers/focus.ts index 35f701464..11af3d32d 100644 --- a/packages/core/src/text-area/event-handlers/focus.ts +++ b/packages/core/src/text-area/event-handlers/focus.ts @@ -3,15 +3,16 @@ * @author wangfupeng */ -import { IDomEditor } from '../../editor/interface' import { DomEditor } from '../../editor/dom-editor' -import TextArea from '../TextArea' +import { IDomEditor } from '../../editor/interface' import { IS_FIREFOX } from '../../utils/ua' import { IS_FOCUSED } from '../../utils/weak-maps' +import TextArea from '../TextArea' function handleOnFocus(event: Event, textarea: TextArea, editor: IDomEditor) { const el = DomEditor.toDOMNode(editor, editor) const root = DomEditor.findDocumentOrShadowRoot(editor) + textarea.latestElement = root.activeElement // COMPAT: If the editor has nested editable elements, the focus diff --git a/packages/core/src/text-area/event-handlers/index.ts b/packages/core/src/text-area/event-handlers/index.ts index cc49b8666..0ec8ae74a 100644 --- a/packages/core/src/text-area/event-handlers/index.ts +++ b/packages/core/src/text-area/event-handlers/index.ts @@ -5,20 +5,20 @@ import handleBeforeInput from './beforeInput' import handleOnBlur from './blur' -import handleOnFocus from './focus' import handleOnClick from './click' import { - handleCompositionStart, handleCompositionEnd, + handleCompositionStart, handleCompositionUpdate, } from './composition' -import handleOnKeydown from './keydown' -import handleKeypress from './keypress' import handleOnCopy from './copy' import handleOnCut from './cut' -import handleOnPaste from './paste' -import { handleOnDragover, handleOnDragstart, handleOnDragend } from './drag' +import { handleOnDragend, handleOnDragover, handleOnDragstart } from './drag' import handleOnDrop from './drop' +import handleOnFocus from './focus' +import handleOnKeydown from './keydown' +import handleKeypress from './keypress' +import handleOnPaste from './paste' const eventConf = { beforeinput: handleBeforeInput, diff --git a/packages/core/src/text-area/event-handlers/keydown.ts b/packages/core/src/text-area/event-handlers/keydown.ts index f58a4865d..d46f0560d 100644 --- a/packages/core/src/text-area/event-handlers/keydown.ts +++ b/packages/core/src/text-area/event-handlers/keydown.ts @@ -4,13 +4,16 @@ */ import { isHotkey } from 'is-hotkey' -import { Editor, Transforms, Range, Node, Element } from 'slate' +import { + Editor, Element, Node, Range, Transforms, +} from 'slate' + import { IDomEditor } from '../../editor/interface' -import TextArea from '../TextArea' import Hotkeys from '../../utils/hotkeys' -import { hasEditableTarget } from '../helpers' import { HAS_BEFORE_INPUT_SUPPORT, IS_CHROME, IS_SAFARI } from '../../utils/ua' -import { EDITOR_TO_TOOLBAR, EDITOR_TO_HOVER_BAR } from '../../utils/weak-maps' +import { EDITOR_TO_HOVER_BAR, EDITOR_TO_TOOLBAR } from '../../utils/weak-maps' +import { hasEditableTarget } from '../helpers' +import TextArea from '../TextArea' function preventDefault(event: Event) { event.preventDefault() @@ -25,13 +28,17 @@ function triggerMenuHotKey(editor: IDomEditor, event: KeyboardEvent) { // 合并所有 menus const allMenus = { ...toolbarMenus, ...hoverbarMenus } - for (let key in allMenus) { + + for (const key in allMenus) { const menu = allMenus[key] const { hotkey } = menu + if (hotkey && isHotkey(hotkey, event)) { const disabled = menu.isDisabled(editor) + if (!disabled) { const val = menu.getValue(editor) + menu.exec(editor, val) // 执行 menu 命令 } } @@ -43,9 +50,9 @@ function handleOnKeydown(e: Event, textarea: TextArea, editor: IDomEditor) { const { selection } = editor const { readOnly } = editor.getConfig() - if (readOnly) return - if (textarea.isComposing) return - if (!hasEditableTarget(editor, event.target)) return + if (readOnly) { return } + if (textarea.isComposing) { return } + if (!hasEditableTarget(editor, event.target)) { return } // 触发 menu 快捷键 triggerMenuHotKey(editor, event) @@ -230,30 +237,27 @@ function handleOnKeydown(e: Event, textarea: TextArea, editor: IDomEditor) { } else { Editor.deleteForward(editor, { unit: 'word' }) } - return + } - } else { - if (IS_CHROME || IS_SAFARI) { - // COMPAT: Chrome and Safari support `beforeinput` event but do not fire - // an event when deleting backwards in a selected void inline node - // 修复在 Chrome 和 Safari 中删除内容时,内联空节点被选中 + } else if (IS_CHROME || IS_SAFARI) { + // COMPAT: Chrome and Safari support `beforeinput` event but do not fire + // an event when deleting backwards in a selected void inline node + // 修复在 Chrome 和 Safari 中删除内容时,内联空节点被选中 + if ( + selection + && (Hotkeys.isDeleteBackward(event) || Hotkeys.isDeleteForward(event)) + && Range.isCollapsed(selection) + ) { + const currentNode = Node.parent(editor, selection.anchor.path) + if ( - selection && - (Hotkeys.isDeleteBackward(event) || Hotkeys.isDeleteForward(event)) && - Range.isCollapsed(selection) + Element.isElement(currentNode) + && Editor.isVoid(editor, currentNode) + && Editor.isInline(editor, currentNode) ) { - const currentNode = Node.parent(editor, selection.anchor.path) - - if ( - Element.isElement(currentNode) && - Editor.isVoid(editor, currentNode) && - Editor.isInline(editor, currentNode) - ) { - event.preventDefault() - Transforms.delete(editor, { unit: 'block' }) - - return - } + event.preventDefault() + Transforms.delete(editor, { unit: 'block' }) + } } } diff --git a/packages/core/src/text-area/event-handlers/keypress.ts b/packages/core/src/text-area/event-handlers/keypress.ts index 9c42066f5..4284c783d 100644 --- a/packages/core/src/text-area/event-handlers/keypress.ts +++ b/packages/core/src/text-area/event-handlers/keypress.ts @@ -4,20 +4,22 @@ */ import { Editor } from 'slate' + import { IDomEditor } from '../../editor/interface' -import TextArea from '../TextArea' import { HAS_BEFORE_INPUT_SUPPORT } from '../../utils/ua' import { hasEditableTarget } from '../helpers' +import TextArea from '../TextArea' // 【注意】虽然 keypress 事件已经过时(建议用 keydown 取代),但这里是为了兼容 beforeinput ,所以不会在高级浏览器生效,不用升级 keydown function handleKeypress(event: Event, textarea: TextArea, editor: IDomEditor) { // 这里是兼容不完全支持 beforeInput 的浏览器。对于支持 beforeInput 的浏览器,会用 beforeinput 事件处理 - if (HAS_BEFORE_INPUT_SUPPORT) return + if (HAS_BEFORE_INPUT_SUPPORT) { return } const { readOnly } = editor.getConfig() - if (readOnly) return - if (!hasEditableTarget(editor, event.target)) return + + if (readOnly) { return } + if (!hasEditableTarget(editor, event.target)) { return } event.preventDefault() diff --git a/packages/core/src/text-area/event-handlers/paste.ts b/packages/core/src/text-area/event-handlers/paste.ts index 128bda27c..a00d27a4e 100644 --- a/packages/core/src/text-area/event-handlers/paste.ts +++ b/packages/core/src/text-area/event-handlers/paste.ts @@ -3,13 +3,13 @@ * @author wangfupeng */ -import { IDomEditor } from '../../editor/interface' import { DomEditor } from '../../editor/dom-editor' -import TextArea from '../TextArea' -import { hasEditableTarget } from '../helpers' +import { IDomEditor } from '../../editor/interface' import { isPlainTextOnlyPaste } from '../../utils/dom' import { HAS_BEFORE_INPUT_SUPPORT } from '../../utils/ua' import { EDITOR_TO_CAN_PASTE } from '../../utils/weak-maps' +import { hasEditableTarget } from '../helpers' +import TextArea from '../TextArea' function handleOnPaste(e: Event, textarea: TextArea, editor: IDomEditor) { EDITOR_TO_CAN_PASTE.set(editor, true) // 标记为:可执行默认粘贴 @@ -17,12 +17,14 @@ function handleOnPaste(e: Event, textarea: TextArea, editor: IDomEditor) { const event = e as ClipboardEvent const { readOnly } = editor.getConfig() - if (readOnly) return - if (!hasEditableTarget(editor, event.target)) return + if (readOnly) { return } + if (!hasEditableTarget(editor, event.target)) { return } const { customPaste } = editor.getConfig() + if (customPaste) { const res = customPaste(editor, event) + if (res === false) { // 自行实现粘贴,不执行默认粘贴 EDITOR_TO_CAN_PASTE.set(editor, false) // 标记为:不可执行默认粘贴 @@ -32,12 +34,13 @@ function handleOnPaste(e: Event, textarea: TextArea, editor: IDomEditor) { // 如果支持 beforeInput 且不是纯粘贴文本(如 html、图片文件),则使用 beforeInput 来实现 // 这里只处理:不支持 beforeInput 或者 粘贴纯文本 - if (HAS_BEFORE_INPUT_SUPPORT && !isPlainTextOnlyPaste(event)) return + if (HAS_BEFORE_INPUT_SUPPORT && !isPlainTextOnlyPaste(event)) { return } event.preventDefault() const data = event.clipboardData - if (data == null) return + + if (data == null) { return } editor.insertData(data) } diff --git a/packages/core/src/text-area/helpers.ts b/packages/core/src/text-area/helpers.ts index fbf07743b..0e87b161d 100644 --- a/packages/core/src/text-area/helpers.ts +++ b/packages/core/src/text-area/helpers.ts @@ -4,23 +4,24 @@ */ import { Editor } from 'slate' -import { DOMRange, DOMNode, isDOMNode } from '../utils/dom' -import { IDomEditor } from '../editor/interface' + import { DomEditor } from '../editor/dom-editor' +import { IDomEditor } from '../editor/interface' +import { DOMNode, DOMRange, isDOMNode } from '../utils/dom' /** * Check if two DOM range objects are equal. */ export const isRangeEqual = (a: DOMRange, b: DOMRange) => { return ( - (a.startContainer === b.startContainer && - a.startOffset === b.startOffset && - a.endContainer === b.endContainer && - a.endOffset === b.endOffset) || - (a.startContainer === b.endContainer && - a.startOffset === b.endOffset && - a.endContainer === b.startContainer && - a.endOffset === b.startOffset) + (a.startContainer === b.startContainer + && a.startOffset === b.startOffset + && a.endContainer === b.endContainer + && a.endOffset === b.endOffset) + || (a.startContainer === b.endContainer + && a.startOffset === b.endOffset + && a.endContainer === b.startContainer + && a.endOffset === b.startOffset) ) } @@ -29,7 +30,7 @@ export const isRangeEqual = (a: DOMRange, b: DOMRange) => { */ export function hasEditableTarget( editor: IDomEditor, - target: EventTarget | null + target: EventTarget | null, ): target is DOMNode { return isDOMNode(target) && DomEditor.hasDOMNode(editor, target, { editable: true }) } @@ -39,12 +40,14 @@ export function hasEditableTarget( */ export function isTargetInsideNonReadonlyVoid( editor: IDomEditor, - target: EventTarget | null + target: EventTarget | null, ): boolean { const { readOnly } = editor.getConfig() - if (readOnly) return false + + if (readOnly) { return false } const slateNode = hasTarget(editor, target) && DomEditor.toSlateNode(editor, target) + return Editor.isVoid(editor, slateNode) } diff --git a/packages/core/src/text-area/place-holder.ts b/packages/core/src/text-area/place-holder.ts index 613649f0e..b9760ee6a 100644 --- a/packages/core/src/text-area/place-holder.ts +++ b/packages/core/src/text-area/place-holder.ts @@ -4,8 +4,8 @@ */ import { IDomEditor } from '../editor/interface' -import TextArea from './TextArea' import $ from '../utils/dom' +import TextArea from './TextArea' /** * 处理 placeholder @@ -14,7 +14,8 @@ import $ from '../utils/dom' */ export function handlePlaceholder(textarea: TextArea, editor: IDomEditor) { const { placeholder } = editor.getConfig() - if (!placeholder) return + + if (!placeholder) { return } const isEmpty = editor.isEmpty() @@ -22,6 +23,7 @@ export function handlePlaceholder(textarea: TextArea, editor: IDomEditor) { if (isEmpty && !textarea.showPlaceholder && !textarea.isComposing) { if (textarea.$placeholder == null) { const $placeholder = $(`<div class="w-e-text-placeholder">${placeholder}</div>`) + textarea.$textAreaContainer.append($placeholder) textarea.$placeholder = $placeholder } @@ -34,7 +36,7 @@ export function handlePlaceholder(textarea: TextArea, editor: IDomEditor) { if (!isEmpty && textarea.showPlaceholder) { textarea.$placeholder?.hide() textarea.showPlaceholder = false // 记录 - return + } } @@ -45,10 +47,12 @@ export function handlePlaceholder(textarea: TextArea, editor: IDomEditor) { */ export function hidePlaceholder(textarea: TextArea, editor: IDomEditor) { const { placeholder } = editor.getConfig() - if (!placeholder) return + + if (!placeholder) { return } const isEmpty = editor.isEmpty() - if (!isEmpty) return + + if (!isEmpty) { return } if (textarea.showPlaceholder) { textarea.$placeholder?.hide() diff --git a/packages/core/src/text-area/syncSelection.ts b/packages/core/src/text-area/syncSelection.ts index 93501c5c5..7ddef28a4 100644 --- a/packages/core/src/text-area/syncSelection.ts +++ b/packages/core/src/text-area/syncSelection.ts @@ -3,16 +3,16 @@ * @author wangfupeng */ -import { Range, Transforms } from 'slate' import scrollIntoView from 'scroll-into-view-if-needed' +import { Range, Transforms } from 'slate' -import { IDomEditor } from '../editor/interface' import { DomEditor } from '../editor/dom-editor' -import TextArea from './TextArea' -import { EDITOR_TO_ELEMENT, IS_FOCUSED } from '../utils/weak-maps' +import { IDomEditor } from '../editor/interface' +import { DOMElement } from '../utils/dom' import { IS_FIREFOX } from '../utils/ua' +import { EDITOR_TO_ELEMENT, IS_FOCUSED } from '../utils/weak-maps' import { hasEditableTarget, isTargetInsideNonReadonlyVoid } from './helpers' -import { DOMElement } from '../utils/dom' +import TextArea from './TextArea' /** * editor onchange 时,将 editor selection 同步给 DOM @@ -26,21 +26,22 @@ export function editorSelectionToDOM(textarea: TextArea, editor: IDomEditor, foc const root = DomEditor.findDocumentOrShadowRoot(editor) const domSelection = root.getSelection() - if (!domSelection) return - if (textarea.isComposing && !focus) return - if (!editor.isFocused()) return + if (!domSelection) { return } + if (textarea.isComposing && !focus) { return } + if (!editor.isFocused()) { return } const hasDomSelection = domSelection.type !== 'None' // If the DOM selection is properly unset, we're done. - if (!selection && !hasDomSelection) return + if (!selection && !hasDomSelection) { return } // verify that the dom selection is in the editor const editorElement = EDITOR_TO_ELEMENT.get(editor)! let hasDomSelectionInEditor = false + if ( - editorElement.contains(domSelection.anchorNode) && - editorElement.contains(domSelection.focusNode) + editorElement.contains(domSelection.anchorNode) + && editorElement.contains(domSelection.focusNode) ) { hasDomSelectionInEditor = true } @@ -54,12 +55,14 @@ export function editorSelectionToDOM(textarea: TextArea, editor: IDomEditor, foc // (e.g. when clicking on contentEditable:false element) suppressThrow: true, }) + if (slateRange && Range.equals(slateRange, selection)) { let canReturn = true // 选区在 table 时,需要特殊处理 if (Range.isCollapsed(selection)) { const { anchorNode, anchorOffset } = domSelection + if (anchorNode === editorElement) { const childNodes = editorElement.childNodes let tableElem @@ -79,7 +82,7 @@ export function editorSelectionToDOM(textarea: TextArea, editor: IDomEditor, foc } // 其他情况,就此结束 - if (canReturn) return + if (canReturn) { return } } } @@ -99,31 +102,33 @@ export function editorSelectionToDOM(textarea: TextArea, editor: IDomEditor, foc textarea.isUpdatingSelection = true const newDomRange = selection && DomEditor.toDOMRange(editor, selection) + if (newDomRange) { if (Range.isBackward(selection!)) { domSelection.setBaseAndExtent( newDomRange.endContainer, newDomRange.endOffset, newDomRange.startContainer, - newDomRange.startOffset + newDomRange.startOffset, ) } else { domSelection.setBaseAndExtent( newDomRange.startContainer, newDomRange.startOffset, newDomRange.endContainer, - newDomRange.endOffset + newDomRange.endOffset, ) } // 滚动到选区 - let leafEl = newDomRange.startContainer.parentElement! as Element + const leafEl = newDomRange.startContainer.parentElement! as Element const spacer = leafEl.closest('[data-slate-spacer]') // 这个 if 防止选中图片时发生滚动 if (!spacer) { leafEl.getBoundingClientRect = newDomRange.getBoundingClientRect.bind(newDomRange) const body = document.body + scrollIntoView(leafEl, { scrollMode: 'if-needed', boundary: config.scroll ? editorElement.parentElement : body, // issue 4215 @@ -157,10 +162,10 @@ export function DOMSelectionToEditor(textarea: TextArea, editor: IDomEditor) { const { isComposing, isUpdatingSelection, isDraggingInternally } = textarea const config = editor.getConfig() - if (config.readOnly) return - if (isComposing) return - if (isUpdatingSelection) return - if (isDraggingInternally) return + if (config.readOnly) { return } + if (isComposing) { return } + if (isUpdatingSelection) { return } + if (isDraggingInternally) { return } const root = DomEditor.findDocumentOrShadowRoot(editor) const { activeElement } = root @@ -182,16 +187,15 @@ export function DOMSelectionToEditor(textarea: TextArea, editor: IDomEditor) { const { anchorNode, focusNode } = domSelection - const anchorNodeSelectable = - hasEditableTarget(editor, anchorNode) || isTargetInsideNonReadonlyVoid(editor, anchorNode) - const focusNodeSelectable = - hasEditableTarget(editor, focusNode) || isTargetInsideNonReadonlyVoid(editor, focusNode) + const anchorNodeSelectable = hasEditableTarget(editor, anchorNode) || isTargetInsideNonReadonlyVoid(editor, anchorNode) + const focusNodeSelectable = hasEditableTarget(editor, focusNode) || isTargetInsideNonReadonlyVoid(editor, focusNode) if (anchorNodeSelectable && focusNodeSelectable) { const range = DomEditor.toSlateRange(editor, domSelection, { exactMatch: false, suppressThrow: false, }) + Transforms.select(editor, range) } else { // 禁用此行,让光标选区继续生效 diff --git a/packages/core/src/text-area/update-view.ts b/packages/core/src/text-area/update-view.ts index 33f706403..36a3a5a56 100644 --- a/packages/core/src/text-area/update-view.ts +++ b/packages/core/src/text-area/update-view.ts @@ -4,20 +4,21 @@ */ import { h, VNode } from 'snabbdom' + import { IDomEditor } from '../editor/interface' -import TextArea from './TextArea' -import { genPatchFn, normalizeVnodeData } from '../utils/vdom' -import $, { Dom7Array, getDefaultView, getElementById } from '../utils/dom' import { node2Vnode } from '../render/node2Vnode' +import $, { Dom7Array, getDefaultView, getElementById } from '../utils/dom' +import { genPatchFn, normalizeVnodeData } from '../utils/vdom' import { + EDITOR_TO_ELEMENT, + EDITOR_TO_WINDOW, + ELEMENT_TO_NODE, IS_FIRST_PATCH, + NODE_TO_ELEMENT, TEXTAREA_TO_PATCH_FN, TEXTAREA_TO_VNODE, - EDITOR_TO_ELEMENT, - NODE_TO_ELEMENT, - ELEMENT_TO_NODE, - EDITOR_TO_WINDOW, } from '../utils/weak-maps' +import TextArea from './TextArea' function genElemId(id: number) { return `w-e-textarea-${id}` @@ -31,7 +32,7 @@ function genElemId(id: number) { function genRootVnode(elemId: string, readOnly = false): VNode { return h(`div#${elemId}`, { props: { - contentEditable: readOnly ? false : true, + contentEditable: !readOnly, }, }) // 其他属性在 genRootElem 中定,这里不用重复写 @@ -72,24 +73,29 @@ function updateView(textarea: TextArea, editor: IDomEditor) { // 生成 newVnode const newVnode = genRootVnode(elemId, readOnly) const content = editor.children || [] + newVnode.children = content.map((node, i) => { - let vnode = node2Vnode(node, i, editor, editor) + const vnode = node2Vnode(node, i, editor, editor) + normalizeVnodeData(vnode) // 整理 vnode.data 以符合 snabbdom 的要求 return vnode }) let textareaElem let isFirstPatch = IS_FIRST_PATCH.get(textarea) - if (isFirstPatch == null) isFirstPatch = true // 尚未赋值,也是第一次 + + if (isFirstPatch == null) { isFirstPatch = true } // 尚未赋值,也是第一次 if (isFirstPatch) { // 第一次 patch ,先生成 elem const $textArea = genRootElem(elemId, readOnly) + $scroll.append($textArea) textarea.$textArea = $textArea // 存储下编辑区域的 DOM 节点 textareaElem = $textArea[0] // 再生成 patch 函数,并执行 const patchFn = genPatchFn() + patchFn(textareaElem, newVnode) // 存储相关信息 @@ -99,7 +105,8 @@ function updateView(textarea: TextArea, editor: IDomEditor) { // 不是第一次 patch const curVnode = TEXTAREA_TO_VNODE.get(textarea) const patchFn = TEXTAREA_TO_PATCH_FN.get(textarea) - if (curVnode == null || patchFn == null) return + + if (curVnode == null || patchFn == null) { return } textareaElem = curVnode.elm patchFn(curVnode, newVnode) @@ -109,11 +116,12 @@ function updateView(textarea: TextArea, editor: IDomEditor) { textareaElem = getElementById(elemId) // 通过 getElementById 获取的有可能是 null (销毁、重建时,可能会发生这种情况) - if (textareaElem == null) return + if (textareaElem == null) { return } } // focus let isFocused + if (isFirstPatch) { // 初次渲染 isFocused = autoFocus @@ -130,6 +138,7 @@ function updateView(textarea: TextArea, editor: IDomEditor) { // 存储相关信息 if (isFirstPatch) { const window = getDefaultView(textareaElem) + window && EDITOR_TO_WINDOW.set(editor, window) } diff --git a/packages/core/src/to-html/elem2html.ts b/packages/core/src/to-html/elem2html.ts index 91b1320ad..0367586fc 100644 --- a/packages/core/src/to-html/elem2html.ts +++ b/packages/core/src/to-html/elem2html.ts @@ -4,9 +4,10 @@ */ import { Editor, Element } from 'slate' + import { IDomEditor } from '../editor/interface' +import { ELEM_TO_HTML_CONF, ElemToHtmlFnType, STYLE_TO_HTML_FN_LIST } from './index' import node2html from './node2html' -import { ElemToHtmlFnType, ELEM_TO_HTML_CONF, STYLE_TO_HTML_FN_LIST } from './index' /** * 默认的 toHtml 函数 @@ -17,6 +18,7 @@ import { ElemToHtmlFnType, ELEM_TO_HTML_CONF, STYLE_TO_HTML_FN_LIST } from './in function defaultParser(elemNode: Element, childrenHtml: string, editor: IDomEditor) { const isInline = editor.isInline(elemNode) const tag = isInline ? 'span' : 'div' + return `<${tag}>${childrenHtml}</${tag}>` } @@ -26,6 +28,7 @@ function defaultParser(elemNode: Element, childrenHtml: string, editor: IDomEdit */ function getParser(type: string): ElemToHtmlFnType { const fn = ELEM_TO_HTML_CONF[type] + return fn || defaultParser } @@ -35,6 +38,7 @@ function elemToHtml(elemNode: Element, editor: IDomEditor): string { // 计算 children html let childrenHtml = '' + if (!isVoid) { // 非 void node childrenHtml = children.map(child => node2html(child, editor)).join('') @@ -45,8 +49,8 @@ function elemToHtml(elemNode: Element, editor: IDomEditor): string { const res = toHtmlFn(elemNode, childrenHtml, editor) let elemHtml = '' - if (typeof res === 'string') elemHtml = res - else elemHtml = res.html || '' + + if (typeof res === 'string') { elemHtml = res } else { elemHtml = res.html || '' } // 添加样式(如 text-align line-height 等) if (!isVoid) { @@ -54,12 +58,13 @@ function elemToHtml(elemNode: Element, editor: IDomEditor): string { } // 直接返回 html 字符串 - if (typeof res === 'string') return elemHtml + if (typeof res === 'string') { return elemHtml } // 解析 prefix suffix (如 list-item) const { prefix = '', suffix = '' } = res - if (prefix) elemHtml = prefix + elemHtml - if (suffix) elemHtml = elemHtml + suffix + + if (prefix) { elemHtml = prefix + elemHtml } + if (suffix) { elemHtml += suffix } return elemHtml } diff --git a/packages/core/src/to-html/index.ts b/packages/core/src/to-html/index.ts index f3cda75f9..42d6f5a66 100644 --- a/packages/core/src/to-html/index.ts +++ b/packages/core/src/to-html/index.ts @@ -3,7 +3,8 @@ * @author wangfupeng */ -import { Element as SlateElement, Descendant } from 'slate' +import { Descendant, Element as SlateElement } from 'slate' + import { IDomEditor } from '../editor/interface' // ------------------------------------ style to html ------------------------------------ diff --git a/packages/core/src/to-html/node2html.ts b/packages/core/src/to-html/node2html.ts index 54ecdcd3a..3da32c88a 100644 --- a/packages/core/src/to-html/node2html.ts +++ b/packages/core/src/to-html/node2html.ts @@ -3,7 +3,8 @@ * @author wangfupeng */ -import { Element, Descendant } from 'slate' +import { Descendant, Element } from 'slate' + import { IDomEditor } from '../editor/interface' import elemToHtml from './elem2html' import textToHtml from './text2html' @@ -12,10 +13,10 @@ function node2html(node: Descendant, editor: IDomEditor): string { if (Element.isElement(node)) { // elem node return elemToHtml(node, editor) - } else { - // text node - return textToHtml(node, editor) } + // text node + return textToHtml(node, editor) + } export default node2html diff --git a/packages/core/src/to-html/text2html.ts b/packages/core/src/to-html/text2html.ts index 40c94fb27..a2f7dc36c 100644 --- a/packages/core/src/to-html/text2html.ts +++ b/packages/core/src/to-html/text2html.ts @@ -4,14 +4,16 @@ */ import { Text } from 'slate' -import { IDomEditor } from '../editor/interface' + import { DomEditor } from '../editor/dom-editor' -import { STYLE_TO_HTML_FN_LIST } from './index' +import { IDomEditor } from '../editor/interface' import { replaceHtmlSpecialSymbols } from '../utils/util' +import { STYLE_TO_HTML_FN_LIST } from './index' function textToHtml(textNode: Text, editor: IDomEditor): string { const { text } = textNode - if (text == null) throw new Error(`Current node is not slate Text ${JSON.stringify(textNode)}`) + + if (text == null) { throw new Error(`Current node is not slate Text ${JSON.stringify(textNode)}`) } let textHtml = text // 替换 html 特殊字符 @@ -21,6 +23,7 @@ function textToHtml(textNode: Text, editor: IDomEditor): string { const parents = DomEditor.getParentsNodes(editor, textNode) const hasPre = parents.some(p => DomEditor.getNodeType(p) === 'pre') // 上级节点中,是否存在 <pre> // 在 <pre> 标签不替换,其他都替换 + if (!hasPre) { textHtml = textHtml.replace(/\r\n|\r|\n/g, '<br>') } @@ -33,6 +36,7 @@ function textToHtml(textNode: Text, editor: IDomEditor): string { // 处理空字符串 if (textHtml === '') { const parentNode = DomEditor.getParentNode(null, textNode) + if (parentNode && parentNode.children.length === 0) { // textNode 是唯一的子节点,则改为 <br> textHtml = '<br>' diff --git a/packages/core/src/upload/createUploader.ts b/packages/core/src/upload/createUploader.ts index 8b03ad585..c51654c13 100644 --- a/packages/core/src/upload/createUploader.ts +++ b/packages/core/src/upload/createUploader.ts @@ -5,8 +5,9 @@ import Uppy from '@uppy/core' import XHRUpload from '@uppy/xhr-upload' -import { IUploadConfig } from './interface' + import { addQueryToUrl } from '../utils/util' +import { IUploadConfig } from './interface' function createUploader(config: IUploadConfig): Uppy { // 获取配置 @@ -42,6 +43,7 @@ function createUploader(config: IUploadConfig): Uppy { // 是否要追加 url 参数 let url = server + if (metaWithUrl) { url = addQueryToUrl(url, meta) } @@ -67,6 +69,7 @@ function createUploader(config: IUploadConfig): Uppy { // 各个 callback uppy.on('upload-success', (file, response) => { const { body = {} } = response + try { // 有用户传入的第三方代码,得用 try catch 包裹 onSuccess(file, body) @@ -78,7 +81,7 @@ function createUploader(config: IUploadConfig): Uppy { uppy.on('progress', progress => { // progress 值范围: 0 - 100 - if (progress < 1) return + if (progress < 1) { return } onProgress(progress) }) diff --git a/packages/core/src/utils/dom.ts b/packages/core/src/utils/dom.ts index 8c6284d5d..6adce8929 100644 --- a/packages/core/src/utils/dom.ts +++ b/packages/core/src/utils/dom.ts @@ -3,73 +3,87 @@ * @author wangfupeng */ -import { htmlVoidElements } from 'html-void-elements' import $, { - css, - append, addClass, - removeClass, - hasClass, - on, - focus, + append, attr, + children, + css, + dataset, + Dom7Array, + each, + empty, + find, + focus, + hasClass, + height, hide, - show, + html, + is, // scrollTop, // scrollLeft, offset, - width, - height, + on, parent, parents, - is, - dataset, - val, - text, - removeAttr, - children, - html, remove, - find, - each, - empty, - Dom7Array, + removeAttr, + removeClass, + show, + text, + val, + width, } from 'dom7' +import { htmlVoidElements } from 'html-void-elements' + +import { toString } from './util' + +// ------------------------------- 分割线,以下内容参考 slate-react dom.ts ------------------------------- + +// COMPAT: This is required to prevent TypeScript aliases from doing some very +// weird things for Slate's types with the same name as globals. (2019/11/27) +// https://github.com/microsoft/TypeScript/issues/35002 +import DOMNode = globalThis.Node +import DOMComment = globalThis.Comment +import DOMElement = globalThis.Element +import DOMText = globalThis.Text +import DOMRange = globalThis.Range +import DOMSelection = globalThis.Selection +import DOMStaticRange = globalThis.StaticRange + export { Dom7Array } from 'dom7' -if (css) $.fn.css = css -if (append) $.fn.append = append -if (addClass) $.fn.addClass = addClass -if (removeClass) $.fn.removeClass = removeClass -if (hasClass) $.fn.hasClass = hasClass -if (on) $.fn.on = on -if (focus) $.fn.focus = focus -if (attr) $.fn.attr = attr -if (removeAttr) $.fn.removeAttr = removeAttr -if (hide) $.fn.hide = hide -if (show) $.fn.show = show +if (css) { $.fn.css = css } +if (append) { $.fn.append = append } +if (addClass) { $.fn.addClass = addClass } +if (removeClass) { $.fn.removeClass = removeClass } +if (hasClass) { $.fn.hasClass = hasClass } +if (on) { $.fn.on = on } +if (focus) { $.fn.focus = focus } +if (attr) { $.fn.attr = attr } +if (removeAttr) { $.fn.removeAttr = removeAttr } +if (hide) { $.fn.hide = hide } +if (show) { $.fn.show = show } // if (scrollTop) $.fn.scrollTop = scrollTop // if (scrollLeft) $.fn.scrollLeft = scrollLeft -if (offset) $.fn.offset = offset -if (width) $.fn.width = width -if (height) $.fn.height = height -if (parent) $.fn.parent = parent -if (parents) $.fn.parents = parents -if (is) $.fn.is = is -if (dataset) $.fn.dataset = dataset -if (val) $.fn.val = val -if (text) $.fn.text = text -if (html) $.fn.html = html -if (children) $.fn.children = children -if (remove) $.fn.remove = remove -if (find) $.fn.find = find -if (each) $.fn.each = each -if (empty) $.fn.empty = empty +if (offset) { $.fn.offset = offset } +if (width) { $.fn.width = width } +if (height) { $.fn.height = height } +if (parent) { $.fn.parent = parent } +if (parents) { $.fn.parents = parents } +if (is) { $.fn.is = is } +if (dataset) { $.fn.dataset = dataset } +if (val) { $.fn.val = val } +if (text) { $.fn.text = text } +if (html) { $.fn.html = html } +if (children) { $.fn.children = children } +if (remove) { $.fn.remove = remove } +if (find) { $.fn.find = find } +if (each) { $.fn.each = each } +if (empty) { $.fn.empty = empty } export default $ -import { toString } from './util' - export const isDocument = (value: any): value is Document => { return toString(value) === '[object HTMLDocument]' } @@ -83,23 +97,13 @@ export const isDataTransfer = (value: any): value is DataTransfer => { } const HTML_ELEMENT_STR_REG_EXP = /\[object HTML([A-Z][a-z]*)*Element\]/ + export const isHTMLElememt = (value: any): value is HTMLElement => { return HTML_ELEMENT_STR_REG_EXP.test(toString(value)) } - -// ------------------------------- 分割线,以下内容参考 slate-react dom.ts ------------------------------- - -// COMPAT: This is required to prevent TypeScript aliases from doing some very -// weird things for Slate's types with the same name as globals. (2019/11/27) -// https://github.com/microsoft/TypeScript/issues/35002 -import DOMNode = globalThis.Node -import DOMComment = globalThis.Comment -import DOMElement = globalThis.Element -import DOMText = globalThis.Text -import DOMRange = globalThis.Range -import DOMSelection = globalThis.Selection -import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } +export { + DOMComment, DOMElement, DOMNode, DOMRange, DOMSelection, DOMStaticRange, DOMText, +} export type DOMPoint = [Node, number] @@ -150,9 +154,9 @@ export const isDOMText = (value: any): value is DOMText => { */ export const isPlainTextOnlyPaste = (event: ClipboardEvent) => { return ( - event.clipboardData && - event.clipboardData.getData('text/plain') !== '' && - event.clipboardData.types.length === 1 + event.clipboardData + && event.clipboardData.getData('text/plain') !== '' + && event.clipboardData.types.length === 1 ) } @@ -166,8 +170,9 @@ export const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => { // including comment nodes, so try to find the right text child node. if (isDOMElement(node) && node.childNodes.length) { let isLast = offset === node.childNodes.length - let index = isLast ? offset - 1 : offset - ;[node, index] = getEditableChildAndIndex(node, index, isLast ? 'backward' : 'forward') + let index = isLast ? offset - 1 : offset; + + [node, index] = getEditableChildAndIndex(node, index, isLast ? 'backward' : 'forward') // If the editable child found is in front of input offset, we instead seek to its end // 如果编辑区域的内容被发现在输入光标位置前面,也就是光标位置不正常,则修正光标的位置到结尾 @@ -177,6 +182,7 @@ export const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => { // can be either text nodes, or other void DOM nodes. while (isDOMElement(node) && node.childNodes.length) { const i = isLast ? node.childNodes.length - 1 : 0 + node = getEditableChild(node, i, isLast ? 'backward' : 'forward') } @@ -200,8 +206,8 @@ export const hasShadowRoot = () => { */ export const getElementById = (id: string): null | HTMLElement => { return ( - window.document.getElementById(id) ?? - (window.document.activeElement?.shadowRoot?.getElementById(id) || null) + window.document.getElementById(id) + ?? (window.document.activeElement?.shadowRoot?.getElementById(id) || null) ) } @@ -211,7 +217,7 @@ export const getElementById = (id: string): null | HTMLElement => { export const getEditableChildAndIndex = ( parent: DOMElement, index: number, - direction: 'forward' | 'backward' + direction: 'forward' | 'backward', ): [DOMNode, number] => { const { childNodes } = parent let child = childNodes[index] @@ -222,9 +228,9 @@ export const getEditableChildAndIndex = ( // While the child is a comment node, or an element node with no children, // keep iterating to find a sibling non-void, non-comment node. while ( - isDOMComment(child) || - (isDOMElement(child) && child.childNodes.length === 0) || - (isDOMElement(child) && child.getAttribute('contenteditable') === 'false') + isDOMComment(child) + || (isDOMElement(child) && child.childNodes.length === 0) + || (isDOMElement(child) && child.getAttribute('contenteditable') === 'false') ) { if (triedForward && triedBackward) { break @@ -260,9 +266,10 @@ export const getEditableChildAndIndex = ( export const getEditableChild = ( parent: DOMElement, index: number, - direction: 'forward' | 'backward' + direction: 'forward' | 'backward', ): DOMNode => { const [child] = getEditableChildAndIndex(parent, index, direction) + return child } @@ -287,10 +294,10 @@ export const getPlainText = (domNode: DOMNode) => { const display = getComputedStyle(domNode).getPropertyValue('display') if ( - display === 'block' || - display === 'list' || - display === 'table-row' || - domNode.tagName === 'BR' + display === 'block' + || display === 'list' + || display === 'table-row' + || domNode.tagName === 'BR' ) { text += '\n' } @@ -306,6 +313,7 @@ export const getPlainText = (domNode: DOMNode) => { export function getFirstVoidChild(elem: DOMElement): DOMElement | null { // 深度优先遍历 const stack: Array<DOMElement> = [] + stack.push(elem) let num = 0 @@ -313,19 +321,22 @@ export function getFirstVoidChild(elem: DOMElement): DOMElement | null { // 开始遍历 while (stack.length > 0) { const curElem = stack.pop() - if (curElem == null) break + + if (curElem == null) { break } num++ - if (num > 10000) break + if (num > 10000) { break } const { nodeName, nodeType } = curElem + if (nodeType === 1) { const name = nodeName.toLowerCase() + if ( - htmlVoidElements.includes(name) || + htmlVoidElements.includes(name) // 补充一些 - name === 'iframe' || - name === 'video' + || name === 'iframe' + || name === 'video' ) { return curElem // 得到 void elem 并返回 } @@ -333,6 +344,7 @@ export function getFirstVoidChild(elem: DOMElement): DOMElement | null { // 继续遍历子节点 const children = curElem.children || [] const length = children.length + if (length) { for (let i = length - 1; i >= 0; i--) { // 注意,需要**逆序**追加自节点 @@ -353,14 +365,15 @@ export function getFirstVoidChild(elem: DOMElement): DOMElement | null { */ export function walkTextNodes( elem: DOMElement, - handler: (textNode: DOMNode, parent: DOMElement) => void + handler: (textNode: DOMNode, parent: DOMElement) => void, ) { // void elem 内部的 text 不处理 - if (isHTMLElememt(elem) && elem.dataset.slateVoid === 'true') return + if (isHTMLElememt(elem) && elem.dataset.slateVoid === 'true') { return } - for (let nodes = elem.childNodes, i = nodes.length; i--; ) { + for (let nodes = elem.childNodes, i = nodes.length; i--;) { const node = nodes[i] const nodeType = node.nodeType + if (nodeType == 3) { // 匹配到 text node ,执行函数 handler(node, elem) @@ -387,8 +400,9 @@ export enum NodeType { * @param $elem $elem */ export function getTagName($elem: Dom7Array): string { - if ($elem.length === 0) return '' + if ($elem.length === 0) { return '' } const elem = $elem[0] - if (elem.nodeType !== NodeType.ELEMENT_NODE) return '' + + if (elem.nodeType !== NodeType.ELEMENT_NODE) { return '' } return elem.tagName.toLowerCase() } diff --git a/packages/core/src/utils/hotkeys.ts b/packages/core/src/utils/hotkeys.ts index bffffe96a..f06c92dd7 100644 --- a/packages/core/src/utils/hotkeys.ts +++ b/packages/core/src/utils/hotkeys.ts @@ -4,6 +4,7 @@ */ import { isKeyHotkey } from 'is-hotkey' + import { IS_APPLE } from './ua' interface KEYS { @@ -66,9 +67,9 @@ const create = (key: string) => { const isWindows = windows && isKeyHotkey(windows) return (event: KeyboardEvent) => { - if (isGeneric && isGeneric(event)) return true - if (IS_APPLE && isApple && isApple(event)) return true - if (!IS_APPLE && isWindows && isWindows(event)) return true + if (isGeneric && isGeneric(event)) { return true } + if (IS_APPLE && isApple && isApple(event)) { return true } + if (!IS_APPLE && isWindows && isWindows(event)) { return true } return false } } diff --git a/packages/core/src/utils/line.ts b/packages/core/src/utils/line.ts index d6c404bc9..c5211281d 100644 --- a/packages/core/src/utils/line.ts +++ b/packages/core/src/utils/line.ts @@ -2,18 +2,21 @@ * @description Utilities for single-line deletion */ -import { Range, Editor } from 'slate' -import { IDomEditor } from '../editor/interface' +import { Editor, Range } from 'slate' + import { DomEditor } from '../editor/dom-editor' +import { IDomEditor } from '../editor/interface' const doRectsIntersect = (rect: DOMRect, compareRect: DOMRect) => { const middle = (compareRect.top + compareRect.bottom) / 2 + return rect.top <= middle && rect.bottom >= middle } const areRangesSameLine = (editor: IDomEditor, range1: Range, range2: Range) => { const rect1 = DomEditor.toDOMRange(editor, range1).getBoundingClientRect() const rect2 = DomEditor.toDOMRange(editor, range2).getBoundingClientRect() + return doRectsIntersect(rect1, rect2) && doRectsIntersect(rect2, rect1) } diff --git a/packages/core/src/utils/ua.ts b/packages/core/src/utils/ua.ts index 03ac2fddf..3b0835a4a 100644 --- a/packages/core/src/utils/ua.ts +++ b/packages/core/src/utils/ua.ts @@ -3,48 +3,40 @@ * @author wangfupeng */ -export const IS_IOS = - typeof globalThis.navigator !== 'undefined' && - typeof globalThis.window !== 'undefined' && - /iPad|iPhone|iPod/.test(navigator.userAgent) && - !globalThis.window.MSStream +export const IS_IOS = typeof globalThis.navigator !== 'undefined' + && typeof globalThis.window !== 'undefined' + && /iPad|iPhone|iPod/.test(navigator.userAgent) + && !globalThis.window.MSStream export const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent) -export const IS_FIREFOX = - typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent) +export const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent) -export const IS_FIREFOX_LEGACY = - typeof navigator !== 'undefined' && - /^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(navigator.userAgent) +export const IS_FIREFOX_LEGACY = typeof navigator !== 'undefined' + && /^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(navigator.userAgent) -export const IS_SAFARI = - typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent) // eslint-disable-line +export const IS_SAFARI = typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent) // eslint-disable-line // "modern" Edge was released at 79.x -export const IS_EDGE_LEGACY = - typeof navigator !== 'undefined' && - /Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent) +export const IS_EDGE_LEGACY = typeof navigator !== 'undefined' + && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent) // Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput -export const IS_CHROME_LEGACY = - typeof navigator !== 'undefined' && - /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent) +export const IS_CHROME_LEGACY = typeof navigator !== 'undefined' + && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent) export const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent) // qq browser -export const IS_QQBROWSER = - typeof navigator !== 'undefined' && /.*QQBrowser/.test(navigator.userAgent) +export const IS_QQBROWSER = typeof navigator !== 'undefined' && /.*QQBrowser/.test(navigator.userAgent) // @ts-ignore 判断浏览器是否支持 beforeinput 事件 https://www.caniuse.com/?search=beforeinput // COMPAT: Firefox/Edge Legacy don't support the `beforeinput` event // Chrome Legacy doesn't support `beforeinput` correctly -export const HAS_BEFORE_INPUT_SUPPORT = - !IS_CHROME_LEGACY && - !IS_EDGE_LEGACY && +export const HAS_BEFORE_INPUT_SUPPORT = !IS_CHROME_LEGACY + && !IS_EDGE_LEGACY // globalThis is undefined in older browsers - typeof globalThis !== 'undefined' && - globalThis.InputEvent && + && typeof globalThis !== 'undefined' + && globalThis.InputEvent // @ts-ignore The `getTargetRanges` property isn't recognized. - typeof globalThis.InputEvent.prototype.getTargetRanges === 'function' + && typeof globalThis.InputEvent.prototype.getTargetRanges === 'function' diff --git a/packages/core/src/utils/util.ts b/packages/core/src/utils/util.ts index 4c520f680..b561b8456 100644 --- a/packages/core/src/utils/util.ts +++ b/packages/core/src/utils/util.ts @@ -13,7 +13,7 @@ type PromiseCallback = (value: void) => void | PromiseLike<void> * @param prefix 前缀 * @returns 随机数字符串 */ -export function genRandomStr(prefix: string = 'r'): string { +export function genRandomStr(prefix = 'r'): string { return `${prefix}-${nanoid()}` } @@ -31,6 +31,7 @@ export function addQueryToUrl(url: string, data: object): string { // 拼接 query string const queryArr: string[] = [] + forEach(data, (val, key) => { queryArr.push(`${key}=${val}`) }) @@ -48,9 +49,9 @@ export function addQueryToUrl(url: string, data: object): string { // 返回拼接好的 url if (hash) { return `${urlWithoutHash}#${hash}` - } else { - return urlWithoutHash } + return urlWithoutHash + } /** diff --git a/packages/core/src/utils/vdom.ts b/packages/core/src/utils/vdom.ts index c3bb8c56f..03529a1ed 100644 --- a/packages/core/src/utils/vdom.ts +++ b/packages/core/src/utils/vdom.ts @@ -5,17 +5,17 @@ import camelCase from 'lodash.camelcase' import { - VNode, - init, + attributesModule, classModule, + Dataset, + datasetModule, + eventListenersModule, + init, + Props, propsModule, styleModule, - datasetModule, + VNode, VNodeStyle, - Props, - Dataset, - eventListenersModule, - attributesModule, } from 'snabbdom' export type PatchFn = (oldVnode: VNode | Element, vnode: VNode) => VNode @@ -34,6 +34,7 @@ export function genPatchFn(): PatchFn { eventListenersModule, // attaches event listeners attributesModule, ]) + return patch } @@ -47,6 +48,7 @@ const DATA_PRESERVE_KEYS = ['props', 'attrs', 'style', 'dataset', 'on', 'hook'] export function normalizeVnodeData(vnode: VNode) { const { data = {}, children = [] } = vnode const dataKeys = Object.keys(data) + dataKeys.forEach((key: string) => { const value = data[key] @@ -57,11 +59,12 @@ export function normalizeVnodeData(vnode: VNode) { } // 忽略 data 保留属性 - if (DATA_PRESERVE_KEYS.includes(key)) return + if (DATA_PRESERVE_KEYS.includes(key)) { return } // dataset if (key.startsWith('data-')) { let datasetKey = key.slice(5) // 截取掉最前面的 'data-' + datasetKey = camelCase(datasetKey) // 转为驼峰写法 // 存储到 data.dataset @@ -80,7 +83,7 @@ export function normalizeVnodeData(vnode: VNode) { // 遍历 children if (children.length > 0) { children.forEach(child => { - if (typeof child === 'string') return + if (typeof child === 'string') { return } normalizeVnodeData(child) }) } @@ -92,9 +95,10 @@ export function normalizeVnodeData(vnode: VNode) { * @param newProp { key: val } */ export function addVnodeProp(vnode: VNode, newProp: Props) { - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } const data = vnode.data - if (data.props == null) data.props = {} + + if (data.props == null) { data.props = {} } Object.assign(data.props, newProp) } @@ -105,9 +109,10 @@ export function addVnodeProp(vnode: VNode, newProp: Props) { * @param newDataset { key: val } */ export function addVnodeDataset(vnode: VNode, newDataset: Dataset) { - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } const data = vnode.data - if (data.dataset == null) data.dataset = {} + + if (data.dataset == null) { data.dataset = {} } Object.assign(data.dataset, newDataset) } @@ -118,9 +123,10 @@ export function addVnodeDataset(vnode: VNode, newDataset: Dataset) { * @param newStyle { key: val } */ export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) { - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } const data = vnode.data - if (data.style == null) data.style = {} + + if (data.style == null) { data.style = {} } Object.assign(data.style, newStyle) } diff --git a/packages/core/src/utils/weak-maps.ts b/packages/core/src/utils/weak-maps.ts index dfdb49013..8bce0e90a 100644 --- a/packages/core/src/utils/weak-maps.ts +++ b/packages/core/src/utils/weak-maps.ts @@ -4,17 +4,20 @@ */ import { Emitter } from 'event-emitter' +import { + Ancestor, Editor, Node, Path, Range, +} from 'slate' import { VNode } from 'snabbdom' -import { Node, Ancestor, Editor, Path, Range } from 'slate' + +import { IEditorConfig } from '../config/interface' import { IDomEditor } from '../editor/interface' -import TextArea from '../text-area/TextArea' -import Toolbar from '../menus/bar/Toolbar' import HoverBar from '../menus/bar/HoverBar' +import Toolbar from '../menus/bar/Toolbar' import { IBarItem } from '../menus/bar-item/index' -import { Key } from './key' -import { PatchFn } from '../utils/vdom' -import { IEditorConfig } from '../config/interface' import PanelAndModal from '../menus/panel-and-modal/BaseClass' +import TextArea from '../text-area/TextArea' +import { PatchFn } from '../utils/vdom' +import { Key } from './key' // textarea - editor export const EDITOR_TO_TEXTAREA = new WeakMap<IDomEditor, TextArea>() diff --git a/packages/custom-types.d.ts b/packages/custom-types.d.ts index 88ccf6ab6..f690ed86d 100644 --- a/packages/custom-types.d.ts +++ b/packages/custom-types.d.ts @@ -2,15 +2,11 @@ * @description 自定义扩展 slate 接口属性 * @author wangfupeng */ -import { StyledText } from './basic-modules/src/modules/text-style/custom-types' +import { BlockQuoteElement } from './basic-modules/src/modules/blockquote/custom-types' +import { CodeElement, PreElement } from './basic-modules/src/modules/code-block/custom-types' import { ColorText } from './basic-modules/src/modules/color/custom-types' +import { DividerElement } from './basic-modules/src/modules/divider/custom-types' import { FontSizeAndFamilyText } from './basic-modules/src/modules/font-size-family/custom-types' -import { LineHeightElement } from './basic-modules/src/modules/line-height/custom-types' -import { JustifyElement } from './basic-modules/src/modules/justify/custom-types' -import { IndentElement } from './basic-modules/src/modules/indent/custom-types' -import { ParagraphElement } from './basic-modules/src/modules/paragraph/custom-types' -import { LinkElement } from './basic-modules/src/modules/link/custom-types' -import { BlockQuoteElement } from './basic-modules/src/modules/blockquote/custom-types' import { Header1Element, Header2Element, @@ -18,17 +14,21 @@ import { Header4Element, Header5Element, } from './basic-modules/src/modules/header/custom-types' -import { DividerElement } from './basic-modules/src/modules/divider/custom-types' import { ImageElement } from './basic-modules/src/modules/image/custom-types' +import { IndentElement } from './basic-modules/src/modules/indent/custom-types' +import { JustifyElement } from './basic-modules/src/modules/justify/custom-types' +import { LineHeightElement } from './basic-modules/src/modules/line-height/custom-types' +import { LinkElement } from './basic-modules/src/modules/link/custom-types' +import { ParagraphElement } from './basic-modules/src/modules/paragraph/custom-types' +import { StyledText } from './basic-modules/src/modules/text-style/custom-types' import { TodoElement } from './basic-modules/src/modules/todo/custom-types' -import { PreElement, CodeElement } from './basic-modules/src/modules/code-block/custom-types' -import { VideoElement } from './video-module/src/module/custom-types' +import { ListItemElement } from './list-module/src/module/custom-types' import { TableCellElement, - TableRowElement, TableElement, + TableRowElement, } from './table-module/src/module/custom-types' -import { ListItemElement } from './list-module/src/module/custom-types' +import { VideoElement } from './video-module/src/module/custom-types' type PureText = { text: string @@ -65,6 +65,30 @@ type CustomElement = | TableElement | ListItemElement +declare global { + interface Window { + MSStream: boolean + } + + interface DocumentOrShadowRoot { + getSelection(): Selection | null + } + + interface CaretPosition { + readonly offsetNode: Node + readonly offset: number + getClientRect(): DOMRect | null + } + + interface Document { + caretPositionFromPoint(x: number, y: number): CaretPosition | null + } + + interface Node { + getRootNode(options?: GetRootNodeOptions): Document | ShadowRoot + } +} + declare module 'slate' { interface CustomTypes { // 扩展 Text diff --git a/packages/editor/__tests__/create.test.ts b/packages/editor/__tests__/create.test.ts index 411bf2335..3220c5f52 100644 --- a/packages/editor/__tests__/create.test.ts +++ b/packages/editor/__tests__/create.test.ts @@ -3,13 +3,14 @@ * @author wangfupeng */ -import { createEditor, createToolbar } from '../../../packages/editor/src/index' +import Boot from '../../../packages/editor/src/Boot' import { ICreateEditorOption, ICreateToolbarOption } from '../../../packages/editor/src/create' +import { createEditor, createToolbar } from '../../../packages/editor/src/index' import { DOMElement } from '../../../packages/editor/src/utils/dom' -import Boot from '../../../packages/editor/src/Boot' function customCreateEditor(config: Partial<ICreateEditorOption> = {}) { const editorContainer = document.createElement('div') + document.body.appendChild(editorContainer) // create editor @@ -23,6 +24,7 @@ function customCreateEditor(config: Partial<ICreateEditorOption> = {}) { function customCreateToolbar(config: Partial<ICreateToolbarOption> = {}) { const toolbarContainer = document.createElement('div') + document.body.appendChild(toolbarContainer) // create editor @@ -41,12 +43,13 @@ function customCreateToolbar(config: Partial<ICreateToolbarOption> = {}) { describe('create editor and toolbar', () => { test('create editor selector undefind', () => { const editor = customCreateEditor() + expect(() => { createToolbar({ editor, selector: undefined as any, }) - }).toThrow(`Cannot find 'selector' when create toolbar`) + }).toThrow('Cannot find \'selector\' when create toolbar') }) test('test new Boot and registerModule', () => { @@ -72,6 +75,7 @@ describe('create editor and toolbar', () => { const editor = customCreateEditor({ mode: 'simple', }) + expect(editor.id).not.toBeNull() }) @@ -86,6 +90,7 @@ describe('create editor and toolbar', () => { test('create editor can not be called twice with same container', () => { const editorContainer = document.createElement('div') + document.body.appendChild(editorContainer) // create editor customCreateEditor({ @@ -103,6 +108,7 @@ describe('create editor and toolbar', () => { test('create toolbar with default mode', () => { const toolbar = customCreateToolbar() + expect(toolbar.$box).not.toBeNull() }) @@ -110,6 +116,7 @@ describe('create editor and toolbar', () => { const toolbar = customCreateToolbar({ mode: 'simple', }) + expect(toolbar.$box).not.toBeNull() }) @@ -118,13 +125,15 @@ describe('create editor and toolbar', () => { mode: 'simple', }) const defaultToolbar = customCreateToolbar() + expect(simpleToolbar.getConfig().toolbarKeys).not.toEqual( - defaultToolbar.getConfig().toolbarKeys + defaultToolbar.getConfig().toolbarKeys, ) }) test('create toolbar can not be called twice with same container', () => { const toolbarContainer = document.createElement('div') + document.body.appendChild(toolbarContainer) customCreateToolbar({ @@ -145,6 +154,7 @@ describe('create editor and toolbar', () => { </p><p><br></p>` const editor = customCreateEditor({ html }) + expect(editor.children).toEqual([ { type: 'header1', children: [{ text: 'header' }] }, { diff --git a/packages/editor/demo/js/custom-elem.js b/packages/editor/demo/js/custom-elem.js index b5386f9d7..149257a98 100644 --- a/packages/editor/demo/js/custom-elem.js +++ b/packages/editor/demo/js/custom-elem.js @@ -22,15 +22,15 @@ * this.constructor so that the native HTMLElement constructor can access the * current under-construction element's definition. */ -;(function () { +(function () { if ( // No Reflect, no classes, no need for shim because native custom elements // require ES2015 classes or Reflect. - window.Reflect === undefined || - window.customElements === undefined || + window.Reflect === undefined + || window.customElements === undefined // The webcomponentsjs custom elements polyfill doesn't require // ES2015-compatible construction (`super()` or `Reflect.construct`). - window.customElements.polyfillWrapFlushCallback + || window.customElements.polyfillWrapFlushCallback ) { return } @@ -46,11 +46,12 @@ return Reflect.construct(BuiltInHTMLElement, [], /** @type {!Function} */ this.constructor) }, } - window.HTMLElement = wrapperForTheName['HTMLElement'] + + window.HTMLElement = wrapperForTheName.HTMLElement HTMLElement.prototype = BuiltInHTMLElement.prototype HTMLElement.prototype.constructor = HTMLElement Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement) -})() +}()) // ------------------------------------------ native-shim end ------------------------------------------ // ------------------------------------------ 顶部导航 start ------------------------------------------ @@ -67,6 +68,7 @@ const document = shadow.ownerDocument const style = document.createElement('style') + style.innerHTML = ` .container { display: flex; @@ -93,15 +95,18 @@ // 容器 const container = document.createElement('div') + container.className = 'container' // 标题 const header = document.createElement('h1') + header.textContent = '' this.header = header // 右侧链接 const rightContainer = document.createElement('div') + rightContainer.className = 'right-container' if (LANG === 'en') { rightContainer.innerHTML = ` @@ -125,14 +130,14 @@ attributeChangedCallback(name, oldValue, newValue) { if (name === 'title') { - if (oldValue == newValue) return + if (oldValue == newValue) { return } this.header.textContent = newValue } } } MyNav.observedAttributes = ['title'] window.customElements.define('demo-nav', MyNav) -})() +}()) // ------------------------------------------ 顶部导航 end ------------------------------------------ // ------------------------------------------ 左侧菜单 start ------------------------------------------ @@ -264,6 +269,7 @@ const MENU_CONF = [ const document = shadow.ownerDocument const style = document.createElement('style') + style.innerHTML = ` ul { list-style-type: none; @@ -285,16 +291,18 @@ const MENU_CONF = [ shadow.appendChild(style) const container = document.createElement('div') + container.innerHTML = `<ul> ${MENU_CONF.map(item => { - const { link, text } = item[LANG] - return `<li><a href="${link}">${text}</a></li>` - }).join('')} + const { link, text } = item[LANG] + + return `<li><a href="${link}">${text}</a></li>` + }).join('')} </ul>` shadow.appendChild(container) } } window.customElements.define('demo-menu', MyMenu) -})() +}()) // ------------------------------------------ 左侧菜单 end ------------------------------------------ diff --git a/packages/editor/demo/js/huge-content.js b/packages/editor/demo/js/huge-content.js index 54c25608e..78ef5f895 100644 --- a/packages/editor/demo/js/huge-content.js +++ b/packages/editor/demo/js/huge-content.js @@ -1,6 +1,7 @@ -;(function () { +(function () { function deepClone(obj) { const str = JSON.stringify(obj) + return JSON.parse(str) } @@ -12,10 +13,8 @@ }, ], } - const text1 = - '全书通过描写梁山好汉反抗欺压、水泊梁山壮大和受宋朝招安,以及受招安后为宋朝征战,最终消亡的宏大故事,艺术地反映了中国历史上宋江起义从发生、发展直至失败的全过程,深刻揭示了起义的社会根源,满腔热情地歌颂了起义英雄的反抗斗争和他们的社会理想,也具体揭示了起义失败的内在历史原因。' - const text2 = - '《水浒传》是中国古典四大名著之一,问世后,在社会上产生了巨大的影响,成了后世中国小说创作的典范。《水浒传》是中国历史上最早用白话文写成的章回小说之一,流传极广,脍炙人口;同时也是汉语言文学中具备史诗特征的作品之一,对中国乃至东亚的叙事文学都有深远的影响。' + const text1 = '全书通过描写梁山好汉反抗欺压、水泊梁山壮大和受宋朝招安,以及受招安后为宋朝征战,最终消亡的宏大故事,艺术地反映了中国历史上宋江起义从发生、发展直至失败的全过程,深刻揭示了起义的社会根源,满腔热情地歌颂了起义英雄的反抗斗争和他们的社会理想,也具体揭示了起义失败的内在历史原因。' + const text2 = '《水浒传》是中国古典四大名著之一,问世后,在社会上产生了巨大的影响,成了后世中国小说创作的典范。《水浒传》是中国历史上最早用白话文写成的章回小说之一,流传极广,脍炙人口;同时也是汉语言文学中具备史诗特征的作品之一,对中国乃至东亚的叙事文学都有深远的影响。' const p1 = { type: 'paragraph', children: [{ text: text1 }], @@ -43,4 +42,4 @@ window.HUGE_CONTENT.push(deepClone(p2)) // window.HUGE_CONTENT.push(deepClone(code)) } -})() +}()) diff --git a/packages/editor/examples/js/huge-content.js b/packages/editor/examples/js/huge-content.js index 79f4d4397..f9c9b947d 100644 --- a/packages/editor/examples/js/huge-content.js +++ b/packages/editor/examples/js/huge-content.js @@ -1,6 +1,7 @@ -;(function () { +(function () { function deepClone(obj) { const str = JSON.stringify(obj) + return JSON.parse(str) } @@ -12,10 +13,8 @@ }, ], } - const text1 = - '全书通过描写梁山好汉反抗欺压、水泊梁山壮大和受宋朝招安,以及受招安后为宋朝征战,最终消亡的宏大故事,艺术地反映了中国历史上宋江起义从发生、发展直至失败的全过程,深刻揭示了起义的社会根源,满腔热情地歌颂了起义英雄的反抗斗争和他们的社会理想,也具体揭示了起义失败的内在历史原因。' - const text2 = - '《水浒传》是中国古典四大名著之一,问世后,在社会上产生了巨大的影响,成了后世中国小说创作的典范。《水浒传》是中国历史上最早用白话文写成的章回小说之一,流传极广,脍炙人口;同时也是汉语言文学中具备史诗特征的作品之一,对中国乃至东亚的叙事文学都有深远的影响。' + const text1 = '全书通过描写梁山好汉反抗欺压、水泊梁山壮大和受宋朝招安,以及受招安后为宋朝征战,最终消亡的宏大故事,艺术地反映了中国历史上宋江起义从发生、发展直至失败的全过程,深刻揭示了起义的社会根源,满腔热情地歌颂了起义英雄的反抗斗争和他们的社会理想,也具体揭示了起义失败的内在历史原因。' + const text2 = '《水浒传》是中国古典四大名著之一,问世后,在社会上产生了巨大的影响,成了后世中国小说创作的典范。《水浒传》是中国历史上最早用白话文写成的章回小说之一,流传极广,脍炙人口;同时也是汉语言文学中具备史诗特征的作品之一,对中国乃至东亚的叙事文学都有深远的影响。' const p1 = { type: 'paragraph', children: [{ text: text1 }], @@ -43,4 +42,4 @@ window.content2.push(deepClone(p2)) // window.content2.push(deepClone(code)) } -})() +}()) diff --git a/packages/editor/rollup.config.js b/packages/editor/rollup.config.js index 91bf6e0db..8f321b845 100644 --- a/packages/editor/rollup.config.js +++ b/packages/editor/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD, IS_DEV } from '../../build/create-rollup-config' +import { createRollupConfig, IS_DEV, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'wangEditor' @@ -13,6 +14,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) // esm @@ -23,6 +25,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) export default configList diff --git a/packages/editor/src/Boot.ts b/packages/editor/src/Boot.ts index ac5599e9c..3c5fe02e9 100644 --- a/packages/editor/src/Boot.ts +++ b/packages/editor/src/Boot.ts @@ -5,37 +5,33 @@ import { IDomEditor, - // 配置 IEditorConfig, - IToolbarConfig, + // to html + IElemToHtmlConf, IModuleConf, - + IParseElemHtmlConf, + IPreParseHtmlConf, // 注册菜单 IRegisterMenuConf, - registerMenu, - // 渲染 modal -> view IRenderElemConf, - RenderStyleFnType, - registerStyleHandler, - registerRenderElemConf, - - // to html - IElemToHtmlConf, - styleToHtmlFnType, - registerStyleToHtmlHandler, - registerElemToHtmlConf, - + IToolbarConfig, + ParseStyleHtmlFnType, // parseHtml PreParseHtmlFnType, - IPreParseHtmlConf, - registerPreParseHtmlConf, - ParseStyleHtmlFnType, - IParseElemHtmlConf, + registerElemToHtmlConf, + registerMenu, registerParseElemHtmlConf, registerParseStyleHtmlHandler, + registerPreParseHtmlConf, + registerRenderElemConf, + registerStyleHandler, + registerStyleToHtmlHandler, + RenderStyleFnType, + styleToHtmlFnType, } from '@wangeditor-next/core' + import registerModule from './register-builtin-modules/register' type PluginType = <T extends IDomEditor>(editor: T) => T @@ -47,13 +43,16 @@ class Boot { // editor 配置 static editorConfig: Partial<IEditorConfig> = {} + static setEditorConfig(newConfig: Partial<IEditorConfig> = {}) { this.editorConfig = { ...this.editorConfig, ...newConfig, } } + static simpleEditorConfig: Partial<IEditorConfig> = {} + static setSimpleEditorConfig(newConfig: Partial<IEditorConfig> = {}) { this.simpleEditorConfig = { ...this.simpleEditorConfig, @@ -61,15 +60,18 @@ class Boot { } } - //toolbar 配置 + // toolbar 配置 static toolbarConfig: Partial<IToolbarConfig> = {} + static setToolbarConfig(newConfig: Partial<IToolbarConfig> = {}) { this.toolbarConfig = { ...this.toolbarConfig, ...newConfig, } } + static simpleToolbarConfig: Partial<IToolbarConfig> = {} + static setSimpleToolbarConfig(newConfig: Partial<IToolbarConfig> = {}) { this.simpleToolbarConfig = { ...this.simpleToolbarConfig, @@ -79,6 +81,7 @@ class Boot { // 注册插件 static plugins: PluginType[] = [] + static registerPlugin(plugin: PluginType) { this.plugins.push(plugin) } diff --git a/packages/editor/src/constants/svg.ts b/packages/editor/src/constants/svg.ts index ecf42ebb8..1564958a7 100644 --- a/packages/editor/src/constants/svg.ts +++ b/packages/editor/src/constants/svg.ts @@ -10,21 +10,16 @@ */ // 缩进 right -export const INDENT_RIGHT_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>' +export const INDENT_RIGHT_SVG = '<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>' // 左对齐 -export const JUSTIFY_LEFT_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' +export const JUSTIFY_LEFT_SVG = '<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' // 图片 -export const IMAGE_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>' +export const IMAGE_SVG = '<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>' // plus -export const MORE_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M505.6 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M806.4 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>' +export const MORE_SVG = '<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M505.6 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M806.4 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>' // 视频 -export const VIDEO_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M981.184 160.096C837.568 139.456 678.848 128 512 128S186.432 139.456 42.816 160.096C15.296 267.808 0 386.848 0 512s15.264 244.16 42.816 351.904C186.464 884.544 345.152 896 512 896s325.568-11.456 469.184-32.096C1008.704 756.192 1024 637.152 1024 512s-15.264-244.16-42.816-351.904zM384 704V320l320 192-320 192z"></path></svg>' +export const VIDEO_SVG = '<svg viewBox="0 0 1024 1024"><path d="M981.184 160.096C837.568 139.456 678.848 128 512 128S186.432 139.456 42.816 160.096C15.296 267.808 0 386.848 0 512s15.264 244.16 42.816 351.904C186.464 884.544 345.152 896 512 896s325.568-11.456 469.184-32.096C1008.704 756.192 1024 637.152 1024 512s-15.264-244.16-42.816-351.904zM384 704V320l320 192-320 192z"></path></svg>' diff --git a/packages/editor/src/create.ts b/packages/editor/src/create.ts index a650f062f..3de415bbd 100644 --- a/packages/editor/src/create.ts +++ b/packages/editor/src/create.ts @@ -3,17 +3,18 @@ * @author wangfupeng */ -import { Descendant } from 'slate' -import Boot from './Boot' -import { DOMElement } from './utils/dom' import { - IEditorConfig, - IDomEditor, - IToolbarConfig, coreCreateEditor, coreCreateToolbar, + IDomEditor, + IEditorConfig, + IToolbarConfig, Toolbar, } from '@wangeditor-next/core' +import { Descendant } from 'slate' + +import Boot from './Boot' +import { DOMElement } from './utils/dom' export interface ICreateEditorOption { selector: string | DOMElement @@ -34,9 +35,11 @@ export interface ICreateToolbarOption { * 创建 editor 实例 */ export function createEditor(option: Partial<ICreateEditorOption> = {}): IDomEditor { - const { selector = '', content = [], html, config = {}, mode = 'default' } = option + const { + selector = '', content = [], html, config = {}, mode = 'default', + } = option - let globalConfig = mode === 'simple' ? Boot.simpleEditorConfig : Boot.editorConfig + const globalConfig = mode === 'simple' ? Boot.simpleEditorConfig : Boot.editorConfig // 单独处理 hoverbarKeys const newHoverbarKeys = { @@ -63,12 +66,15 @@ export function createEditor(option: Partial<ICreateEditorOption> = {}): IDomEdi * 创建 toolbar 实例 */ export function createToolbar(option: ICreateToolbarOption): Toolbar { - const { selector, editor, config = {}, mode = 'default' } = option + const { + selector, editor, config = {}, mode = 'default', + } = option + if (!selector) { - throw new Error(`Cannot find 'selector' when create toolbar`) + throw new Error('Cannot find \'selector\' when create toolbar') } - let globalConfig = mode === 'simple' ? Boot.simpleToolbarConfig : Boot.toolbarConfig + const globalConfig = mode === 'simple' ? Boot.simpleToolbarConfig : Boot.toolbarConfig const toolbar = coreCreateToolbar(editor, { selector, diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 82c97be1d..5b08fb0b2 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -5,14 +5,11 @@ import './assets/index.less' import '@wangeditor-next/core/dist/css/style.css' - // 兼容性(要放在最开始就执行) import './utils/browser-polyfill' import './utils/node-polyfill' - // 配置多语言 import './locale/index' - // 注册内置模块 import './register-builtin-modules/index' // 初始化默认配置 @@ -20,47 +17,48 @@ import './init-default-config' // 全局注册 import Boot from './Boot' + export { Boot } // 导出 core API 和接口(注意,此处按需导出,不可直接用 `*` ) export { + // 第三方模块 - 上传时用到 + createUploader, DomEditor, + genModalButtonElems, + genModalInputElems, + // 第三方模块 - modal 中用到的 API + genModalTextareaElems, + i18nAddResources, + // 第三方模块 - 多语言 + i18nChangeLanguage, + i18nGetResources, + IButtonMenu, IDomEditor, + IDropPanelMenu, IEditorConfig, - IToolbarConfig, - Toolbar, + IModalMenu, // 第三方模块 - 接口 IModuleConf, - IButtonMenu, ISelectMenu, - IDropPanelMenu, - IModalMenu, - // 第三方模块 - 多语言 - i18nChangeLanguage, - i18nAddResources, - i18nGetResources, - t, - // 第三方模块 - modal 中用到的 API - genModalTextareaElems, - genModalInputElems, - genModalButtonElems, - // 第三方模块 - 上传时用到 - createUploader, + IToolbarConfig, IUploadConfig, + t, + Toolbar, } from '@wangeditor-next/core' // 导出 slate API 和接口 (需重命名,加 `Slate` 前缀) export { - Transforms as SlateTransforms, Descendant as SlateDescendant, Editor as SlateEditor, - Node as SlateNode, Element as SlateElement, - Text as SlateText, - Path as SlatePath, - Range as SlateRange, Location as SlateLocation, + Node as SlateNode, + Path as SlatePath, Point as SlatePoint, + Range as SlateRange, + Text as SlateText, + Transforms as SlateTransforms, } from 'slate' // 导出 create 函数 diff --git a/packages/editor/src/init-default-config/config/index.ts b/packages/editor/src/init-default-config/config/index.ts index d3dd5a626..e674b92ad 100644 --- a/packages/editor/src/init-default-config/config/index.ts +++ b/packages/editor/src/init-default-config/config/index.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import { genDefaultToolbarKeys, genSimpleToolbarKeys } from './toolbar' import { genDefaultHoverbarKeys, genSimpleHoverbarKeys } from './hoverbar' +import { genDefaultToolbarKeys, genSimpleToolbarKeys } from './toolbar' export function getDefaultEditorConfig() { return { diff --git a/packages/editor/src/init-default-config/config/toolbar.ts b/packages/editor/src/init-default-config/config/toolbar.ts index bac3479b4..52977ca3f 100644 --- a/packages/editor/src/init-default-config/config/toolbar.ts +++ b/packages/editor/src/init-default-config/config/toolbar.ts @@ -4,10 +4,11 @@ */ import { t } from '@wangeditor-next/core' + import { + IMAGE_SVG, INDENT_RIGHT_SVG, JUSTIFY_LEFT_SVG, - IMAGE_SVG, MORE_SVG, VIDEO_SVG, } from '../../constants/svg' diff --git a/packages/editor/src/init-default-config/index.ts b/packages/editor/src/init-default-config/index.ts index f3976dd83..d201ea215 100644 --- a/packages/editor/src/init-default-config/index.ts +++ b/packages/editor/src/init-default-config/index.ts @@ -3,6 +3,8 @@ * @author wangfupeng */ +import { wangEditorCodeHighLightDecorate } from '@wangeditor-next/code-highlight' + import Boot from '../Boot' import { getDefaultEditorConfig, @@ -11,22 +13,24 @@ import { getSimpleToolbarConfig, } from './config' -import { wangEditorCodeHighLightDecorate } from '@wangeditor-next/code-highlight' - const defaultEditorConfig = getDefaultEditorConfig() + Boot.setEditorConfig({ ...defaultEditorConfig, decorate: wangEditorCodeHighLightDecorate, // 代码高亮 }) const simpleEditorConfig = getSimpleEditorConfig() + Boot.setSimpleEditorConfig({ ...simpleEditorConfig, decorate: wangEditorCodeHighLightDecorate, // 代码高亮 }) const defaultToolbarConfig = getDefaultToolbarConfig() + Boot.setToolbarConfig(defaultToolbarConfig) const simpleToolbarConfig = getSimpleToolbarConfig() + Boot.setSimpleToolbarConfig(simpleToolbarConfig) diff --git a/packages/editor/src/locale/index.ts b/packages/editor/src/locale/index.ts index f3b7049e9..120f32884 100644 --- a/packages/editor/src/locale/index.ts +++ b/packages/editor/src/locale/index.ts @@ -4,6 +4,7 @@ */ import { i18nAddResources } from '@wangeditor-next/core' + import enResources from './en' import zhResources from './zh-CN' diff --git a/packages/editor/src/register-builtin-modules/index.ts b/packages/editor/src/register-builtin-modules/index.ts index 4fdb58f7b..3c9e4ebb0 100644 --- a/packages/editor/src/register-builtin-modules/index.ts +++ b/packages/editor/src/register-builtin-modules/index.ts @@ -5,26 +5,22 @@ // basic-modules import '@wangeditor-next/basic-modules/dist/css/style.css' -import basicModules from '@wangeditor-next/basic-modules' - import '@wangeditor-next/list-module/dist/css/style.css' -import wangEditorListModule from '@wangeditor-next/list-module' - // table-module import '@wangeditor-next/table-module/dist/css/style.css' -import wangEditorTableModule from '@wangeditor-next/table-module' - // video-module import '@wangeditor-next/video-module/dist/css/style.css' -import wangEditorVideoModule from '@wangeditor-next/video-module' - // upload-image-module import '@wangeditor-next/upload-image-module/dist/css/style.css' -import wangEditorUploadImageModule from '@wangeditor-next/upload-image-module' - // code-highlight import '@wangeditor-next/code-highlight/dist/css/style.css' + +import basicModules from '@wangeditor-next/basic-modules' import { wangEditorCodeHighlightModule } from '@wangeditor-next/code-highlight' +import wangEditorListModule from '@wangeditor-next/list-module' +import wangEditorTableModule from '@wangeditor-next/table-module' +import wangEditorUploadImageModule from '@wangeditor-next/upload-image-module' +import wangEditorVideoModule from '@wangeditor-next/video-module' import registerModule from './register' diff --git a/packages/editor/src/register-builtin-modules/register.ts b/packages/editor/src/register-builtin-modules/register.ts index 3e1a2e449..ea56179c6 100644 --- a/packages/editor/src/register-builtin-modules/register.ts +++ b/packages/editor/src/register-builtin-modules/register.ts @@ -3,9 +3,10 @@ * @author wangfupeng */ -import Boot from '../Boot' import { IModuleConf } from '@wangeditor-next/core' +import Boot from '../Boot' + function registerModule(module: Partial<IModuleConf>) { const { menus, diff --git a/packages/editor/src/utils/browser-polyfill.ts b/packages/editor/src/utils/browser-polyfill.ts index 4aa4f8310..efb2fd47e 100644 --- a/packages/editor/src/utils/browser-polyfill.ts +++ b/packages/editor/src/utils/browser-polyfill.ts @@ -10,6 +10,7 @@ if (typeof global === 'undefined') { // 检查 IE 浏览器 if ('ActiveXObject' in window) { let info = '抱歉,wangEditor V5+ 版本开始,不在支持 IE 浏览器' + info += '\n Sorry, wangEditor V5+ versions do not support IE browser.' console.error(info) } @@ -34,6 +35,7 @@ function AggregateErrorPolyfill() { if (typeof AggregateError === 'undefined') { window.AggregateError = function (errors, msg) { const err = new Error(msg) + err.errors = errors return err } diff --git a/packages/editor/src/utils/node-polyfill.ts b/packages/editor/src/utils/node-polyfill.ts index affc164cf..09df97b02 100644 --- a/packages/editor/src/utils/node-polyfill.ts +++ b/packages/editor/src/utils/node-polyfill.ts @@ -25,7 +25,7 @@ if (typeof global === 'object') { } global.btoa = () => {} global.crypto = { - getRandomValues: function (buffer: any) { + getRandomValues(buffer: any) { return nodeCrypto.randomFillSync(buffer) }, } diff --git a/packages/list-module/__tests__/elem-to-html.test.ts b/packages/list-module/__tests__/elem-to-html.test.ts index db0504ab4..a355c8ef6 100644 --- a/packages/list-module/__tests__/elem-to-html.test.ts +++ b/packages/list-module/__tests__/elem-to-html.test.ts @@ -4,9 +4,9 @@ */ import createEditor from '../../../tests/utils/create-editor' -import { ELEM_TO_EDITOR } from '../src/utils/maps' -import $, { getTagName } from '../src/utils/dom' import listItemToHtmlConf from '../src/module/elem-to-html' +import $, { getTagName } from '../src/utils/dom' +import { ELEM_TO_EDITOR } from '../src/utils/maps' describe('module elem-to-html', () => { const childrenHtml = '<span>hello</span>' @@ -28,15 +28,16 @@ describe('module elem-to-html', () => { ELEM_TO_EDITOR.set(unOrderedItem2, editor) ELEM_TO_EDITOR.set(unOrderedItem21, editor) - test(`toHtml conf type`, () => { + test('toHtml conf type', () => { expect(listItemToHtmlConf.type).toBe('list-item') }) - test(`ordered item toHtml`, () => { + test('ordered item toHtml', () => { const { elemToHtml } = listItemToHtmlConf // first item const firstHtml = elemToHtml(orderedElem1, childrenHtml) + expect(firstHtml).toEqual({ html: '<li><span>hello</span></li>', prefix: '<ol>', // 第一个 item ,前面会有 <ol> @@ -45,6 +46,7 @@ describe('module elem-to-html', () => { // last item const lastHtml = elemToHtml(orderedElem2, childrenHtml) + expect(lastHtml).toEqual({ html: '<li><span>hello</span></li>', prefix: '', @@ -52,11 +54,12 @@ describe('module elem-to-html', () => { }) }) - test(`unOrdered item toHtml`, () => { + test('unOrdered item toHtml', () => { const { elemToHtml } = listItemToHtmlConf // first item const firstHtml = elemToHtml(unOrderedItem1, childrenHtml) + expect(firstHtml).toEqual({ html: '<li><span>hello</span></li>', prefix: '<ul>', // 第一个 item ,前面会有 <ul> @@ -65,6 +68,7 @@ describe('module elem-to-html', () => { // second item const secondHtml = elemToHtml(unOrderedItem2, childrenHtml) + expect(secondHtml).toEqual({ html: '<li><span>hello</span></li>', // 第二个 item ,不应该有 <ul> prefix: '', @@ -73,6 +77,7 @@ describe('module elem-to-html', () => { // last item - leveled const lastHtml = elemToHtml(unOrderedItem21, childrenHtml) + expect(lastHtml).toEqual({ html: '<li><span>hello</span></li>', // 最后一个 item ( leveled ) ,包裹 <ul> prefix: '<ul>', @@ -85,15 +90,20 @@ describe('module elem-to-html', () => { // 创建一个空的 Dom7Array const $elem = $() const tagName = getTagName($elem) + expect(tagName).toBe('') }) }) describe('module elem-to-html complex list', () => { const unOrderedElem1 = { type: 'list-item', ordered: false, children: [{ text: '' }] } - const unOrderedElem2 = { type: 'list-item', ordered: false, level: 1, children: [{ text: '' }] } + const unOrderedElem2 = { + type: 'list-item', ordered: false, level: 1, children: [{ text: '' }], + } const unOrderedElem3 = { type: 'list-item', ordered: false, children: [{ text: '' }] } - const orderedElem1 = { type: 'list-item', ordered: true, level: 1, children: [{ text: '' }] } + const orderedElem1 = { + type: 'list-item', ordered: true, level: 1, children: [{ text: '' }], + } const orderedElem2 = { type: 'list-item', ordered: true, children: [{ text: '' }] } const firstTextHtml = { type: 'paragraph', children: [{ text: 'hello' }] } const lastTextHtml = { type: 'paragraph', children: [{ text: 'world' }] } @@ -122,24 +132,28 @@ describe('module elem-to-html complex list', () => { const childrenHtml = '<span>hello</span>' const { elemToHtml } = listItemToHtmlConf const unOrderedHtml1 = elemToHtml(unOrderedElem1, childrenHtml) + expect(unOrderedHtml1).toEqual({ html: '<li><span>hello</span></li>', prefix: '<ul>', suffix: '', }) const unOrderedHtml2 = elemToHtml(unOrderedElem2, childrenHtml) + expect(unOrderedHtml2).toEqual({ html: '<li><span>hello</span></li>', prefix: '<ul>', suffix: '</ul>', }) const orderedHtml1 = elemToHtml(orderedElem1, childrenHtml) + expect(orderedHtml1).toEqual({ html: '<li><span>hello</span></li>', prefix: '<ol>', suffix: '</ol></ul>', }) const orderedHtml2 = elemToHtml(orderedElem2, childrenHtml) + expect(orderedHtml2).toEqual({ html: '<li><span>hello</span></li>', prefix: '<ol>', diff --git a/packages/list-module/__tests__/menu/bulleted-list-menu.test.ts b/packages/list-module/__tests__/menu/bulleted-list-menu.test.ts index 1abb3cd91..f66d5c176 100644 --- a/packages/list-module/__tests__/menu/bulleted-list-menu.test.ts +++ b/packages/list-module/__tests__/menu/bulleted-list-menu.test.ts @@ -3,14 +3,15 @@ * @author wangfupeng */ -import BulletedListMenu from '../../src/module/menu/BulletedListMenu' import createEditor from '../../../../tests/utils/create-editor' +import BulletedListMenu from '../../src/module/menu/BulletedListMenu' describe('list BulletedListMenu', () => { const menu = new BulletedListMenu() it('getValue', () => { const editor = createEditor() + expect(menu.getValue(editor)).toBe('') }) @@ -75,6 +76,7 @@ describe('list BulletedListMenu', () => { const editor = createEditor({ content: [pElem], }) + editor.select({ path: [0, 0], offset: 0 }) // 选中 p menu.exec(editor, '') // p 转 li diff --git a/packages/list-module/__tests__/menu/numbered-list-menu.test.ts b/packages/list-module/__tests__/menu/numbered-list-menu.test.ts index ada56b747..e07b368fa 100644 --- a/packages/list-module/__tests__/menu/numbered-list-menu.test.ts +++ b/packages/list-module/__tests__/menu/numbered-list-menu.test.ts @@ -3,14 +3,15 @@ * @author wangfupeng */ -import NumberedListMenu from '../../src/module/menu/NumberedListMenu' import createEditor from '../../../../tests/utils/create-editor' +import NumberedListMenu from '../../src/module/menu/NumberedListMenu' describe('list NumberedListMenu', () => { const menu = new NumberedListMenu() it('getValue', () => { const editor = createEditor() + expect(menu.getValue(editor)).toBe('') }) @@ -75,6 +76,7 @@ describe('list NumberedListMenu', () => { const editor = createEditor({ content: [pElem], }) + editor.select({ path: [0, 0], offset: 0 }) // 选中 p menu.exec(editor, '') // p 转 li diff --git a/packages/list-module/__tests__/parse-html.test.ts b/packages/list-module/__tests__/parse-html.test.ts index 5ded5d336..613466e73 100644 --- a/packages/list-module/__tests__/parse-html.test.ts +++ b/packages/list-module/__tests__/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../tests/utils/create-editor' import { parseItemHtmlConf, parseListHtmlConf } from '../src/module/parse-elem-html' @@ -13,10 +14,12 @@ describe('list - parse html', () => { it('parse unOrdered list item', () => { const $ul = $('<ul></ul>') const $li = $('<li></li>') + $ul.append($li) const children = [{ text: 'hello' }] const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) + expect(elem).toEqual({ type: 'list-item', ordered: false, @@ -28,10 +31,12 @@ describe('list - parse html', () => { it('parse ordered list item', () => { const $ol = $('<ol></ol>') const $li = $('<li></li>') + $ol.append($li) const children = [{ text: 'hello' }] const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) + expect(elem).toEqual({ type: 'list-item', ordered: true, @@ -44,11 +49,13 @@ describe('list - parse html', () => { const $ul = $('<ul></ul>') const $ol = $('<ol></ol>') const $li = $('<li></li>') + $ul.append($ol) $ol.append($li) const children = [{ text: 'hello' }] const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) + expect(elem).toEqual({ type: 'list-item', ordered: true, @@ -86,6 +93,7 @@ describe('list - parse html', () => { ] // @ts-ignore const listElems = parseListHtmlConf.parseElemHtml($ol[0], children, editor) + expect(listElems.length).toBe(4) // parse list 时,会把输出的结果(数组)flatten ,把嵌套的平铺开 }) @@ -93,6 +101,7 @@ describe('list - parse html', () => { const $ul = $('<ul></ul>') const $ol = $('<ol></ol>') const $li = $('<li></li>') + $ul.append($ol) $ol.append($li) const children = [ @@ -104,6 +113,7 @@ describe('list - parse html', () => { ] const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) + expect(elem).toEqual({ type: 'list-item', ordered: true, @@ -116,6 +126,7 @@ describe('list - parse html', () => { const $ul = $('<ul></ul>') const $ol = $('<ol></ol>') const $li = $('<li></li>') + $ul.append($ol) $ol.append($li) const children = [ @@ -126,6 +137,7 @@ describe('list - parse html', () => { ] const elem = parseItemHtmlConf.parseElemHtml($li[0], children, editor) + expect(elem).toEqual({ type: 'list-item', ordered: true, diff --git a/packages/list-module/__tests__/plugin.test.ts b/packages/list-module/__tests__/plugin.test.ts index d09c3fb96..1eb4eb98b 100644 --- a/packages/list-module/__tests__/plugin.test.ts +++ b/packages/list-module/__tests__/plugin.test.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import withList from '../src/module/plugin' import createEditor from '../../../tests/utils/create-editor' +import withList from '../src/module/plugin' describe('list plugin test', () => { it('insert tab - increase level', () => { @@ -16,6 +16,7 @@ describe('list plugin test', () => { let textEditor = createEditor({ content: [textItem], }) + editor = withList(editor) // 使用插件 textEditor = withList(textEditor) // 使用插件 @@ -26,13 +27,15 @@ describe('list plugin test', () => { textEditor.select({ path: [0, 0], offset: 0 }) textEditor.handleTab() // tab const textChildren = textEditor.children + expect(textChildren).toEqual([{ type: 'paragraph', children: [{ text: ' ' }] }]) editor.select({ path: [1, 0], offset: 0 }) // 选中 list-item 开头 editor.handleTab() // tab - let children = editor.children + const children = editor.children + expect(children[1]).toEqual({ ...listItem, level: 1, // 增加 level @@ -45,10 +48,12 @@ describe('list plugin test', () => { let editor = createEditor({ content: [listItem], }) + editor = withList(editor) // 使用插件 editor.select([]) // 全选 editor.handleTab() // tab const children = editor.children + expect(children).toEqual([{ children: [{ text: ' ' }], type: 'list-item' }]) }) it('insert tab - select all and other item', () => { @@ -58,10 +63,12 @@ describe('list plugin test', () => { let editor = createEditor({ content: [textItem, listItem], }) + editor = withList(editor) // 使用插件 editor.select([]) // 全选 editor.handleTab() // tab const children = editor.children + expect(children).toEqual([{ children: [{ text: ' ' }], type: 'list-item' }]) }) @@ -71,10 +78,12 @@ describe('list plugin test', () => { let editor = createEditor({ content: [listItem, listItem1], }) + editor = withList(editor) // 使用插件 editor.select([]) // 全选 editor.handleTab() // tab const children = editor.children + expect(children).toEqual([ { children: [{ text: 'hello' }], level: 1, type: 'list-item' }, { children: [{ text: 'world' }], level: 1, type: 'list-item' }, @@ -84,20 +93,22 @@ describe('list plugin test', () => { it('insert delete - decrease level', () => { // 测试没有选区 let emptyEditor = createEditor() + emptyEditor = withList(emptyEditor) // 使用插件 emptyEditor.deleteBackward('character') expect(emptyEditor.children).toEqual([{ type: 'paragraph', children: [{ text: '' }] }]) - //没有 list + // 没有 list emptyEditor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 emptyEditor.deleteBackward('character') expect(emptyEditor.children).toEqual([{ type: 'paragraph', children: [{ text: '' }] }]) - //单个 list + // 单个 list const listItem = { type: 'list-item', children: [{ text: 'hello' }], level: 1 } let editor = createEditor({ content: [listItem], }) + editor = withList(editor) // 使用插件 editor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 editor.deleteBackward('character') // delete @@ -125,8 +136,9 @@ describe('list plugin test', () => { }) it('insert enter - empty', () => { - //没有 list - let emptyEditor = createEditor() + // 没有 list + const emptyEditor = createEditor() + emptyEditor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 emptyEditor.insertBreak() expect(emptyEditor.children).toEqual([ @@ -136,13 +148,14 @@ describe('list plugin test', () => { }) it('insert delete - decrease level multi list', () => { - //测试没有同级 list + // 测试没有同级 list const textItem = { type: 'paragraph', children: [{ text: '' }] } const listItem = { type: 'list-item', children: [{ text: 'hello' }], level: 1 } const listItem1 = { type: 'list-item', children: [{ text: 'hello' }], level: 1 } let editor = createEditor({ content: [textItem, listItem, listItem1], }) + editor = withList(editor) // 使用插件 editor.select({ path: [2, 0], offset: 0 }) // 选中 list-item 开头 editor.deleteBackward('character') // delete @@ -151,7 +164,7 @@ describe('list plugin test', () => { level: 0, // 减少 level }) - //测试有同级 list + // 测试有同级 list const listOrderedItem = { type: 'list-item', ordered: true, children: [{ text: '' }] } const listUnOrderedItem = { type: 'list-item', @@ -159,6 +172,7 @@ describe('list plugin test', () => { ordered: false, level: 1, } + editor = createEditor({ content: [listOrderedItem, listUnOrderedItem], }) @@ -177,6 +191,7 @@ describe('list plugin test', () => { let editor = createEditor({ content: [listItem], }) + editor = withList(editor) // 使用插件 editor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 @@ -189,6 +204,7 @@ describe('list plugin test', () => { let editor = createEditor({ content: [listItem], }) + editor = withList(editor) // 使用插件 editor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 @@ -204,6 +220,7 @@ describe('list plugin test', () => { let editor = createEditor({ content: [listItem], }) + editor = withList(editor) // 使用插件 editor.select({ path: [0, 0], offset: 0 }) // 选中 list-item 开头 @@ -223,6 +240,7 @@ describe('list plugin test', () => { ], }) const bulletedList = { type: 'bulleted-list', children: [listItem] } + editor = withList(editor) // 使用插件 editor.insertNode(bulletedList) diff --git a/packages/list-module/__tests__/render-elem.test.ts b/packages/list-module/__tests__/render-elem.test.ts index 57ed246b3..65113c1f1 100644 --- a/packages/list-module/__tests__/render-elem.test.ts +++ b/packages/list-module/__tests__/render-elem.test.ts @@ -28,39 +28,48 @@ describe('list module - render elem', () => { it('render ordered list item elem', () => { const vnode: any = renderListItemConf.renderElem(orderedItem, null, editor) + expect(vnode.sel).toBe('div') // render-elem 使用 <div> 模拟 <li> const prefixVnode = vnode.children[0] || {} + expect(prefixVnode.text).toBe('1.') // ordered list-item 有序号 }) it('render unOrdered list item elem', () => { const vnode: any = renderListItemConf.renderElem(unOrderedItem, null, editor) + expect(vnode.sel).toBe('div') // render-elem 使用 <div> 模拟 <li> const prefixVnode = vnode.children[0] || {} + expect(prefixVnode.text).toBe('•') // unOrdered list-item 点号 }) it('render leveled list item elem', () => { const vnode: any = renderListItemConf.renderElem(leveledItem, null, editor) const style = vnode.data.style + expect(style).toEqual({ margin: '5px 0 5px 60px' }) // margin-left 60px }) it('render one leveled orderd list item elem', () => { const vnode: any = renderListItemConf.renderElem(leveleOneUndOrderedItem, null, editor) + expect(vnode.sel).toBe('div') // render-elem 使用 <div> 模拟 <li> const prefixVnode = vnode.children[0] || {} + expect(prefixVnode.text).toBe('◦') // unOrdered list-item 点号 }) it('render two leveled orderd list item elem', () => { const vnode: any = renderListItemConf.renderElem(leveleTwoUndOrderedItem, null, editor) + expect(vnode.sel).toBe('div') // render-elem 使用 <div> 模拟 <li> const prefixVnode = vnode.children[0] || {} + expect(prefixVnode.text).toBe('▪') // unOrdered list-item 点号 }) @@ -70,9 +79,11 @@ describe('list module - render elem', () => { content: [orderedItem, orderedItem], }) const vnode: any = renderListItemConf.renderElem(orderedItem, null, editor) + expect(vnode.sel).toBe('div') // render-elem 使用 <div> 模拟 <li> const prefixVnode = vnode.children[0] || {} + expect(prefixVnode.text).toBe('2.') // ordered list-item 有序号 }) }) diff --git a/packages/list-module/rollup.config.js b/packages/list-module/rollup.config.js index 28f6aa6e9..a48732e72 100644 --- a/packages/list-module/rollup.config.js +++ b/packages/list-module/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorListModule' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/list-module/src/constants/svg.ts b/packages/list-module/src/constants/svg.ts index 1ee141bfa..d726d8142 100644 --- a/packages/list-module/src/constants/svg.ts +++ b/packages/list-module/src/constants/svg.ts @@ -10,9 +10,7 @@ */ // 无序列表 -export const BULLETED_LIST_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M384 64h640v128H384V64z m0 384h640v128H384v-128z m0 384h640v128H384v-128zM0 128a128 128 0 1 1 256 0 128 128 0 0 1-256 0z m0 384a128 128 0 1 1 256 0 128 128 0 0 1-256 0z m0 384a128 128 0 1 1 256 0 128 128 0 0 1-256 0z"></path></svg>' +export const BULLETED_LIST_SVG = '<svg viewBox="0 0 1024 1024"><path d="M384 64h640v128H384V64z m0 384h640v128H384v-128z m0 384h640v128H384v-128zM0 128a128 128 0 1 1 256 0 128 128 0 0 1-256 0z m0 384a128 128 0 1 1 256 0 128 128 0 0 1-256 0z m0 384a128 128 0 1 1 256 0 128 128 0 0 1-256 0z"></path></svg>' // 有序列表 -export const NUMBERED_LIST_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M384 832h640v128H384z m0-384h640v128H384z m0-384h640v128H384zM192 0v256H128V64H64V0zM128 526.016v50.016h128v64H64v-146.016l128-60V384H64v-64h192v146.016zM256 704v320H64v-64h128v-64H64v-64h128v-64H64v-64z"></path></svg>' +export const NUMBERED_LIST_SVG = '<svg viewBox="0 0 1024 1024"><path d="M384 832h640v128H384z m0-384h640v128H384z m0-384h640v128H384zM192 0v256H128V64H64V0zM128 526.016v50.016h128v64H64v-146.016l128-60V384H64v-64h192v146.016zM256 704v320H64v-64h128v-64H64v-64h128v-64H64v-64z"></path></svg>' diff --git a/packages/list-module/src/index.ts b/packages/list-module/src/index.ts index a32d617ab..02387b31e 100644 --- a/packages/list-module/src/index.ts +++ b/packages/list-module/src/index.ts @@ -4,10 +4,10 @@ */ import './assets/index.less' - // 配置多语言 import './locale/index' // 导出 module import wangEditorListModule from './module/index' + export default wangEditorListModule diff --git a/packages/list-module/src/locale/index.ts b/packages/list-module/src/locale/index.ts index f3b7049e9..120f32884 100644 --- a/packages/list-module/src/locale/index.ts +++ b/packages/list-module/src/locale/index.ts @@ -4,6 +4,7 @@ */ import { i18nAddResources } from '@wangeditor-next/core' + import enResources from './en' import zhResources from './zh-CN' diff --git a/packages/list-module/src/module/custom-types.ts b/packages/list-module/src/module/custom-types.ts index 3c72fe47c..711bd9cb1 100644 --- a/packages/list-module/src/module/custom-types.ts +++ b/packages/list-module/src/module/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type ListItemElement = { type: 'list-item' diff --git a/packages/list-module/src/module/elem-to-html.ts b/packages/list-module/src/module/elem-to-html.ts index 222656c4e..57aef703b 100644 --- a/packages/list-module/src/module/elem-to-html.ts +++ b/packages/list-module/src/module/elem-to-html.ts @@ -3,12 +3,13 @@ * @author wangfupeng */ -import { Element, Path, Editor } from 'slate' import { DomEditor } from '@wangeditor-next/core' -import { ListItemElement } from './custom-types' +import { Editor, Element, Path } from 'slate' + import { ELEM_TO_EDITOR } from '../utils/maps' -import { hasSameOrderWithBrother } from './helpers' import { getListItemColor } from '../utils/util' +import { ListItemElement } from './custom-types' +import { hasSameOrderWithBrother } from './helpers' /** * 当前 list-item 前面需要拼接几个 <ol> 或 <ul> @@ -16,11 +17,13 @@ import { getListItemColor } from '../utils/util' */ function getStartContainerTagNumber(elem: Element): number { const editor = ELEM_TO_EDITOR.get(elem) - if (editor == null) return 0 + + if (editor == null) { return 0 } const { type, ordered = false, level = 0 } = elem as ListItemElement const path = DomEditor.findPath(editor, elem) + if (path[0] === 0) { // list-item 是第一个元素,再往前没有了。需要拼接 <ol> 或 <ul> return level + 1 @@ -29,10 +32,12 @@ function getStartContainerTagNumber(elem: Element): number { // 获取上一个 elem const prevPath = Path.previous(path) const prevEntry = Editor.node(editor, prevPath) - if (!prevEntry) return 0 + + if (!prevEntry) { return 0 } const [prevElem] = prevEntry const prevType = DomEditor.getNodeType(prevElem) + if (prevType !== type) { // 上一个 elem 不是 list-item ,需要拼接 <ol> 或 <ul> return level + 1 @@ -40,6 +45,7 @@ function getStartContainerTagNumber(elem: Element): number { // 上一个 elem 是 list-item const { ordered: prevOrdered = false, level: prevLevel = 0 } = prevElem as ListItemElement + if (prevLevel < level) { // 上一个 level 小于当前 level ,需要拼接 <ol> 或 <ul> return level - prevLevel @@ -53,10 +59,10 @@ function getStartContainerTagNumber(elem: Element): number { if (prevOrdered === ordered) { // ordered 一致,则不需要拼接 <ol> 或 <ul> return 0 - } else { - /// ordered 不一致,则需要拼接 <ol> 或 <ul> - return 1 } + /// ordered 不一致,则需要拼接 <ol> 或 <ul> + return 1 + } // 其他情况 @@ -69,11 +75,13 @@ function getStartContainerTagNumber(elem: Element): number { */ function getEndContainerTagNumber(elem: Element): number { const editor = ELEM_TO_EDITOR.get(elem) - if (editor == null) return 0 + + if (editor == null) { return 0 } const { type, ordered = false, level = 0 } = elem as ListItemElement const path = DomEditor.findPath(editor, elem) + if (path[0] === editor.children.length - 1) { // list-item 是最后一个元素,再往后没有了。需要拼接 </ol> 或 </ul> return level + 1 @@ -82,10 +90,12 @@ function getEndContainerTagNumber(elem: Element): number { // 获取下一个 elem const nextPath = Path.next(path) const nextEntry = Editor.node(editor, nextPath) - if (!nextEntry) return 0 + + if (!nextEntry) { return 0 } const [nextElem] = nextEntry const nextType = DomEditor.getNodeType(nextElem) + if (nextType !== type) { // 下一个 elem 不是 list-item ,需要拼接 <ol> 或 <ul> return level + 1 @@ -93,15 +103,16 @@ function getEndContainerTagNumber(elem: Element): number { // 下一个 elem 是 list-item const { ordered: nextOrdered = false, level: nextLevel = 0 } = nextElem as ListItemElement + if (nextLevel < level) { // 下一个 level 小于当前 level,此处需要看上一个同级兄弟节点 ordered 是否一致,如果一致则不需要拼接,否则需要拼接 if (hasSameOrderWithBrother(editor, nextElem as ListItemElement)) { // ordered 一致,则不需要额外拼接 </ol> 或 </ul> return level - nextLevel - } else { - // ordered 不一致,则需要额外拼接 </ol> 或 </ul> - return level - nextLevel + 1 } + // ordered 不一致,则需要额外拼接 </ol> 或 </ul> + return level - nextLevel + 1 + } if (nextLevel > level) { // 下一个 level 大于当前 level ,不需要拼接 </ol> 或 </ul> @@ -112,10 +123,10 @@ function getEndContainerTagNumber(elem: Element): number { if (nextOrdered === ordered) { // ordered 一致,则不需要拼接 </ol> 或 </ul> return 0 - } else { - /// ordered 不一致,则需要拼接 </ol> 或 </ul> - return 1 } + /// ordered 不一致,则需要拼接 </ol> 或 </ul> + return 1 + } // 其他情况 @@ -127,7 +138,7 @@ const CONTAINER_TAG_STACK: Array<string> = [] function elemToHtml( elem: Element, - childrenHtml: string + childrenHtml: string, ): { html: string prefix?: string @@ -141,6 +152,7 @@ function elemToHtml( // 前面需要拼接几个 <ol> 或 <ul> const startContainerTagNumber = getStartContainerTagNumber(elem) + if (startContainerTagNumber > 0) { for (let i = 0; i < startContainerTagNumber; i++) { startContainerStr += `<${containerTag}>` // 记录 start container tag ,如 `<ul>` @@ -150,9 +162,11 @@ function elemToHtml( // 后面需要拼接几个 </ol> 或 </ul> const endContainerTagNumber = getEndContainerTagNumber(elem) + if (endContainerTagNumber > 0) { for (let i = 0; i < endContainerTagNumber; i++) { const tag = CONTAINER_TAG_STACK.pop() // tag 从栈中获取 + endContainerStr += `</${tag}>` // 记录 end container tag ,如 `</ul>` } } @@ -170,7 +184,7 @@ function elemToHtml( const listItemToHtmlConf = { type: 'list-item', - elemToHtml: elemToHtml, + elemToHtml, } export default listItemToHtmlConf diff --git a/packages/list-module/src/module/helpers.ts b/packages/list-module/src/module/helpers.ts index 5f268d6f6..336413a24 100644 --- a/packages/list-module/src/module/helpers.ts +++ b/packages/list-module/src/module/helpers.ts @@ -3,8 +3,9 @@ * @author wangfupeng */ -import { Path, Editor } from 'slate' import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { Editor, Path } from 'slate' + import { ListItemElement } from './custom-types' /** @@ -15,7 +16,7 @@ import { ListItemElement } from './custom-types' export function getBrotherListNodeByLevel( editor: IDomEditor, elem: ListItemElement, - level?: number + level?: number, ): ListItemElement | null { const { type, ...otherProps } = elem // level 可能是 退格前的值,所以这里需要判断 @@ -53,8 +54,9 @@ export function getBrotherListNodeByLevel( export function hasSameOrderWithBrother( editor: IDomEditor, elem: ListItemElement, - level?: number + level?: number, ): boolean { const brotherElem = getBrotherListNodeByLevel(editor, elem, level) + return brotherElem ? brotherElem.ordered === elem.ordered : false } diff --git a/packages/list-module/src/module/index.ts b/packages/list-module/src/module/index.ts index 2fb9b14f4..3b63836cb 100644 --- a/packages/list-module/src/module/index.ts +++ b/packages/list-module/src/module/index.ts @@ -4,11 +4,12 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import renderListItemConf from './render-elem' -import withList from './plugin' -import { bulletedListMenuConf, numberedListMenuConf } from './menu/index' + import listItemToHtmlConf from './elem-to-html' +import { bulletedListMenuConf, numberedListMenuConf } from './menu/index' import { parseItemHtmlConf, parseListHtmlConf } from './parse-elem-html' +import withList from './plugin' +import renderListItemConf from './render-elem' const list: Partial<IModuleConf> = { renderElems: [renderListItemConf], diff --git a/packages/list-module/src/module/menu/BaseMenu.ts b/packages/list-module/src/module/menu/BaseMenu.ts index 8e92c6ff7..78be4073c 100644 --- a/packages/list-module/src/module/menu/BaseMenu.ts +++ b/packages/list-module/src/module/menu/BaseMenu.ts @@ -3,19 +3,27 @@ * @author wangfupeng */ -import { Editor, Node, Transforms, Element } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor } from '@wangeditor-next/core' +import { DomEditor, IButtonMenu, IDomEditor } from '@wangeditor-next/core' +import { + Editor, Element, Node, Transforms, +} from 'slate' + import { ListItemElement } from '../custom-types' abstract class BaseMenu implements IButtonMenu { readonly type = 'list-item' + abstract readonly ordered: boolean + abstract readonly title: string + abstract readonly iconSvg: string + readonly tag = 'button' private getListNode(editor: IDomEditor): Node | null { const { type } = this + return DomEditor.getSelectedNodeByType(editor, type) } @@ -25,34 +33,37 @@ abstract class BaseMenu implements IButtonMenu { isActive(editor: IDomEditor): boolean { const node = this.getListNode(editor) - if (node == null) return false + + if (node == null) { return false } const { ordered = false } = node as ListItemElement + return ordered === this.ordered } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const selectedElems = DomEditor.getSelectedElems(editor) const notMatch = selectedElems.some((elem: Element) => { - if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem)) return true + if (Editor.isVoid(editor, elem) && Editor.isBlock(editor, elem)) { return true } const { type } = elem as Element - if (['pre', 'code', 'table'].includes(type)) return true + + if (['pre', 'code', 'table'].includes(type)) { return true } }) - if (notMatch) return true + + if (notMatch) { return true } return false } exec(editor: IDomEditor, value: string | boolean): void { const active = this.isActive(editor) + if (active) { // 如果当前 active ,则转换为 p 标签 Transforms.setNodes(editor, { type: 'paragraph', - ordered: undefined, - level: undefined, }) } else { // 否则,转换为 list-item diff --git a/packages/list-module/src/module/menu/BulletedListMenu.ts b/packages/list-module/src/module/menu/BulletedListMenu.ts index 07204fe92..a86e49bc4 100644 --- a/packages/list-module/src/module/menu/BulletedListMenu.ts +++ b/packages/list-module/src/module/menu/BulletedListMenu.ts @@ -4,12 +4,15 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { BULLETED_LIST_SVG } from '../../constants/svg' +import BaseMenu from './BaseMenu' class BulletedListMenu extends BaseMenu { readonly ordered = false + readonly title = t('listModule.unOrderedList') + readonly iconSvg = BULLETED_LIST_SVG } diff --git a/packages/list-module/src/module/menu/NumberedListMenu.ts b/packages/list-module/src/module/menu/NumberedListMenu.ts index 40f89c968..84cf41a2f 100644 --- a/packages/list-module/src/module/menu/NumberedListMenu.ts +++ b/packages/list-module/src/module/menu/NumberedListMenu.ts @@ -4,12 +4,15 @@ */ import { t } from '@wangeditor-next/core' -import BaseMenu from './BaseMenu' + import { NUMBERED_LIST_SVG } from '../../constants/svg' +import BaseMenu from './BaseMenu' class NumberedListMenu extends BaseMenu { readonly ordered = true + readonly title = t('listModule.orderedList') + readonly iconSvg = NUMBERED_LIST_SVG } diff --git a/packages/list-module/src/module/parse-elem-html.ts b/packages/list-module/src/module/parse-elem-html.ts index c1c32d23b..b6679a541 100644 --- a/packages/list-module/src/module/parse-elem-html.ts +++ b/packages/list-module/src/module/parse-elem-html.ts @@ -3,10 +3,11 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Dom7Array } from 'dom7' import { Descendant, Text } from 'slate' + import $, { DOMElement, getTagName } from '../utils/dom' -import { IDomEditor } from '@wangeditor-next/core' import { ListItemElement } from './custom-types' /** @@ -16,7 +17,8 @@ import { ListItemElement } from './custom-types' function getOrdered($elem: Dom7Array): boolean { const $list = $elem.parent() const listTagName = getTagName($list) - if (listTagName === 'ol') return true + + if (listTagName === 'ol') { return true } return false } @@ -42,13 +44,13 @@ function getLevel($elem: Dom7Array): number { function parseItemHtml( elem: DOMElement, children: Descendant[], - editor: IDomEditor + editor: IDomEditor, ): ListItemElement { const $elem = $(elem) children = children.filter(child => { - if (Text.isText(child)) return true - if (editor.isInline(child)) return true + if (Text.isText(child)) { return true } + if (editor.isInline(child)) { return true } return false }) @@ -77,7 +79,7 @@ export const parseItemHtmlConf = { function parseListHtml( elem: DOMElement, children: Descendant[], - editor: IDomEditor + editor: IDomEditor, ): ListItemElement[] { // @ts-ignore flatten 因为可能有 ul/ol 嵌套,重要!!! return children.flat(Infinity) diff --git a/packages/list-module/src/module/plugin.ts b/packages/list-module/src/module/plugin.ts index 9e99e94ff..14eea80a4 100644 --- a/packages/list-module/src/module/plugin.ts +++ b/packages/list-module/src/module/plugin.ts @@ -3,8 +3,11 @@ * @author wangfupeng */ -import { Editor, Transforms, Range, Path } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' +import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { + Editor, Path, Range, Transforms, +} from 'slate' + import { ListItemElement } from './custom-types' import { getBrotherListNodeByLevel } from './helpers' @@ -20,7 +23,9 @@ function getTopSelectedElemsBySelection(editor: IDomEditor) { } function withList<T extends IDomEditor>(editor: T): T { - const { deleteBackward, handleTab, normalizeNode, insertBreak } = editor + const { + deleteBackward, handleTab, normalizeNode, insertBreak, + } = editor const newEditor = editor // 重写 insertBreak - 空 list 点击回车时删除该空 list @@ -29,13 +34,13 @@ function withList<T extends IDomEditor>(editor: T): T { match: n => DomEditor.checkNodeType(n, 'list-item'), universal: true, }) - if (!nodeEntry) return insertBreak() + + if (!nodeEntry) { return insertBreak() } const listElem = nodeEntry[0] as ListItemElement + if (listElem.children[0].text === '') { Transforms.setNodes(newEditor, { type: 'paragraph', - ordered: undefined, - level: undefined, }) return } @@ -45,6 +50,7 @@ function withList<T extends IDomEditor>(editor: T): T { // 重写 deleteBackward - 降低 level 或者转换为 p 元素 newEditor.deleteBackward = unit => { const { selection } = newEditor + if (selection == null) { deleteBackward(unit) return @@ -56,6 +62,7 @@ function withList<T extends IDomEditor>(editor: T): T { } const listItemElem = DomEditor.getSelectedNodeByType(newEditor, 'list-item') + if (listItemElem == null) { // 未匹配到 list-item deleteBackward(unit) @@ -65,22 +72,22 @@ function withList<T extends IDomEditor>(editor: T): T { if (selection.focus.offset === 0) { // 选中了当前 list-item 文本的开头,此时按删除键,应该降低 level 或转换为 p 元素 const { level = 0, ordered = false } = listItemElem as ListItemElement + if (level > 0) { // 如果有兄弟节点,则判断 ordered 是否一致,不一致需要切换 ordered const brotherElem = getBrotherListNodeByLevel( editor, listItemElem as ListItemElement, - level - 1 + level - 1, ) + if (brotherElem && brotherElem.ordered !== ordered) { Transforms.setNodes(newEditor, { level: level - 1, ordered: !ordered }) - } else Transforms.setNodes(newEditor, { level: level - 1 }) + } else { Transforms.setNodes(newEditor, { level: level - 1 }) } } else { // 转换为 p 元素 Transforms.setNodes(newEditor, { type: 'paragraph', - ordered: undefined, - level: undefined, }) } return @@ -93,6 +100,7 @@ function withList<T extends IDomEditor>(editor: T): T { // 重写 tab - 当选中 list-item 文本开头时,增加 level newEditor.handleTab = () => { const { selection } = newEditor + if (selection == null) { handleTab() return @@ -101,6 +109,7 @@ function withList<T extends IDomEditor>(editor: T): T { // 选区是合并的,判断单个 list-item 即可 if (Range.isCollapsed(selection)) { const listItemElem = DomEditor.getSelectedNodeByType(newEditor, 'list-item') + if (listItemElem == null) { // 未匹配到 list-item handleTab() @@ -110,6 +119,7 @@ function withList<T extends IDomEditor>(editor: T): T { if (selection.focus.offset === 0) { // 选中了当前 list-item 文本的开头,此时按 tab 应该增加 level const { level = 0 } = listItemElem as ListItemElement + Transforms.setNodes(newEditor, { level: level + 1 }) return } @@ -123,8 +133,8 @@ function withList<T extends IDomEditor>(editor: T): T { for (const entry of getTopSelectedElemsBySelection(newEditor)) { const [elem] = entry const type = DomEditor.getNodeType(elem) - if (type === 'list-item') listItemNum++ - else hasOtherElem = true + + if (type === 'list-item') { listItemNum++ } else { hasOtherElem = true } } if (hasOtherElem || listItemNum <= 1) { @@ -137,6 +147,7 @@ function withList<T extends IDomEditor>(editor: T): T { for (const entry of getTopSelectedElemsBySelection(newEditor)) { const [elem, path] = entry const { level = 0 } = elem as ListItemElement + Transforms.setNodes(newEditor, { level: level + 1 }, { at: path }) } return diff --git a/packages/list-module/src/module/render-elem.tsx b/packages/list-module/src/module/render-elem.tsx index e013aff13..9bec6281c 100644 --- a/packages/list-module/src/module/render-elem.tsx +++ b/packages/list-module/src/module/render-elem.tsx @@ -3,12 +3,15 @@ * @author wangfupeng */ -import { Element as SlateElement, Path, Editor, Text } from 'slate' +import { DomEditor, IDomEditor } from '@wangeditor-next/core' +import { + Editor, Element as SlateElement, Path, Text, +} from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' -import { ListItemElement } from './custom-types' + import { ELEM_TO_EDITOR } from '../utils/maps' import { getListItemColor } from '../utils/util' +import { ListItemElement } from './custom-types' /** * 无序列表:根据 level 获取的前置符号 @@ -16,6 +19,7 @@ import { getListItemColor } from '../utils/util' */ function genPreSymbol(level = 0): string { let s = '' + switch (level) { case 0: s = '•' // 第一层级 @@ -45,25 +49,26 @@ function getOrderedItemNumber(editor: IDomEditor, elem: SlateElement): number { let curPath = DomEditor.findPath(editor, curElem) // 第一个元素,直接返回 1 - if (curPath[0] === 0) return 1 + if (curPath[0] === 0) { return 1 } while (curPath[0] > 0) { const prevPath = Path.previous(curPath) const prevEntry = Editor.node(editor, prevPath) - if (prevEntry == null) break + + if (prevEntry == null) { break } const prevElem = prevEntry[0] as ListItemElement // 上一个节点 const { level: prevLevel = 0, type: prevType, ordered: prevOrdered } = prevElem // type 不一致,退出循环,不再累加 num - if (prevType !== type) break + if (prevType !== type) { break } // prevLevel 更小,退出循环,不再累加 num - if (prevLevel < level) break + if (prevLevel < level) { break } if (prevLevel === level) { // level 一样,如果 ordered 不一样,则退出循环,不再累加 num - if (prevOrdered !== ordered) break + if (prevOrdered !== ordered) { break } // level 一样,order 一样,则累加 num - else num++ + else { num++ } } // prevLevel 更大,不累加 num ,继续向前 @@ -77,7 +82,7 @@ function getOrderedItemNumber(editor: IDomEditor, elem: SlateElement): number { function renderListElem( elemNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { ELEM_TO_EDITOR.set(elemNode, editor) // 记录 elem 和 editor 关系,elem-to-html 时要用 @@ -88,9 +93,11 @@ function renderListElem( // list-item 前缀 let prefix = '' + if (ordered) { // 有序列表:获取前缀 number const orderedNumber = getOrderedItemNumber(editor, elemNode) + prefix = `${orderedNumber}.` } else { // 无序列表:根据层级,使用不同的前缀符号 @@ -112,6 +119,7 @@ function renderListElem( <span>{children}</span> </div> ) + return vnode } diff --git a/packages/list-module/src/utils/dom.ts b/packages/list-module/src/utils/dom.ts index c07bb007c..88d543cec 100644 --- a/packages/list-module/src/utils/dom.ts +++ b/packages/list-module/src/utils/dom.ts @@ -3,19 +3,9 @@ * @author wangfupeng */ -import $, { append, on, focus, attr, val, html, parent, hasClass, Dom7Array, empty } from 'dom7' - -if (append) $.fn.append = append -// if (on) $.fn.on = on -// if (focus) $.fn.focus = focus -if (attr) $.fn.attr = attr -// if (val) $.fn.val = val -// if (html) $.fn.html = html -if (parent) $.fn.parent = parent -// if (hasClass) $.fn.hasClass = hasClass -// if (empty) $.fn.empty = empty - -export default $ +import $, { + append, attr, Dom7Array, empty, focus, hasClass, html, on, parent, val, +} from 'dom7' // COMPAT: This is required to prevent TypeScript aliases from doing some very // weird things for Slate's types with the same name as globals. (2019/11/27) @@ -27,13 +17,27 @@ import DOMText = globalThis.Text import DOMRange = globalThis.Range import DOMSelection = globalThis.Selection import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } + +if (append) { $.fn.append = append } +// if (on) $.fn.on = on +// if (focus) $.fn.focus = focus +if (attr) { $.fn.attr = attr } +// if (val) $.fn.val = val +// if (html) $.fn.html = html +if (parent) { $.fn.parent = parent } +// if (hasClass) $.fn.hasClass = hasClass +// if (empty) $.fn.empty = empty + +export default $ +export { + DOMComment, DOMElement, DOMNode, DOMRange, DOMSelection, DOMStaticRange, DOMText, +} /** * 获取 tagName lower-case * @param $elem $elem */ export function getTagName($elem: Dom7Array): string { - if ($elem.length) return $elem[0].tagName.toLowerCase() + if ($elem.length) { return $elem[0].tagName.toLowerCase() } return '' } diff --git a/packages/list-module/src/utils/maps.ts b/packages/list-module/src/utils/maps.ts index 81893029c..967477596 100644 --- a/packages/list-module/src/utils/maps.ts +++ b/packages/list-module/src/utils/maps.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -import { Element as SlateElement } from 'slate' import { IDomEditor } from '@wangeditor-next/core' +import { Element as SlateElement } from 'slate' export const ELEM_TO_EDITOR = new WeakMap<SlateElement, IDomEditor>() diff --git a/packages/list-module/src/utils/util.ts b/packages/list-module/src/utils/util.ts index 198e971f8..3ade3f96f 100644 --- a/packages/list-module/src/utils/util.ts +++ b/packages/list-module/src/utils/util.ts @@ -7,16 +7,18 @@ import { Element as SlateElement, Text } from 'slate' export function getListItemColor(elem: SlateElement): string { const children = elem.children || [] const length = children.length - if (length === 0) return '' + + if (length === 0) { return '' } let firstTextNode for (let i = 0; i < length; i++) { - if (firstTextNode) break // 已找到第一个 text-node ,则退出 + if (firstTextNode) { break } // 已找到第一个 text-node ,则退出 const child = children[i] - if (Text.isText(child)) firstTextNode = child + + if (Text.isText(child)) { firstTextNode = child } } - if (firstTextNode == null) return '' - return firstTextNode['color'] || '' + if (firstTextNode == null) { return '' } + return firstTextNode.color || '' } diff --git a/packages/table-module/__tests__/elem-to-html.test.ts b/packages/table-module/__tests__/elem-to-html.test.ts index 0d9c8f875..037a1f2dc 100644 --- a/packages/table-module/__tests__/elem-to-html.test.ts +++ b/packages/table-module/__tests__/elem-to-html.test.ts @@ -3,13 +3,14 @@ * @author luochao */ +import * as core from '@wangeditor-next/core' +import { Ancestor } from 'slate' + import { tableCellToHtmlConf, - tableToHtmlConf, tableRowToHtmlConf, + tableToHtmlConf, } from '../src/module/elem-to-html' -import * as core from '@wangeditor-next/core' -import { Ancestor } from 'slate' describe('TableModule module', () => { describe('module elem-to-html', () => { @@ -28,7 +29,7 @@ describe('TableModule module', () => { tableCellToHtmlConf.elemToHtml(element, '<span>123</span>') } catch (err) { expect(err.message).toBe( - `Cannot get table row node by cell node ${JSON.stringify(element)}` + `Cannot get table row node by cell node ${JSON.stringify(element)}`, ) } }) @@ -38,6 +39,7 @@ describe('TableModule module', () => { type: 'table-cell', children: [], } + jest .spyOn(core.DomEditor, 'getParentNode') .mockReturnValue({ type: 'table-row', children: [{ text: '' }] } as any) @@ -53,12 +55,14 @@ describe('TableModule module', () => { type: 'table-cell', children: [], } + jest .spyOn(core.DomEditor, 'getParentNode') .mockReturnValueOnce({ type: 'table-row', children: [{ text: '' }] } as any) .mockReturnValueOnce({ type: 'table', children: [{ text: '' }] } as Ancestor) const res = tableCellToHtmlConf.elemToHtml(element, '<span>123</span>') + expect(res).toBe('<td colSpan="1" rowSpan="1" width="auto" style=""><span>123</span></td>') }) @@ -73,6 +77,7 @@ describe('TableModule module', () => { children: [], } const res = tableRowToHtmlConf.elemToHtml(element, '<td>123</td>') + expect(res).toBe('<tr><td>123</td></tr>') }) @@ -87,8 +92,9 @@ describe('TableModule module', () => { children: [], } const res = tableToHtmlConf.elemToHtml(element, '<tr><td>123</td></tr>') + expect(res).toBe( - '<table style="width: auto;table-layout: fixed;height:auto"><tbody><tr><td>123</td></tr></tbody></table>' + '<table style="width: auto;table-layout: fixed;height:auto"><tbody><tr><td>123</td></tr></tbody></table>', ) }) @@ -100,8 +106,9 @@ describe('TableModule module', () => { children: [], } const res = tableToHtmlConf.elemToHtml(element, '<tr><td>123</td></tr>') + expect(res).toBe( - '<table style="width: 100%;table-layout: fixed;height:60px"><tbody><tr><td>123</td></tr></tbody></table>' + '<table style="width: 100%;table-layout: fixed;height:60px"><tbody><tr><td>123</td></tr></tbody></table>', ) }) }) diff --git a/packages/table-module/__tests__/menu/delete-col.test.ts b/packages/table-module/__tests__/menu/delete-col.test.ts index 233837c90..e689e85cb 100644 --- a/packages/table-module/__tests__/menu/delete-col.test.ts +++ b/packages/table-module/__tests__/menu/delete-col.test.ts @@ -1,10 +1,11 @@ -import DeleteCol from '../../src/module/menu/DeleteCol' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { DEL_COL_SVG } from '../../src/constants/svg' -import * as utils from '../../src/utils' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import DeleteCol from '../../src/module/menu/DeleteCol' +import * as utils from '../../src/utils' jest.mock('../../src/utils', () => ({ filledMatrix: jest.fn(), @@ -16,13 +17,14 @@ function setEditorSelection( selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Delete Col Menu', () => { test('it should create DeleteCol object', () => { const deleteColMenu = new DeleteCol() + expect(typeof deleteColMenu).toBe('object') expect(deleteColMenu.tag).toBe('button') expect(deleteColMenu.iconSvg).toBe(DEL_COL_SVG) @@ -32,18 +34,21 @@ describe('Table Module Delete Col Menu', () => { test('it should get empty string if invoke getValue method', () => { const deleteColMenu = new DeleteCol() const editor = createEditor() + expect(deleteColMenu.getValue(editor)).toBe('') }) test('it should get falsy value if invoke isActive method', () => { const deleteColMenu = new DeleteCol() const editor = createEditor() + expect(deleteColMenu.isActive(editor)).toBeFalsy() }) test('isDisabled should get truthy value if editor selection is null', () => { const deleteColMenu = new DeleteCol() const editor = createEditor() + editor.selection = null expect(deleteColMenu.isDisabled(editor)).toBeTruthy() }) @@ -51,6 +56,7 @@ describe('Table Module Delete Col Menu', () => { test('isDisabled should get truthy value if editor selection is collapsed', () => { const deleteColMenu = new DeleteCol() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) @@ -61,6 +67,7 @@ describe('Table Module Delete Col Menu', () => { test('isDisabled should get truthy value if editor current selected node is not table cell', () => { const deleteColMenu = new DeleteCol() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -72,6 +79,7 @@ describe('Table Module Delete Col Menu', () => { test('isDisabled should get falsy value if editor current selected node is table cell', () => { const deleteColMenu = new DeleteCol() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -83,6 +91,7 @@ describe('Table Module Delete Col Menu', () => { test('exec should return directly if menu is disabled', () => { const deleteColMenu = new DeleteCol() const editor = createEditor() + setEditorSelection(editor, null) expect(deleteColMenu.exec(editor, '')).toBeUndefined() @@ -107,8 +116,10 @@ describe('Table Module Delete Col Menu', () => { [0, 1], ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) const removeNodesFn = jest.fn() + jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) deleteColMenu.exec(editor, '') @@ -143,6 +154,7 @@ describe('Table Module Delete Col Menu', () => { [0, 1], ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) jest.spyOn(core.DomEditor, 'findPath').mockImplementation(() => [0, 1] as slate.Path) @@ -151,26 +163,35 @@ describe('Table Module Delete Col Menu', () => { [ [ [{ type: 'table-cell', children: [{ text: '' }], isHeader: false }, [0, 0, 0]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], [ [{ type: 'table-cell', children: [{ text: '' }], isHeader: false }, [0, 0, 1]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], ], [ [ [{ type: 'table-cell', children: [{ text: '' }] }, [0, 1, 0]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], [ [{ type: 'table-cell', children: [{ text: '' }] }, [0, 1, 1]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], ], ] }) const removeNodesFn = jest.fn() + jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) deleteColMenu.exec(editor, '') diff --git a/packages/table-module/__tests__/menu/delete-row.test.ts b/packages/table-module/__tests__/menu/delete-row.test.ts index 5c607bb7d..2500d5e85 100644 --- a/packages/table-module/__tests__/menu/delete-row.test.ts +++ b/packages/table-module/__tests__/menu/delete-row.test.ts @@ -1,10 +1,11 @@ -import DeleteRow from '../../src/module/menu/DeleteRow' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { DEL_ROW_SVG } from '../../src/constants/svg' -import * as utils from '../../src/utils' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import DeleteRow from '../../src/module/menu/DeleteRow' +import * as utils from '../../src/utils' jest.mock('../../src/utils', () => ({ filledMatrix: jest.fn(), @@ -16,13 +17,14 @@ function setEditorSelection( selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Delete Row Menu', () => { test('it should create DeleteRow object', () => { const deleteRowMenu = new DeleteRow() + expect(typeof deleteRowMenu).toBe('object') expect(deleteRowMenu.tag).toBe('button') expect(deleteRowMenu.iconSvg).toBe(DEL_ROW_SVG) @@ -32,18 +34,21 @@ describe('Table Module Delete Row Menu', () => { test('it should get empty string if invoke getValue method', () => { const deleteRowMenu = new DeleteRow() const editor = createEditor() + expect(deleteRowMenu.getValue(editor)).toBe('') }) test('it should get falsy value if invoke isActive method', () => { const deleteRowMenu = new DeleteRow() const editor = createEditor() + expect(deleteRowMenu.isActive(editor)).toBeFalsy() }) test('isDisabled should get truthy value if editor selection is null', () => { const deleteRowMenu = new DeleteRow() const editor = createEditor() + editor.selection = null expect(deleteRowMenu.isDisabled(editor)).toBeTruthy() }) @@ -51,6 +56,7 @@ describe('Table Module Delete Row Menu', () => { test('isDisabled should get truthy value if editor selection is collapsed', () => { const deleteRowMenu = new DeleteRow() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) @@ -61,6 +67,7 @@ describe('Table Module Delete Row Menu', () => { test('isDisabled should get truthy value if editor current selected node is not table cell', () => { const deleteRowMenu = new DeleteRow() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -72,6 +79,7 @@ describe('Table Module Delete Row Menu', () => { test('isDisabled should get falsy value if editor current selected node is table cell', () => { const deleteRowMenu = new DeleteRow() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -83,6 +91,7 @@ describe('Table Module Delete Row Menu', () => { test('exec should return directly if menu is disabled', () => { const deleteRowMenu = new DeleteRow() const editor = createEditor() + setEditorSelection(editor, null) expect(deleteRowMenu.exec(editor, '')).toBeUndefined() @@ -113,8 +122,10 @@ describe('Table Module Delete Row Menu', () => { path, ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) const removeNodesFn = jest.fn() + jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) deleteRowMenu.exec(editor, '') @@ -150,32 +161,42 @@ describe('Table Module Delete Row Menu', () => { path, ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockImplementation(() => fn()) mockedUtils.filledMatrix.mockImplementation(() => { return [ [ [ [{ type: 'table-cell', children: [{ text: '' }], isHeader: false }, [0, 0, 0]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], [ [{ type: 'table-cell', children: [{ text: '' }], isHeader: false }, [0, 0, 1]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], ], [ [ [{ type: 'table-cell', children: [{ text: '' }] }, [0, 1, 0]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], [ [{ type: 'table-cell', children: [{ text: '' }] }, [0, 1, 1]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], ], ] }) const removeNodesFn = jest.fn() + jest.spyOn(slate.Transforms, 'removeNodes').mockImplementation(removeNodesFn) deleteRowMenu.exec(editor, '') diff --git a/packages/table-module/__tests__/menu/delete-table.test.ts b/packages/table-module/__tests__/menu/delete-table.test.ts index 77c7ee304..b8111d9c7 100644 --- a/packages/table-module/__tests__/menu/delete-table.test.ts +++ b/packages/table-module/__tests__/menu/delete-table.test.ts @@ -1,21 +1,23 @@ -import DeleteTable from '../../src/module/menu/DeleteTable' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import DeleteTable from '../../src/module/menu/DeleteTable' function setEditorSelection( editor: core.IDomEditor, selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Delete Table Menu', () => { test('it should create DeleteTable object', () => { const deleteTableMenu = new DeleteTable() + expect(typeof deleteTableMenu).toBe('object') expect(deleteTableMenu.tag).toBe('button') expect(deleteTableMenu.title).toBe(locale.tableModule.deleteTable) @@ -24,18 +26,21 @@ describe('Table Module Delete Table Menu', () => { test('it should get empty string if invoke getValue method', () => { const deleteTableMenu = new DeleteTable() const editor = createEditor() + expect(deleteTableMenu.getValue(editor)).toBe('') }) test('it should get falsy value if invoke isActive method', () => { const deleteTableMenu = new DeleteTable() const editor = createEditor() + expect(deleteTableMenu.isActive(editor)).toBeFalsy() }) test('isDisabled should get truthy value if editor selection is null', () => { const deleteTableMenu = new DeleteTable() const editor = createEditor() + editor.selection = null expect(deleteTableMenu.isDisabled(editor)).toBeTruthy() }) @@ -43,6 +48,7 @@ describe('Table Module Delete Table Menu', () => { test('isDisabled should get truthy value if editor current selected node is not table cell', () => { const deleteTableMenu = new DeleteTable() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) @@ -53,6 +59,7 @@ describe('Table Module Delete Table Menu', () => { test('isDisabled should get falsy value if editor current selected node is table cell', () => { const deleteTableMenu = new DeleteTable() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) @@ -63,6 +70,7 @@ describe('Table Module Delete Table Menu', () => { test('exec should return directly if menu is disabled', () => { const deleteTableMenu = new DeleteTable() const editor = createEditor() + setEditorSelection(editor, null) expect(deleteTableMenu.exec(editor, '')).toBeUndefined() diff --git a/packages/table-module/__tests__/menu/full-width.test.ts b/packages/table-module/__tests__/menu/full-width.test.ts index 03c014f04..042f13112 100644 --- a/packages/table-module/__tests__/menu/full-width.test.ts +++ b/packages/table-module/__tests__/menu/full-width.test.ts @@ -1,22 +1,24 @@ -import FullWidth from '../../src/module/menu/FullWidth' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { FULL_WIDTH_SVG } from '../../src/constants/svg' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import FullWidth from '../../src/module/menu/FullWidth' function setEditorSelection( editor: core.IDomEditor, selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Full Width Menu', () => { test('it should create FullWidth object', () => { const fullWidthMenu = new FullWidth() + expect(typeof fullWidthMenu).toBe('object') expect(fullWidthMenu.tag).toBe('button') expect(fullWidthMenu.iconSvg).toBe(FULL_WIDTH_SVG) @@ -31,7 +33,7 @@ describe('Table Module Full Width Menu', () => { expect(fullWidthMenu.getValue(editor)).toBeFalsy() }) - test(`getValue should get truthy value if editor selected table's width is 100%`, () => { + test('getValue should get truthy value if editor selected table\'s width is 100%', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() @@ -44,6 +46,7 @@ describe('Table Module Full Width Menu', () => { test('isActive should get falsy value if editor selected node is not table', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() + jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => null) expect(fullWidthMenu.isActive(editor)).toBeFalsy() @@ -52,6 +55,7 @@ describe('Table Module Full Width Menu', () => { test('isDisabled should get truthy value if editor selection is null', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() + editor.selection = null expect(fullWidthMenu.isDisabled(editor)).toBeTruthy() }) @@ -59,6 +63,7 @@ describe('Table Module Full Width Menu', () => { test('isDisabled should get truthy value if editor selection is collapsed', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) @@ -69,6 +74,7 @@ describe('Table Module Full Width Menu', () => { test('isDisabled should get truthy value if editor current selected node is not table cell', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -80,6 +86,7 @@ describe('Table Module Full Width Menu', () => { test('isDisabled should get falsy value if editor current selected node is table cell', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -91,6 +98,7 @@ describe('Table Module Full Width Menu', () => { test('exec should return directly if menu is disabled', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() + setEditorSelection(editor, null) expect(fullWidthMenu.exec(editor, '')).toBeUndefined() @@ -99,12 +107,14 @@ describe('Table Module Full Width Menu', () => { test('exec should invoke setNodes with props if menu is not disabled', () => { const fullWidthMenu = new FullWidth() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) jest.spyOn(core.DomEditor, 'getSelectedNodeByType').mockImplementation(() => ({} as any)) const fn = jest.fn() + jest.spyOn(slate.Transforms, 'setNodes').mockImplementation(fn) fullWidthMenu.exec(editor, true) diff --git a/packages/table-module/__tests__/menu/insert-col.test.ts b/packages/table-module/__tests__/menu/insert-col.test.ts index 2fa58fca2..669a4a942 100644 --- a/packages/table-module/__tests__/menu/insert-col.test.ts +++ b/packages/table-module/__tests__/menu/insert-col.test.ts @@ -1,10 +1,11 @@ -import InsertCol from '../../src/module/menu/InsertCol' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { ADD_COL_SVG } from '../../src/constants/svg' -import * as utils from '../../src/utils' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import InsertCol from '../../src/module/menu/InsertCol' +import * as utils from '../../src/utils' jest.mock('../../src/utils', () => ({ filledMatrix: jest.fn(), @@ -16,13 +17,14 @@ function setEditorSelection( selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Insert Col Menu', () => { test('it should create InsertCol object', () => { const insertColMenu = new InsertCol() + expect(typeof insertColMenu).toBe('object') expect(insertColMenu.tag).toBe('button') expect(insertColMenu.iconSvg).toBe(ADD_COL_SVG) @@ -32,18 +34,21 @@ describe('Table Module Insert Col Menu', () => { test('it should get empty string if invoke getValue method', () => { const insertColMenu = new InsertCol() const editor = createEditor() + expect(insertColMenu.getValue(editor)).toBe('') }) test('it should get falsy value if invoke isActive method', () => { const insertColMenu = new InsertCol() const editor = createEditor() + expect(insertColMenu.isActive(editor)).toBeFalsy() }) test('isDisabled should get truthy value if editor selection is null', () => { const insertColMenu = new InsertCol() const editor = createEditor() + editor.selection = null expect(insertColMenu.isDisabled(editor)).toBeTruthy() }) @@ -51,6 +56,7 @@ describe('Table Module Insert Col Menu', () => { test('isDisabled should get truthy value if editor selection is collapsed', () => { const insertColMenu = new InsertCol() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) @@ -61,6 +67,7 @@ describe('Table Module Insert Col Menu', () => { test('isDisabled should get truthy value if editor current selected node is not table cell', () => { const insertColMenu = new InsertCol() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -72,6 +79,7 @@ describe('Table Module Insert Col Menu', () => { test('isDisabled should get falsy value if editor current selected node is table cell', () => { const insertColMenu = new InsertCol() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -83,6 +91,7 @@ describe('Table Module Insert Col Menu', () => { test('exec should return directly if menu is disabled', () => { const insertColMenu = new InsertCol() const editor = createEditor() + setEditorSelection(editor, null) expect(insertColMenu.exec(editor, '')).toBeUndefined() @@ -103,6 +112,7 @@ describe('Table Module Insert Col Menu', () => { [0, 1], ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) jest.spyOn(core.DomEditor, 'getParentNode').mockReturnValue(null) @@ -124,6 +134,7 @@ describe('Table Module Insert Col Menu', () => { [0, 1], ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) jest .spyOn(core.DomEditor, 'getParentNode') @@ -148,6 +159,7 @@ describe('Table Module Insert Col Menu', () => { [0, 1], ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) jest .spyOn(core.DomEditor, 'getParentNode') @@ -186,27 +198,36 @@ describe('Table Module Insert Col Menu', () => { jest.spyOn(core.DomEditor, 'findPath').mockReturnValue([0, 1]) const insertNodesFn = jest.fn() + jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(insertNodesFn) mockedUtils.filledMatrix.mockImplementation(() => { return [ [ [ [{ type: 'table-cell', children: [{ text: '' }], isHeader: false }, [0, 0, 0]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], [ [{ type: 'table-cell', children: [{ text: '' }], isHeader: false }, [0, 0, 1]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], ], [ [ [{ type: 'table-cell', children: [{ text: '' }] }, [0, 1, 0]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], [ [{ type: 'table-cell', children: [{ text: '' }] }, [0, 1, 1]], - { rtl: 1, ltr: 1, ttb: 1, btt: 1 }, + { + rtl: 1, ltr: 1, ttb: 1, btt: 1, + }, ], ], ] @@ -217,7 +238,7 @@ describe('Table Module Insert Col Menu', () => { expect(insertNodesFn).toBeCalledWith( editor, { type: 'table-cell', children: [{ text: '' }], hidden: false }, - { at: [0, 0, 0] } + { at: [0, 0, 0] }, ) }) }) diff --git a/packages/table-module/__tests__/menu/insert-row.test.ts b/packages/table-module/__tests__/menu/insert-row.test.ts index e3bdfbb1a..3d1c10e66 100644 --- a/packages/table-module/__tests__/menu/insert-row.test.ts +++ b/packages/table-module/__tests__/menu/insert-row.test.ts @@ -1,22 +1,24 @@ -import InsertRow from '../../src/module/menu/InsertRow' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { ADD_ROW_SVG } from '../../src/constants/svg' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import InsertRow from '../../src/module/menu/InsertRow' function setEditorSelection( editor: core.IDomEditor, selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Insert Row Menu', () => { test('it should create InsertRow object', () => { const insertRowMenu = new InsertRow() + expect(typeof insertRowMenu).toBe('object') expect(insertRowMenu.tag).toBe('button') expect(insertRowMenu.iconSvg).toBe(ADD_ROW_SVG) @@ -26,18 +28,21 @@ describe('Table Module Insert Row Menu', () => { test('it should get empty string if invoke getValue method', () => { const insertRowMenu = new InsertRow() const editor = createEditor() + expect(insertRowMenu.getValue(editor)).toBe('') }) test('it should get falsy value if invoke isActive method', () => { const insertRowMenu = new InsertRow() const editor = createEditor() + expect(insertRowMenu.isActive(editor)).toBeFalsy() }) test('isDisabled should get truthy value if editor selection is null', () => { const insertRowMenu = new InsertRow() const editor = createEditor() + editor.selection = null expect(insertRowMenu.isDisabled(editor)).toBeTruthy() }) @@ -45,6 +50,7 @@ describe('Table Module Insert Row Menu', () => { test('isDisabled should get truthy value if editor selection is collapsed', () => { const insertRowMenu = new InsertRow() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) @@ -55,6 +61,7 @@ describe('Table Module Insert Row Menu', () => { test('isDisabled should get truthy value if editor current selected node is not table cell', () => { const insertRowMenu = new InsertRow() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -66,6 +73,7 @@ describe('Table Module Insert Row Menu', () => { test('isDisabled should get falsy value if editor current selected node is table cell', () => { const insertRowMenu = new InsertRow() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -77,6 +85,7 @@ describe('Table Module Insert Row Menu', () => { test('exec should return directly if menu is disabled', () => { const insertRowMenu = new InsertRow() const editor = createEditor() + setEditorSelection(editor, null) expect(insertRowMenu.exec(editor, '')).toBeUndefined() @@ -109,8 +118,10 @@ describe('Table Module Insert Row Menu', () => { [0, 1], ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) const insertNodesFn = jest.fn() + jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(insertNodesFn) insertRowMenu.exec(editor, '') @@ -135,8 +146,10 @@ describe('Table Module Insert Row Menu', () => { [0, 1], ] as slate.NodeEntry<slate.Element> } + jest.spyOn(slate.Editor, 'nodes').mockReturnValue(fn()) const insertNodesFn = jest.fn() + jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(insertNodesFn) expect(insertRowMenu.exec(editor, '')).toBeUndefined() diff --git a/packages/table-module/__tests__/menu/insert-table.test.ts b/packages/table-module/__tests__/menu/insert-table.test.ts index 763621df3..14e8b13c1 100644 --- a/packages/table-module/__tests__/menu/insert-table.test.ts +++ b/packages/table-module/__tests__/menu/insert-table.test.ts @@ -1,24 +1,26 @@ -import InsertTable from '../../src/module/menu/InsertTable' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' +import { isDOMElement } from '../../../core/src/utils/dom' import { TABLE_SVG } from '../../src/constants/svg' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import InsertTable from '../../src/module/menu/InsertTable' import $ from '../../src/utils/dom' -import { isDOMElement } from './../../../core/src/utils/dom' function setEditorSelection( editor: core.IDomEditor, selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Insert Table Menu', () => { test('it should create InsertTable object', () => { const insertTableMenu = new InsertTable() + expect(typeof insertTableMenu).toBe('object') expect(insertTableMenu.tag).toBe('button') expect(insertTableMenu.iconSvg).toBe(TABLE_SVG) @@ -28,18 +30,21 @@ describe('Table Module Insert Table Menu', () => { test('it should get empty string if invoke getValue method', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + expect(insertTableMenu.getValue(editor)).toBe('') }) test('it should get falsy value if invoke isActive method', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + expect(insertTableMenu.isActive(editor)).toBeFalsy() }) test('isDisabled should get truthy value if editor selection is null', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + editor.selection = null expect(insertTableMenu.isDisabled(editor)).toBeTruthy() }) @@ -47,6 +52,7 @@ describe('Table Module Insert Table Menu', () => { test('isDisabled should get truthy value if editor selection is collapsed', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) @@ -57,6 +63,7 @@ describe('Table Module Insert Table Menu', () => { test('isDisabled should get truthy value if editor current selected node is contains pre node', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -70,6 +77,7 @@ describe('Table Module Insert Table Menu', () => { test('isDisabled should get truthy value if editor current selected node is contains table node', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -83,6 +91,7 @@ describe('Table Module Insert Table Menu', () => { test('isDisabled should get truthy value if editor current selected node is contains void node', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -98,6 +107,7 @@ describe('Table Module Insert Table Menu', () => { test('isDisabled should get falsy value if editor current selected node is valid', () => { const insertTableMenu = new InsertTable() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -124,6 +134,7 @@ describe('Table Module Insert Table Menu', () => { const tdEl = $(tablePanel).find('td')[0] const fn = jest.fn() + jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(fn) tdEl.dispatchEvent( @@ -131,7 +142,7 @@ describe('Table Module Insert Table Menu', () => { view: window, bubbles: true, cancelable: true, - }) + }), ) expect(fn).toBeCalled() @@ -151,7 +162,7 @@ describe('Table Module Insert Table Menu', () => { view: window, bubbles: true, cancelable: true, - }) + }), ) expect(tdEl.className).toBe('active') diff --git a/packages/table-module/__tests__/menu/table-header.test.ts b/packages/table-module/__tests__/menu/table-header.test.ts index cfd9fe4ae..b6236739f 100644 --- a/packages/table-module/__tests__/menu/table-header.test.ts +++ b/packages/table-module/__tests__/menu/table-header.test.ts @@ -1,22 +1,24 @@ -import TableHeader from '../../src/module/menu/TableHeader' +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import createEditor from '../../../../tests/utils/create-editor' import { TABLE_HEADER_SVG } from '../../src/constants/svg' import locale from '../../src/locale/zh-CN' -import * as slate from 'slate' -import * as core from '@wangeditor-next/core' +import TableHeader from '../../src/module/menu/TableHeader' function setEditorSelection( editor: core.IDomEditor, selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } describe('Table Module Table Header Menu', () => { test('it should create TableHeader object', () => { const tableHeaderMenu = new TableHeader() + expect(typeof tableHeaderMenu).toBe('object') expect(tableHeaderMenu.tag).toBe('button') expect(tableHeaderMenu.iconSvg).toBe(TABLE_HEADER_SVG) @@ -44,6 +46,7 @@ describe('Table Module Table Header Menu', () => { test('isDisabled should get truthy value if editor selection is null', () => { const tableHeaderMenu = new TableHeader() const editor = createEditor() + editor.selection = null expect(tableHeaderMenu.isDisabled(editor)).toBeTruthy() }) @@ -51,6 +54,7 @@ describe('Table Module Table Header Menu', () => { test('isDisabled should get truthy value if editor selection is collapsed', () => { const tableHeaderMenu = new TableHeader() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => false) @@ -61,6 +65,7 @@ describe('Table Module Table Header Menu', () => { test('isDisabled should get truthy value if editor current selected node is not table cell', () => { const tableHeaderMenu = new TableHeader() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -72,6 +77,7 @@ describe('Table Module Table Header Menu', () => { test('isDisabled should get falsy value if editor current selected node is table cell', () => { const tableHeaderMenu = new TableHeader() const editor = createEditor() + setEditorSelection(editor) jest.spyOn(slate.Range, 'isCollapsed').mockImplementation(() => true) @@ -83,6 +89,7 @@ describe('Table Module Table Header Menu', () => { test('exec should return directly if menu is disabled', () => { const tableHeaderMenu = new TableHeader() const editor = createEditor() + setEditorSelection(editor, null) expect(tableHeaderMenu.exec(editor, '')).toBeUndefined() @@ -124,6 +131,7 @@ describe('Table Module Table Header Menu', () => { })) const fn = jest.fn() + jest.spyOn(slate.Transforms, 'setNodes').mockImplementation(fn) jest.spyOn(core.DomEditor, 'findPath').mockImplementation(() => [0, 1]) diff --git a/packages/table-module/__tests__/parse-html.test.ts b/packages/table-module/__tests__/parse-html.test.ts index 09fdd1084..1493834c6 100644 --- a/packages/table-module/__tests__/parse-html.test.ts +++ b/packages/table-module/__tests__/parse-html.test.ts @@ -4,13 +4,14 @@ */ import { $ } from 'dom7' + import createEditor from '../../../tests/utils/create-editor' -import { preParseTableHtmlConf } from '../src/module/pre-parse-html' import { parseCellHtmlConf, parseRowHtmlConf, parseTableHtmlConf, } from '../src/module/parse-elem-html' +import { preParseTableHtmlConf } from '../src/module/pre-parse-html' describe('table - pre parse html', () => { it('pre parse', () => { @@ -21,6 +22,7 @@ describe('table - pre parse html', () => { // pre parse const res = preParseTableHtmlConf.preParseHtml($table[0]) + expect(res.outerHTML).toBe('<table><tr><td>hello</td></tr></table>') }) @@ -29,6 +31,7 @@ describe('table - pre parse html', () => { // pre parse const res = preParseTableHtmlConf.preParseHtml(fakeTable[0]) + expect(res.outerHTML).toBe('<div>hello</div>') }) @@ -37,6 +40,7 @@ describe('table - pre parse html', () => { // pre parse const res = preParseTableHtmlConf.preParseHtml(table[0]) + expect(res.outerHTML).toBe('<table><tr><td>hello</td></tr></table>') }) }) @@ -46,6 +50,7 @@ describe('table - parse html', () => { it('table cell', () => { const $cell1 = $('<td>hello&nbsp;world</td>') + expect($cell1[0].matches(parseCellHtmlConf.selector)).toBeTruthy() expect(parseCellHtmlConf.parseElemHtml($cell1[0], [], editor)).toEqual({ type: 'table-cell', @@ -59,6 +64,7 @@ describe('table - parse html', () => { const $cell2 = $('<th style="display:none"></th>') const children = [{ text: 'hello ' }, { text: 'world', bold: true }] + expect($cell2[0].matches(parseCellHtmlConf.selector)).toBeTruthy() expect(parseCellHtmlConf.parseElemHtml($cell2[0], children, editor)).toEqual({ type: 'table-cell', diff --git a/packages/table-module/__tests__/plugin.test.ts b/packages/table-module/__tests__/plugin.test.ts index 1838f3018..e4fe657e1 100644 --- a/packages/table-module/__tests__/plugin.test.ts +++ b/packages/table-module/__tests__/plugin.test.ts @@ -3,11 +3,12 @@ * @author luochao */ -import createEditor from '../../../tests/utils/create-editor' -import withTable from '../src/module/plugin' import * as core from '@wangeditor-next/core' import * as slate from 'slate' +import createEditor from '../../../tests/utils/create-editor' +import withTable from '../src/module/plugin' + describe('TableModule module', () => { describe('module plugin', () => { test('use withTable plugin when break line not split node', () => { @@ -20,6 +21,7 @@ describe('TableModule module', () => { } as slate.Element) const mockFn = jest.fn() + newEditor.insertText = mockFn newEditor.insertBreak() @@ -37,6 +39,7 @@ describe('TableModule module', () => { } as slate.Element) const mockFn = jest.fn() + slate.Editor.insertText = mockFn newEditor.insertData({ getData: () => 'test' } as unknown as DataTransfer) @@ -47,6 +50,7 @@ describe('TableModule module', () => { test('use withTable plugin when insertData should invoke original insertData if selection not in table node', () => { const editor = createEditor() const mockInsertDataFn = jest.fn() + editor.insertData = mockInsertDataFn const newEditor = withTable(editor) diff --git a/packages/table-module/__tests__/render-elem.test.ts b/packages/table-module/__tests__/render-elem.test.ts index 314b691b4..9af4b3a91 100644 --- a/packages/table-module/__tests__/render-elem.test.ts +++ b/packages/table-module/__tests__/render-elem.test.ts @@ -1,5 +1,5 @@ import createEditor from '../../../tests/utils/create-editor' -import { renderTableConf, renderTableCellConf, renderTableRowConf } from '../src/module/render-elem' +import { renderTableCellConf, renderTableConf, renderTableRowConf } from '../src/module/render-elem' describe('table module - render elem', () => { const editor = createEditor() @@ -9,6 +9,7 @@ describe('table module - render elem', () => { const elem = { type: 'table-cell', children: [] } const vnode = renderTableCellConf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('td') }) @@ -27,6 +28,7 @@ describe('table module - render elem', () => { const elem = { type: 'table-row', children: [] } const vnode = renderTableRowConf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('tr') }) @@ -39,10 +41,13 @@ describe('table module - render elem', () => { * 改变了结构,新增外层 DIV */ const observerVnode = renderTableConf.renderElem(elem, null, editor) as any + expect(observerVnode.sel).toBe('div') const containerVnode = observerVnode.children[0] as any + expect(containerVnode.sel).toBe('div') const tableVnode = containerVnode.children[0] as any + expect(tableVnode.sel).toBe('table') }) @@ -52,6 +57,7 @@ describe('table module - render elem', () => { const observerVnode = renderTableConf.renderElem(elem, null, editor) as any const containerVnode = observerVnode.children[0] as any const tableVnode = containerVnode.children[0] as any + expect(tableVnode.data.width).toBe('100%') }) }) diff --git a/packages/table-module/rollup.config.js b/packages/table-module/rollup.config.js index d51fa5cf8..796378269 100644 --- a/packages/table-module/rollup.config.js +++ b/packages/table-module/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorTableModule' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/table-module/src/constants/svg.ts b/packages/table-module/src/constants/svg.ts index 933fba94a..149d3bff9 100644 --- a/packages/table-module/src/constants/svg.ts +++ b/packages/table-module/src/constants/svg.ts @@ -10,69 +10,52 @@ */ // 表格 -export const TABLE_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M0 64v896h1024V64H0z m384 576v-192h256v192h-256z m256 64v192h-256v-192h256z m0-512v192h-256V192h256zM320 192v192H64V192h256z m-256 256h256v192H64v-192z m640 0h256v192h-256v-192z m0-64V192h256v192h-256zM64 704h256v192H64v-192z m640 192v-192h256v192h-256z"></path></svg>' +export const TABLE_SVG = '<svg viewBox="0 0 1024 1024"><path d="M0 64v896h1024V64H0z m384 576v-192h256v192h-256z m256 64v192h-256v-192h256z m0-512v192h-256V192h256zM320 192v192H64V192h256z m-256 256h256v192H64v-192z m640 0h256v192h-256v-192z m0-64V192h256v192h-256zM64 704h256v192H64v-192z m640 192v-192h256v192h-256z"></path></svg>' // 垃圾桶(删除) -export const TRASH_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M826.8032 356.5312c-19.328 0-36.3776 15.6928-36.3776 35.0464v524.2624c0 19.328-16 34.56-35.328 34.56H264.9344c-19.328 0-35.5072-15.3088-35.5072-34.56V390.0416c0-19.328-14.1568-35.0464-33.5104-35.0464s-33.5104 15.6928-33.5104 35.0464V915.712c0 57.9328 44.6208 108.288 102.528 108.288H755.2c57.9328 0 108.0832-50.4576 108.0832-108.288V391.4752c-0.1024-19.2512-17.1264-34.944-36.48-34.944z" p-id="9577"></path><path d="M437.1712 775.7568V390.6048c0-19.328-14.1568-35.0464-33.5104-35.0464s-33.5104 15.616-33.5104 35.0464v385.152c0 19.328 14.1568 35.0464 33.5104 35.0464s33.5104-15.7184 33.5104-35.0464zM649.7024 775.7568V390.6048c0-19.328-17.0496-35.0464-36.3776-35.0464s-36.3776 15.616-36.3776 35.0464v385.152c0 19.328 17.0496 35.0464 36.3776 35.0464s36.3776-15.7184 36.3776-35.0464zM965.0432 217.0368h-174.6176V145.5104c0-57.9328-47.2064-101.76-104.6528-101.76h-350.976c-57.8304 0-105.3952 43.8528-105.3952 101.76v71.5264H54.784c-19.4304 0-35.0464 14.1568-35.0464 33.5104 0 19.328 15.616 33.5104 35.0464 33.5104h910.3616c19.328 0 35.0464-14.1568 35.0464-33.5104 0-19.3536-15.6928-33.5104-35.1488-33.5104z m-247.3728 0H297.3952V145.5104c0-19.328 18.2016-34.7648 37.4272-34.7648h350.976c19.1488 0 31.872 15.1296 31.872 34.7648v71.5264z"></path></svg>' +export const TRASH_SVG = '<svg viewBox="0 0 1024 1024"><path d="M826.8032 356.5312c-19.328 0-36.3776 15.6928-36.3776 35.0464v524.2624c0 19.328-16 34.56-35.328 34.56H264.9344c-19.328 0-35.5072-15.3088-35.5072-34.56V390.0416c0-19.328-14.1568-35.0464-33.5104-35.0464s-33.5104 15.6928-33.5104 35.0464V915.712c0 57.9328 44.6208 108.288 102.528 108.288H755.2c57.9328 0 108.0832-50.4576 108.0832-108.288V391.4752c-0.1024-19.2512-17.1264-34.944-36.48-34.944z" p-id="9577"></path><path d="M437.1712 775.7568V390.6048c0-19.328-14.1568-35.0464-33.5104-35.0464s-33.5104 15.616-33.5104 35.0464v385.152c0 19.328 14.1568 35.0464 33.5104 35.0464s33.5104-15.7184 33.5104-35.0464zM649.7024 775.7568V390.6048c0-19.328-17.0496-35.0464-36.3776-35.0464s-36.3776 15.616-36.3776 35.0464v385.152c0 19.328 17.0496 35.0464 36.3776 35.0464s36.3776-15.7184 36.3776-35.0464zM965.0432 217.0368h-174.6176V145.5104c0-57.9328-47.2064-101.76-104.6528-101.76h-350.976c-57.8304 0-105.3952 43.8528-105.3952 101.76v71.5264H54.784c-19.4304 0-35.0464 14.1568-35.0464 33.5104 0 19.328 15.616 33.5104 35.0464 33.5104h910.3616c19.328 0 35.0464-14.1568 35.0464-33.5104 0-19.3536-15.6928-33.5104-35.1488-33.5104z m-247.3728 0H297.3952V145.5104c0-19.328 18.2016-34.7648 37.4272-34.7648h350.976c19.1488 0 31.872 15.1296 31.872 34.7648v71.5264z"></path></svg>' // 表格 添加行 -export const ADD_ROW_SVG = - '<svg viewBox="0 0 1048 1024"><path d="M707.7888 521.0112h-147.456v-147.456H488.2432v147.456h-147.456v68.8128h147.456v147.456h72.0896v-147.456h147.456zM0 917.504V0h1048.576v917.504H0zM327.68 65.536H65.536v196.608H327.68V65.536z m327.68 0H393.216v196.608h262.144V65.536z m327.68 0h-262.144v196.608h262.144V65.536z m0 258.8672H65.536v462.0288H983.04V324.4032z"></path></svg>' +export const ADD_ROW_SVG = '<svg viewBox="0 0 1048 1024"><path d="M707.7888 521.0112h-147.456v-147.456H488.2432v147.456h-147.456v68.8128h147.456v147.456h72.0896v-147.456h147.456zM0 917.504V0h1048.576v917.504H0zM327.68 65.536H65.536v196.608H327.68V65.536z m327.68 0H393.216v196.608h262.144V65.536z m327.68 0h-262.144v196.608h262.144V65.536z m0 258.8672H65.536v462.0288H983.04V324.4032z"></path></svg>' // 表格 删除行 -export const DEL_ROW_SVG = - '<svg viewBox="0 0 1048 1024"><path d="M907.6736 586.5472L747.1104 425.984l163.84-163.84-78.6432-78.6432-163.84 163.84L507.904 186.7776 429.2608 262.144l163.84 163.84-167.1168 167.1168 78.6432 78.6432 167.1168-167.1168 160.5632 160.5632 75.3664-78.6432zM0 917.504V0h1048.576v917.504H0z m983.04-327.68h-22.9376l-65.536-65.536H983.04V327.68h-91.7504l65.536-65.536h26.2144V65.536H65.536v196.608h317.8496l65.536 65.536H65.536v196.608h380.1088l-65.536 65.536H65.536v196.608H983.04v-196.608z"></path></svg>' +export const DEL_ROW_SVG = '<svg viewBox="0 0 1048 1024"><path d="M907.6736 586.5472L747.1104 425.984l163.84-163.84-78.6432-78.6432-163.84 163.84L507.904 186.7776 429.2608 262.144l163.84 163.84-167.1168 167.1168 78.6432 78.6432 167.1168-167.1168 160.5632 160.5632 75.3664-78.6432zM0 917.504V0h1048.576v917.504H0z m983.04-327.68h-22.9376l-65.536-65.536H983.04V327.68h-91.7504l65.536-65.536h26.2144V65.536H65.536v196.608h317.8496l65.536 65.536H65.536v196.608h380.1088l-65.536 65.536H65.536v196.608H983.04v-196.608z"></path></svg>' // 表格 添加列 -export const ADD_COL_SVG = - '<svg viewBox="0 0 1048 1024"><path d="M327.68 193.3312v186.7776H140.9024v91.7504H327.68v186.7776h88.4736V471.8592h190.0544V380.1088H416.1536V193.3312zM0 917.504V0h1048.576v917.504H0zM655.36 65.536H65.536v720.896H655.36V65.536z m327.68 0h-262.144v196.608h262.144V65.536z m0 262.144h-262.144v196.608h262.144V327.68z m0 262.144h-262.144v196.608h262.144v-196.608z"></path></svg>' +export const ADD_COL_SVG = '<svg viewBox="0 0 1048 1024"><path d="M327.68 193.3312v186.7776H140.9024v91.7504H327.68v186.7776h88.4736V471.8592h190.0544V380.1088H416.1536V193.3312zM0 917.504V0h1048.576v917.504H0zM655.36 65.536H65.536v720.896H655.36V65.536z m327.68 0h-262.144v196.608h262.144V65.536z m0 262.144h-262.144v196.608h262.144V327.68z m0 262.144h-262.144v196.608h262.144v-196.608z"></path></svg>' // 表格 删除列 -export const DEL_COL_SVG = - '<svg viewBox="0 0 1048 1024"><path d="M327.68 510.976L393.216 445.44v-13.1072L327.68 366.7968V510.976z m327.68-78.4384l65.536-65.536V507.904L655.36 442.368v-9.8304z m393.216 484.9664V0H0v917.504h1048.576z m-65.536-131.072h-262.144v-52.4288l-13.1072 13.1072-52.4288-52.4288v91.7504H393.216v-91.7504l-52.4288 52.4288-13.1072-13.1072v52.4288H65.536V65.536H327.68v121.2416l36.0448-36.0448 29.4912 29.4912V62.2592h262.144V180.224l49.152-49.152 16.384 16.384V62.2592h262.144V786.432z m-294.912-108.1344l-160.5632-160.5632-167.1168 167.1168-78.6432-78.6432 167.1168-167.1168L288.3584 278.528l78.6432-78.6432 160.5632 160.5632 163.84-163.84 78.6432 78.6432-163.84 163.84 160.5632 160.5632-78.6432 78.6432z"></path></svg>' +export const DEL_COL_SVG = '<svg viewBox="0 0 1048 1024"><path d="M327.68 510.976L393.216 445.44v-13.1072L327.68 366.7968V510.976z m327.68-78.4384l65.536-65.536V507.904L655.36 442.368v-9.8304z m393.216 484.9664V0H0v917.504h1048.576z m-65.536-131.072h-262.144v-52.4288l-13.1072 13.1072-52.4288-52.4288v91.7504H393.216v-91.7504l-52.4288 52.4288-13.1072-13.1072v52.4288H65.536V65.536H327.68v121.2416l36.0448-36.0448 29.4912 29.4912V62.2592h262.144V180.224l49.152-49.152 16.384 16.384V62.2592h262.144V786.432z m-294.912-108.1344l-160.5632-160.5632-167.1168 167.1168-78.6432-78.6432 167.1168-167.1168L288.3584 278.528l78.6432-78.6432 160.5632 160.5632 163.84-163.84 78.6432 78.6432-163.84 163.84 160.5632 160.5632-78.6432 78.6432z"></path></svg>' // 表头 -export const TABLE_HEADER_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M704 128l-64 0L384 128 320 128 0 128l0 256 0 64 0 192 0 64 0 256 320 0 64 0 256 0 64 0 320 0 0-256 0-64L1024 448 1024 384 1024 128 704 128zM640 640 384 640 384 448l256 0L640 640zM64 448l256 0 0 192L64 640 64 448zM320 896 64 896l0-192 256 0L320 896zM640 896 384 896l0-192 256 0L640 896zM960 896l-256 0 0-192 256 0L960 896zM960 640l-256 0L704 448l256 0L960 640z"></path></svg>' +export const TABLE_HEADER_SVG = '<svg viewBox="0 0 1024 1024"><path d="M704 128l-64 0L384 128 320 128 0 128l0 256 0 64 0 192 0 64 0 256 320 0 64 0 256 0 64 0 320 0 0-256 0-64L1024 448 1024 384 1024 128 704 128zM640 640 384 640 384 448l256 0L640 640zM64 448l256 0 0 192L64 640 64 448zM320 896 64 896l0-192 256 0L320 896zM640 896 384 896l0-192 256 0L640 896zM960 896l-256 0 0-192 256 0L960 896zM960 640l-256 0L704 448l256 0L960 640z"></path></svg>' // 宽度 -export const FULL_WIDTH_SVG = - '<svg viewBox="0 0 1228 1024"><path d="M862.514337 563.200461H404.581995v121.753478a13.311987 13.311987 0 0 1-6.655993 11.468789 10.23999 10.23999 0 0 1-12.083188-1.433599l-204.799795-179.199821a13.721586 13.721586 0 0 1 0-20.479979l204.799795-179.302221a10.23999 10.23999 0 0 1 12.185588-1.535998 13.209587 13.209587 0 0 1 6.553593 11.673588v115.097485h457.932342V319.693504a11.571188 11.571188 0 0 1 18.841582-10.239989l204.799795 179.19982a13.721586 13.721586 0 0 1 0 20.47998l-204.799795 179.199821a10.23999 10.23999 0 0 1-12.185588 1.535998 13.311987 13.311987 0 0 1-6.655994-11.571188V563.200461zM136.499064 14.951409v993.893406a15.257585 15.257585 0 0 1-15.155185 15.052785H15.155185A15.155185 15.155185 0 0 1 0 1008.844815V14.951409a15.257585 15.257585 0 0 1 15.155185-15.052785h106.086294a15.155185 15.155185 0 0 1 15.257585 15.155185zM1228.798771 14.951409v993.893406a15.257585 15.257585 0 0 1-15.155185 15.052785h-106.188693a15.155185 15.155185 0 0 1-15.155185-15.052785V14.951409a15.257585 15.257585 0 0 1 15.155185-15.052785h106.086293A15.155185 15.155185 0 0 1 1228.798771 15.053809z"></path></svg>' +export const FULL_WIDTH_SVG = '<svg viewBox="0 0 1228 1024"><path d="M862.514337 563.200461H404.581995v121.753478a13.311987 13.311987 0 0 1-6.655993 11.468789 10.23999 10.23999 0 0 1-12.083188-1.433599l-204.799795-179.199821a13.721586 13.721586 0 0 1 0-20.479979l204.799795-179.302221a10.23999 10.23999 0 0 1 12.185588-1.535998 13.209587 13.209587 0 0 1 6.553593 11.673588v115.097485h457.932342V319.693504a11.571188 11.571188 0 0 1 18.841582-10.239989l204.799795 179.19982a13.721586 13.721586 0 0 1 0 20.47998l-204.799795 179.199821a10.23999 10.23999 0 0 1-12.185588 1.535998 13.311987 13.311987 0 0 1-6.655994-11.571188V563.200461zM136.499064 14.951409v993.893406a15.257585 15.257585 0 0 1-15.155185 15.052785H15.155185A15.155185 15.155185 0 0 1 0 1008.844815V14.951409a15.257585 15.257585 0 0 1 15.155185-15.052785h106.086294a15.155185 15.155185 0 0 1 15.257585 15.155185zM1228.798771 14.951409v993.893406a15.257585 15.257585 0 0 1-15.155185 15.052785h-106.188693a15.155185 15.155185 0 0 1-15.155185-15.052785V14.951409a15.257585 15.257585 0 0 1 15.155185-15.052785h106.086293A15.155185 15.155185 0 0 1 1228.798771 15.053809z"></path></svg>' // 合并单元格 -export const MERGE_CELL_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M482.2 508.4 331.3 389c-3-2.4-7.3-.2-7.3 3.6V478H184V184h204v128c0 2.2 1.8 4 4 4h60c2.2 0 4-1.8 4-4V144c0-15.5-12.5-28-28-28H144c-15.5 0-28 12.5-28 28v736c0 15.5 12.5 28 28 28h284c15.5 0 28-12.5 28-28V712c0-2.2-1.8-4-4-4h-60c-2.2 0-4 1.8-4 4v128H184V546h140v85.4c0 3.8 4.4 6 7.3 3.6l150.9-119.4c2.4-1.8 2.4-5.4 0-7.2zM880 116H596c-15.5 0-28 12.5-28 28v168c0 2.2 1.8 4 4 4h60c2.2 0 4-1.8 4-4V184h204v294H700v-85.4c0-3.8-4.3-6-7.3-3.6l-151 119.4c-2.3 1.8-2.3 5.3 0 7.1l151 119.5c2.9 2.3 7.3.2 7.3-3.6V546h140v294H636V712c0-2.2-1.8-4-4-4h-60c-2.2 0-4 1.8-4 4v168c0 15.5 12.5 28 28 28h284c15.5 0 28-12.5 28-28V144c0-15.5-12.5-28-28-28z"/></svg>' +export const MERGE_CELL_SVG = '<svg viewBox="0 0 1024 1024"><path d="M482.2 508.4 331.3 389c-3-2.4-7.3-.2-7.3 3.6V478H184V184h204v128c0 2.2 1.8 4 4 4h60c2.2 0 4-1.8 4-4V144c0-15.5-12.5-28-28-28H144c-15.5 0-28 12.5-28 28v736c0 15.5 12.5 28 28 28h284c15.5 0 28-12.5 28-28V712c0-2.2-1.8-4-4-4h-60c-2.2 0-4 1.8-4 4v128H184V546h140v85.4c0 3.8 4.4 6 7.3 3.6l150.9-119.4c2.4-1.8 2.4-5.4 0-7.2zM880 116H596c-15.5 0-28 12.5-28 28v168c0 2.2 1.8 4 4 4h60c2.2 0 4-1.8 4-4V184h204v294H700v-85.4c0-3.8-4.3-6-7.3-3.6l-151 119.4c-2.3 1.8-2.3 5.3 0 7.1l151 119.5c2.9 2.3 7.3.2 7.3-3.6V546h140v294H636V712c0-2.2-1.8-4-4-4h-60c-2.2 0-4 1.8-4 4v168c0 15.5 12.5 28 28 28h284c15.5 0 28-12.5 28-28V144c0-15.5-12.5-28-28-28z"/></svg>' // 拆分单元格 -export const SPLIT_CELL_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M362.667 494.933v53.334l25.6-25.6zm0-241.066L460.8 352V78.933H57.6v98.134h305.067zm0 535.466v57.6H57.6v98.134h403.2V691.2zM661.333 494.933v53.334l-25.6-25.6zm0-241.066L563.2 352V78.933h403.2v98.134H661.333zm0 535.466v57.6H966.4v98.134H563.2V691.2z"/><path d="M753.067 341.333 693.333 281.6 512 460.8 330.667 281.6l-59.734 59.733 181.334 181.334L270.933 704l59.734 59.733L512 582.4l181.333 181.333L753.067 704 571.733 522.667z"/></svg>' +export const SPLIT_CELL_SVG = '<svg viewBox="0 0 1024 1024"><path d="M362.667 494.933v53.334l25.6-25.6zm0-241.066L460.8 352V78.933H57.6v98.134h305.067zm0 535.466v57.6H57.6v98.134h403.2V691.2zM661.333 494.933v53.334l-25.6-25.6zm0-241.066L563.2 352V78.933h403.2v98.134H661.333zm0 535.466v57.6H966.4v98.134H563.2V691.2z"/><path d="M753.067 341.333 693.333 281.6 512 460.8 330.667 281.6l-59.734 59.733 181.334 181.334L270.933 704l59.734 59.733L512 582.4l181.333 181.333L753.067 704 571.733 522.667z"/></svg>' // 表格属性 -export const TABLE_PROPERTY_SVG = - '<svg viewBox="0 0 20 20"><path d="M8 2v5h4V2h1v5h5v1h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5H7v-5H2v-1h5V8H2V7h5V2h1zm4 6H8v4h4V8z" opacity=".6"/><path d="m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43L15.5 11.5zM17 1a2 2 0 0 1 2 2v9.475l-.85-.124-.857-1.736a2.048 2.048 0 0 0-.292-.44L17 3H3v14h7.808l.402.392L10.935 19H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h14z"/></svg>' +export const TABLE_PROPERTY_SVG = '<svg viewBox="0 0 20 20"><path d="M8 2v5h4V2h1v5h5v1h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5H7v-5H2v-1h5V8H2V7h5V2h1zm4 6H8v4h4V8z" opacity=".6"/><path d="m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43L15.5 11.5zM17 1a2 2 0 0 1 2 2v9.475l-.85-.124-.857-1.736a2.048 2.048 0 0 0-.292-.44L17 3H3v14h7.808l.402.392L10.935 19H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h14z"/></svg>' // 单元格属性 -export const CELL_PROPERTY_SVG = - '<svg viewBox="0 0 20 20"><path d="m11.105 18-.17 1H2.5A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1h15A1.5 1.5 0 0 1 19 2.5v9.975l-.85-.124-.15-.302V8h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5h3.105zM2 12h5V8H2v4zm10-4H8v4h4V8zM2 2v5h5V2H2zm0 16h5v-5H2v5zM13 7h5V2h-5v5zM8 2v5h4V2H8z" opacity=".6"/><path d="m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43L15.5 11.5zM13 6a1 1 0 0 1 1 1v3.172a2.047 2.047 0 0 0-.293.443l-.858 1.736-1.916.28-.151.027A1.976 1.976 0 0 0 9.315 14H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h6zm-1 2H8v4h4V8z"/></svg>' +export const CELL_PROPERTY_SVG = '<svg viewBox="0 0 20 20"><path d="m11.105 18-.17 1H2.5A1.5 1.5 0 0 1 1 17.5v-15A1.5 1.5 0 0 1 2.5 1h15A1.5 1.5 0 0 1 19 2.5v9.975l-.85-.124-.15-.302V8h-5v4h.021l-.172.351-1.916.28-.151.027c-.287.063-.54.182-.755.341L8 13v5h3.105zM2 12h5V8H2v4zm10-4H8v4h4V8zM2 2v5h5V2H2zm0 16h5v-5H2v5zM13 7h5V2h-5v5zM8 2v5h4V2H8z" opacity=".6"/><path d="m15.5 11.5 1.323 2.68 2.957.43-2.14 2.085.505 2.946L15.5 18.25l-2.645 1.39.505-2.945-2.14-2.086 2.957-.43L15.5 11.5zM13 6a1 1 0 0 1 1 1v3.172a2.047 2.047 0 0 0-.293.443l-.858 1.736-1.916.28-.151.027A1.976 1.976 0 0 0 9.315 14H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h6zm-1 2H8v4h4V8z"/></svg>' // 左对齐 -export const JUSTIFY_LEFT_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' +export const JUSTIFY_LEFT_SVG = '<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' // 右对齐 -export const JUSTIFY_RIGHT_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M972.8 793.6v102.4H256v-102.4h716.8z m0-230.4v102.4H51.2v-102.4h921.6z m0-230.4v102.4H256v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' +export const JUSTIFY_RIGHT_SVG = '<svg viewBox="0 0 1024 1024"><path d="M972.8 793.6v102.4H256v-102.4h716.8z m0-230.4v102.4H51.2v-102.4h921.6z m0-230.4v102.4H256v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' // 居中对齐 -export const JUSTIFY_CENTER_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M870.4 793.6v102.4H153.6v-102.4h716.8z m102.4-230.4v102.4H51.2v-102.4h921.6z m-102.4-230.4v102.4H153.6v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' +export const JUSTIFY_CENTER_SVG = '<svg viewBox="0 0 1024 1024"><path d="M870.4 793.6v102.4H153.6v-102.4h716.8z m102.4-230.4v102.4H51.2v-102.4h921.6z m-102.4-230.4v102.4H153.6v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>' // 两端对齐 -export const JUSTIFY_JUSTIFY_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m0 192h1024v128H0z m0 192h1024v128H0z m0 192h1024v128H0z m0 192h1024v128H0z"></path></svg>' +export const JUSTIFY_JUSTIFY_SVG = '<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m0 192h1024v128H0z m0 192h1024v128H0z m0 192h1024v128H0z m0 192h1024v128H0z"></path></svg>' // 清空(颜色) -export const CLEAN_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M236.8 128L896 787.2V128H236.8z m614.4 704L192 172.8V832h659.2zM192 64h704c38.4 0 64 25.6 64 64v704c0 38.4-25.6 64-64 64H192c-38.4 0-64-25.6-64-64V128c0-38.4 25.6-64 64-64z"></path></svg>' +export const CLEAN_SVG = '<svg viewBox="0 0 1024 1024"><path d="M236.8 128L896 787.2V128H236.8z m614.4 704L192 172.8V832h659.2zM192 64h704c38.4 0 64 25.6 64 64v704c0 38.4-25.6 64-64 64H192c-38.4 0-64-25.6-64-64V128c0-38.4 25.6-64 64-64z"></path></svg>' diff --git a/packages/table-module/src/index.ts b/packages/table-module/src/index.ts index 1fec86c6c..868e0324a 100644 --- a/packages/table-module/src/index.ts +++ b/packages/table-module/src/index.ts @@ -4,9 +4,9 @@ */ import './assets/index.less' - // 配置多语言 import './locale/index' import wangEditorTableModule from './module/index' + export default wangEditorTableModule diff --git a/packages/table-module/src/locale/index.ts b/packages/table-module/src/locale/index.ts index f3b7049e9..120f32884 100644 --- a/packages/table-module/src/locale/index.ts +++ b/packages/table-module/src/locale/index.ts @@ -4,6 +4,7 @@ */ import { i18nAddResources } from '@wangeditor-next/core' + import enResources from './en' import zhResources from './zh-CN' diff --git a/packages/table-module/src/module/column-resize.ts b/packages/table-module/src/module/column-resize.ts index 3aedd51d0..50557d359 100644 --- a/packages/table-module/src/module/column-resize.ts +++ b/packages/table-module/src/module/column-resize.ts @@ -1,18 +1,19 @@ -import throttle from 'lodash.throttle' -import { Element as SlateElement, Transforms, Editor } from 'slate' import { IDomEditor, isHTMLElememt } from '@wangeditor-next/core' -import { TableElement } from './custom-types' +import throttle from 'lodash.throttle' +import { Editor, Element as SlateElement, Transforms } from 'slate' + import { isOfType } from '../utils' import $ from '../utils/dom' +import { TableElement } from './custom-types' -/*** +/** * * 计算 cell border 距离 table 左侧距离 */ function getCumulativeWidths(columnWidths: number[]) { - let cumulativeWidths: number[] = [] + const cumulativeWidths: number[] = [] let totalWidth = 0 - for (let width of columnWidths) { + for (const width of columnWidths) { totalWidth += width cumulativeWidths.push(totalWidth) } @@ -20,14 +21,14 @@ function getCumulativeWidths(columnWidths: number[]) { return cumulativeWidths } -/*** +/** * * 用于计算拖动 cell 时,cell 宽度变化的比例 */ export function getColumnWidthRatios(columnWidths: number[]) { - let columnWidthsRatio: number[] = [] - let totalWidth = columnWidths.reduce((a, b) => a + b, 0) + const columnWidthsRatio: number[] = [] + const totalWidth = columnWidths.reduce((a, b) => a + b, 0) - for (let width of columnWidths) { + for (const width of columnWidths) { columnWidthsRatio.push(width / totalWidth) } @@ -39,9 +40,11 @@ export function getColumnWidthRatios(columnWidths: number[]) { * ResizeObserver 需要即时释放,以免引起内存泄露 */ let resizeObserver: ResizeObserver | null = null + export function observerTableResize(editor: IDomEditor, elm: Node | undefined) { if (isHTMLElememt(elm)) { const table = elm.querySelector('table') + if (table) { resizeObserver = new ResizeObserver(([{ contentRect }]) => { // 当非拖动引起的宽度变化,需要调整 columnWidths @@ -51,7 +54,7 @@ export function observerTableResize(editor: IDomEditor, elm: Node | undefined) { scrollWidth: contentRect.width, height: contentRect.height, } as TableElement, - { mode: 'highest' } + { mode: 'highest' }, ) }) resizeObserver.observe(table) @@ -74,16 +77,18 @@ let clientXWhenMouseDown = 0 let cellWidthWhenMouseDown = 0 let editorWhenMouseDown: IDomEditor | null = null const $window = $(window) + $window.on('mousedown', onMouseDown) function onMouseDown(event: Event) { const elem = event.target as HTMLElement // 判断是否为光标选区行为,对列宽变更行为进行过滤 // console.log('onMouseDown', elem) + if (elem.closest('[data-block-type="table-cell"]')) { isSelectionOperation = true } else if (elem.tagName == 'DIV' && elem.closest('.column-resizer-item')) { - if (editorWhenMouseDown == null) return + if (editorWhenMouseDown == null) { return } const [[elemNode]] = Editor.nodes(editorWhenMouseDown, { match: isOfType(editorWhenMouseDown, 'table'), @@ -93,6 +98,7 @@ function onMouseDown(event: Event) { // 记录必要信息 isMouseDownForResize = true const { clientX } = event as MouseEvent + clientXWhenMouseDown = clientX cellWidthWhenMouseDown = columnWidths[resizingIndex] document.body.style.cursor = 'col-resize' @@ -103,15 +109,16 @@ function onMouseDown(event: Event) { $window.on('mouseup', onMouseUp) } -const onMouseMove = throttle(function (event: Event) { - if (!isMouseDownForResize) return - if (editorWhenMouseDown == null) return +const onMouseMove = throttle((event: Event) => { + if (!isMouseDownForResize) { return } + if (editorWhenMouseDown == null) { return } event.preventDefault() const { clientX } = event as MouseEvent let newWith = cellWidthWhenMouseDown + (clientX - clientXWhenMouseDown) // 计算新宽度 + newWith = Math.floor(newWith * 100) / 100 // 保留小数点后两位 - if (newWith < 30) newWith = 30 // 最小宽度 + if (newWith < 30) { newWith = 30 } // 最小宽度 const [[elemNode]] = Editor.nodes(editorWhenMouseDown, { match: isOfType(editorWhenMouseDown, 'table'), @@ -123,11 +130,13 @@ const onMouseMove = throttle(function (event: Event) { // 如果拖动引起的宽度超过容器宽度,则不调整 const containerElement = document.querySelector('.table-container') + if (containerElement && remainWidth + newWith > containerElement.clientWidth) { return } const adjustColumnWidths = [...columnWidths].map(width => Math.floor(width)) + adjustColumnWidths[resizingIndex] = newWith // 这是宽度 @@ -155,10 +164,10 @@ export function handleCellBorderVisible( editor: IDomEditor, elemNode: SlateElement, e: MouseEvent, - scrollWidth: number + scrollWidth: number, ) { - if (editor.isDisabled()) return - if (isSelectionOperation || isMouseDownForResize) return + if (editor.isDisabled()) { return } + if (isSelectionOperation || isMouseDownForResize) { return } const { width: tableWidth = 'auto', @@ -170,6 +179,7 @@ export function handleCellBorderVisible( // Cell Border 宽度为 10px const { clientX, target } = e // 当单元格合并的时候,鼠标在 cell 中间,则不显示 cell border + if (isHTMLElememt(target)) { const rect = target.getBoundingClientRect() @@ -178,7 +188,7 @@ export function handleCellBorderVisible( Transforms.setNodes( editor, { isHoverCellBorder: false, resizingIndex: -1 } as TableElement, - { mode: 'highest' } + { mode: 'highest' }, ) } return @@ -186,28 +196,28 @@ export function handleCellBorderVisible( } if (isHTMLElememt(target)) { const parent = target.closest('.table') + if (parent) { const { clientX } = e const rect = parent.getBoundingClientRect() - const widths = - tableWidth === '100%' - ? getColumnWidthRatios(columnWidths).map(v => v * scrollWidth) - : columnWidths + const widths = tableWidth === '100%' + ? getColumnWidthRatios(columnWidths).map(v => v * scrollWidth) + : columnWidths - let cumulativeWidths = getCumulativeWidths(widths) + const cumulativeWidths = getCumulativeWidths(widths) // 鼠标移动时,计算当前鼠标位置,判断在哪个 Cell border 上 for (let i = 0; i < cumulativeWidths.length; i++) { if ( - clientX - rect.x >= cumulativeWidths[i] - 5 && - clientX - rect.x < cumulativeWidths[i] + 5 + clientX - rect.x >= cumulativeWidths[i] - 5 + && clientX - rect.x < cumulativeWidths[i] + 5 ) { // 节流,防止多次引起Transforms.setNodes重绘 - if (resizingIndex == i) return + if (resizingIndex == i) { return } Transforms.setNodes( editor, { isHoverCellBorder: true, resizingIndex: i } as TableElement, - { mode: 'highest' } + { mode: 'highest' }, ) return } @@ -236,6 +246,6 @@ export function handleCellBorderHighlight(editor: IDomEditor, e: MouseEvent) { } export function handleCellBorderMouseDown(editor: IDomEditor, elemNode: SlateElement) { - if (isMouseDownForResize) return // 此时正在修改列宽 + if (isMouseDownForResize) { return } // 此时正在修改列宽 editorWhenMouseDown = editor } diff --git a/packages/table-module/src/module/custom-types.ts b/packages/table-module/src/module/custom-types.ts index 7ca8ebb38..488513f46 100644 --- a/packages/table-module/src/module/custom-types.ts +++ b/packages/table-module/src/module/custom-types.ts @@ -5,7 +5,7 @@ import { Text } from 'slate' -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts export type TableCellProperty = { /** 用于设置单元格属性 */ backgroundColor?: string // 背景色 diff --git a/packages/table-module/src/module/elem-to-html.ts b/packages/table-module/src/module/elem-to-html.ts index b43ef2c37..ed3cf5e50 100644 --- a/packages/table-module/src/module/elem-to-html.ts +++ b/packages/table-module/src/module/elem-to-html.ts @@ -4,17 +4,18 @@ */ import { Element } from 'slate' -import { TableCellElement, TableRowElement, TableElement } from './custom-types' + +import { TableCellElement, TableElement, TableRowElement } from './custom-types' function tableToHtml(elemNode: Element, childrenHtml: string): string { const { width = 'auto', columnWidths, height = 'auto' } = elemNode as TableElement - let cols = columnWidths + const cols = columnWidths ?.map(width => { return `<col width=${width}></col>` }) .join('') - const colgroupStr = cols ? '<colgroup contentEditable="false">' + cols + '</colgroup>' : '' + const colgroupStr = cols ? `<colgroup contentEditable="false">${cols}</colgroup>` : '' return `<table style="width: ${width};table-layout: fixed;height:${height}">${colgroupStr}<tbody>${childrenHtml}</tbody></table>` } @@ -33,6 +34,7 @@ function tableCellToHtml(cellNode: Element, childrenHtml: string): string { } = cellNode as TableCellElement const tag = isHeader ? 'th' : 'td' const style = hidden ? 'display:none' : '' + return `<${tag} colSpan="${colSpan}" rowSpan="${rowSpan}" width="${width}" style="${style}">${childrenHtml}</${tag}>` } diff --git a/packages/table-module/src/module/helpers.ts b/packages/table-module/src/module/helpers.ts index 95f43cd5a..debbe79df 100644 --- a/packages/table-module/src/module/helpers.ts +++ b/packages/table-module/src/module/helpers.ts @@ -4,7 +4,8 @@ */ import { DomEditor, IDomEditor } from '@wangeditor-next/core' -import { TableElement, TableCellElement } from './custom-types' + +import { TableCellElement, TableElement } from './custom-types' /** * 获取第一行所有 cells @@ -12,9 +13,11 @@ import { TableElement, TableCellElement } from './custom-types' */ export function getFirstRowCells(tableNode: TableElement): TableCellElement[] { const rows = tableNode.children || [] // 所有行 - if (rows.length === 0) return [] + + if (rows.length === 0) { return [] } const firstRow = rows[0] || {} // 第一行 const cells = firstRow.children || [] // 第一行所有 cell + return cells } @@ -24,6 +27,7 @@ export function getFirstRowCells(tableNode: TableElement): TableCellElement[] { */ export function isTableWithHeader(tableNode: TableElement): boolean { const firstRowCells = getFirstRowCells(tableNode) + return firstRowCells.every(cell => !!cell.isHeader) } @@ -34,10 +38,13 @@ export function isTableWithHeader(tableNode: TableElement): boolean { */ export function isCellInFirstRow(editor: IDomEditor, cellNode: TableCellElement): boolean { const rowNode = DomEditor.getParentNode(editor, cellNode) - if (rowNode == null) return false + + if (rowNode == null) { return false } const tableNode = DomEditor.getParentNode(editor, rowNode) - if (tableNode == null) return false + + if (tableNode == null) { return false } const firstRowCells = getFirstRowCells(tableNode as TableElement) + return firstRowCells.some(c => c === cellNode) } diff --git a/packages/table-module/src/module/index.ts b/packages/table-module/src/module/index.ts index f0d95d516..e3b9a3752 100644 --- a/packages/table-module/src/module/index.ts +++ b/packages/table-module/src/module/index.ts @@ -4,28 +4,29 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import withTable from './plugin' -import { renderTableConf, renderTableRowConf, renderTableCellConf } from './render-elem/index' -import { tableToHtmlConf, tableRowToHtmlConf, tableCellToHtmlConf } from './elem-to-html' -import { preParseTableHtmlConf } from './pre-parse-html' -import { renderStyle } from './render-style' -import { styleToHtml } from './style-to-html' -import { parseStyleHtml } from './parse-style-html' -import { parseCellHtmlConf, parseRowHtmlConf, parseTableHtmlConf } from './parse-elem-html' + +import { tableCellToHtmlConf, tableRowToHtmlConf, tableToHtmlConf } from './elem-to-html' import { - insertTableMenuConf, + deleteTableColConf, deleteTableMenuConf, - insertTableRowConf, deleteTableRowConf, insertTableColConf, - deleteTableColConf, - tableHeaderMenuConf, - tableFullWidthMenuConf, + insertTableMenuConf, + insertTableRowConf, mergeTableCellConf, - splitTableCellConf, - setTablePropertyConf, setTableCellPropertyConf, + setTablePropertyConf, + splitTableCellConf, + tableFullWidthMenuConf, + tableHeaderMenuConf, } from './menu/index' +import { parseCellHtmlConf, parseRowHtmlConf, parseTableHtmlConf } from './parse-elem-html' +import { parseStyleHtml } from './parse-style-html' +import withTable from './plugin' +import { preParseTableHtmlConf } from './pre-parse-html' +import { renderTableCellConf, renderTableConf, renderTableRowConf } from './render-elem/index' +import { renderStyle } from './render-style' +import { styleToHtml } from './style-to-html' const table: Partial<IModuleConf> = { renderStyle, diff --git a/packages/table-module/src/module/menu/CellProperty.ts b/packages/table-module/src/module/menu/CellProperty.ts index 3be3ac34b..e0abc42c3 100644 --- a/packages/table-module/src/module/menu/CellProperty.ts +++ b/packages/table-module/src/module/menu/CellProperty.ts @@ -1,20 +1,26 @@ -import { Editor } from 'slate' import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' +import { Editor } from 'slate' + import { CELL_PROPERTY_SVG } from '../../constants/svg' import { isOfType } from '../../utils' import TableProperty from './TableProperty' class CellProperty extends TableProperty implements IButtonMenu { readonly title = t('tableModule.cellProperty') + readonly iconSvg = CELL_PROPERTY_SVG + readonly tag = 'button' + readonly showModal = true + readonly modalWidth = 300 getModalContentNode(editor: IDomEditor) { const [node] = Editor.nodes(editor, { match: isOfType(editor, 'td'), }) + return node } } diff --git a/packages/table-module/src/module/menu/DeleteCol.ts b/packages/table-module/src/module/menu/DeleteCol.ts index 45b238271..6580fd1b1 100644 --- a/packages/table-module/src/module/menu/DeleteCol.ts +++ b/packages/table-module/src/module/menu/DeleteCol.ts @@ -3,15 +3,22 @@ * @author wangfupeng */ -import { Editor, Transforms, Range, Node, Path } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { + Editor, Node, Path, Range, Transforms, +} from 'slate' + import { DEL_COL_SVG } from '../../constants/svg' import { filledMatrix } from '../../utils' import { TableCellElement, TableElement } from '../custom-types' class DeleteCol implements IButtonMenu { readonly title = t('tableModule.deleteCol') + readonly iconSvg = DEL_COL_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -26,10 +33,12 @@ class DeleteCol implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } const cellNode = DomEditor.getSelectedNodeByType(editor, 'table-cell') + if (cellNode == null) { // 选区未处于 table cell node ,则禁用 return true @@ -38,7 +47,7 @@ class DeleteCol implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } const [cellEntry] = Editor.nodes(editor, { match: n => DomEditor.checkNodeType(n, 'table-cell'), @@ -49,6 +58,7 @@ class DeleteCol implements IButtonMenu { // 如果只有一列,则删除整个表格 const rowNode = DomEditor.getParentNode(editor, selectedCellNode) const colLength = rowNode?.children.length || 0 + if (!rowNode || colLength <= 1) { Transforms.removeNodes(editor, { mode: 'highest' }) // 删除整个表格 return @@ -57,10 +67,12 @@ class DeleteCol implements IButtonMenu { // ------------------------- 不只有 1 列,则继续 ------------------------- const tableNode = DomEditor.getParentNode(editor, rowNode) - if (tableNode == null) return + + if (tableNode == null) { return } const matrix = filledMatrix(editor) let tdIndex = 0 + out: for (let x = 0; x < matrix.length; x++) { for (let y = 0; y < matrix[x].length; y++) { const [[, path]] = matrix[x][y] @@ -87,10 +99,11 @@ class DeleteCol implements IButtonMenu { rowSpan, colSpan: Math.max(colSpan - 1, 1), }, - { at: path } + { at: path }, ) } else { const [[, rightPath]] = matrix[x][tdIndex + 1] + Transforms.setNodes<TableCellElement>( editor, { @@ -98,7 +111,7 @@ class DeleteCol implements IButtonMenu { colSpan: colSpan - 1, hidden: false, }, - { at: rightPath } + { at: rightPath }, ) // 移动单元格 文本、图片等元素 for (const [, childPath] of Node.children(editor, path, { reverse: true })) { @@ -114,6 +127,7 @@ class DeleteCol implements IButtonMenu { // 挨个删除 cell for (let x = 0; x < matrix.length; x++) { const [[, path]] = matrix[x][tdIndex] + Transforms.removeNodes(editor, { at: path }) } @@ -122,10 +136,12 @@ class DeleteCol implements IButtonMenu { match: n => DomEditor.checkNodeType(n, 'table'), universal: true, }) + if (tableEntry) { const [elemNode, tablePath] = tableEntry const { columnWidths = [] } = elemNode as TableElement const adjustColumnWidths = [...columnWidths] + adjustColumnWidths.splice(tdIndex, 1) Transforms.setNodes(editor, { columnWidths: adjustColumnWidths } as TableElement, { diff --git a/packages/table-module/src/module/menu/DeleteRow.ts b/packages/table-module/src/module/menu/DeleteRow.ts index 029e418be..c3b1d34af 100644 --- a/packages/table-module/src/module/menu/DeleteRow.ts +++ b/packages/table-module/src/module/menu/DeleteRow.ts @@ -3,15 +3,22 @@ * @author wangfupeng */ -import { Editor, Transforms, Range, Path, Node } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { + Editor, Node, Path, Range, Transforms, +} from 'slate' + import { DEL_ROW_SVG } from '../../constants/svg' import { filledMatrix } from '../../utils' import { TableCellElement } from '../custom-types' class DeleteRow implements IButtonMenu { readonly title = t('tableModule.deleteRow') + readonly iconSvg = DEL_ROW_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -26,10 +33,12 @@ class DeleteRow implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } const rowNode = DomEditor.getSelectedNodeByType(editor, 'table-row') + if (rowNode == null) { // 选区未处于 table row node ,则禁用 return true @@ -38,7 +47,7 @@ class DeleteRow implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } const [rowEntry] = Editor.nodes(editor, { match: n => DomEditor.checkNodeType(n, 'table-row'), @@ -48,6 +57,7 @@ class DeleteRow implements IButtonMenu { const tableNode = DomEditor.getParentNode(editor, rowNode) const rowsLength = tableNode?.children.length || 0 + if (rowsLength <= 1) { // row 只有一行,则删掉整个表格 Transforms.removeNodes(editor, { mode: 'highest' }) @@ -62,9 +72,11 @@ class DeleteRow implements IButtonMenu { const [, cellPath] = cellEntry const matrix = filledMatrix(editor) let trIndex = 0 + outer: for (let x = 0; x < matrix.length; x++) { for (let y = 0; y < matrix[x].length; y++) { const [[, path]] = matrix[x][y] + if (!Path.equals(cellPath, path)) { continue } @@ -82,6 +94,7 @@ class DeleteRow implements IButtonMenu { // 找到显示中 rowSpan 节点 const [[{ rowSpan = 1, colSpan = 1 }, path]] = matrix[trIndex - (ttb - 1)][y] // 如果当前选中节点为隐藏节点,则向上寻找处理 rowSpan 逻辑 + if (hidden) { Transforms.setNodes<TableCellElement>( editor, @@ -89,10 +102,11 @@ class DeleteRow implements IButtonMenu { rowSpan: Math.max(rowSpan - 1, 1), colSpan, }, - { at: path } + { at: path }, ) } else { const [[, belowPath]] = matrix[trIndex + 1][y] + Transforms.setNodes<TableCellElement>( editor, { @@ -100,7 +114,7 @@ class DeleteRow implements IButtonMenu { colSpan, hidden: false, }, - { at: belowPath } + { at: belowPath }, ) // 移动单元格 文本、图片等元素 diff --git a/packages/table-module/src/module/menu/DeleteTable.ts b/packages/table-module/src/module/menu/DeleteTable.ts index c47736619..d4fc75f67 100644 --- a/packages/table-module/src/module/menu/DeleteTable.ts +++ b/packages/table-module/src/module/menu/DeleteTable.ts @@ -3,13 +3,18 @@ * @author wangfupeng */ +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' import { Transforms } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' + import { TRASH_SVG } from '../../constants/svg' class DeleteTable implements IButtonMenu { readonly title = t('tableModule.deleteTable') + readonly iconSvg = TRASH_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -23,9 +28,10 @@ class DeleteTable implements IButtonMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') + if (tableNode == null) { // 选区未处于 table node ,则禁用 return true @@ -34,7 +40,7 @@ class DeleteTable implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } // 删除表格 Transforms.removeNodes(editor, { mode: 'highest' }) diff --git a/packages/table-module/src/module/menu/FullWidth.ts b/packages/table-module/src/module/menu/FullWidth.ts index 2e3495d69..7584b9d3f 100644 --- a/packages/table-module/src/module/menu/FullWidth.ts +++ b/packages/table-module/src/module/menu/FullWidth.ts @@ -3,20 +3,26 @@ * @author wangfupeng */ -import { Transforms, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { Range, Transforms } from 'slate' + import { FULL_WIDTH_SVG } from '../../constants/svg' import { TableElement } from '../custom-types' class TableFullWidth implements IButtonMenu { readonly title = t('tableModule.widthAuto') + readonly iconSvg = FULL_WIDTH_SVG + readonly tag = 'button' // 是否已设置 宽度自适应 getValue(editor: IDomEditor): string | boolean { const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') - if (tableNode == null) return false + + if (tableNode == null) { return false } return (tableNode as TableElement).width === '100%' } @@ -26,10 +32,12 @@ class TableFullWidth implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') + if (tableNode == null) { // 选区未处于 table node ,则禁用 return true @@ -38,11 +46,12 @@ class TableFullWidth implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } const props: Partial<TableElement> = { width: value ? 'auto' : '100%', // 切换 'auto' 和 '100%' } + Transforms.setNodes(editor, props, { mode: 'highest' }) } } diff --git a/packages/table-module/src/module/menu/InsertCol.ts b/packages/table-module/src/module/menu/InsertCol.ts index a47d3176b..27f50c504 100644 --- a/packages/table-module/src/module/menu/InsertCol.ts +++ b/packages/table-module/src/module/menu/InsertCol.ts @@ -3,16 +3,23 @@ * @author wangfupeng */ -import { Editor, Transforms, Range, Node, Path } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { + Editor, Node, Path, Range, Transforms, +} from 'slate' + import { ADD_COL_SVG } from '../../constants/svg' +import { filledMatrix } from '../../utils' import { TableCellElement, TableElement } from '../custom-types' import { isTableWithHeader } from '../helpers' -import { filledMatrix } from '../../utils' class InsertCol implements IButtonMenu { readonly title = t('tableModule.insertCol') + readonly iconSvg = ADD_COL_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -27,10 +34,12 @@ class InsertCol implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') + if (tableNode == null) { // 选区未处于 table cell node ,则禁用 return true @@ -39,7 +48,7 @@ class InsertCol implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } const [cellEntry] = Editor.nodes(editor, { match: n => DomEditor.checkNodeType(n, 'table-cell'), @@ -48,12 +57,15 @@ class InsertCol implements IButtonMenu { const [selectedCellNode, selectedCellPath] = cellEntry const rowNode = DomEditor.getParentNode(editor, selectedCellNode) - if (rowNode == null) return + + if (rowNode == null) { return } const tableNode = DomEditor.getParentNode(editor, rowNode) as TableElement - if (tableNode == null) return + + if (tableNode == null) { return } const matrix = filledMatrix(editor) let tdIndex = 0 + out: for (let x = 0; x < matrix.length; x++) { for (let y = 0; y < matrix[x].length; y++) { const [[, path]] = matrix[x][y] @@ -73,7 +85,7 @@ class InsertCol implements IButtonMenu { // 向左找到 1 元素为止 if (ltr > 1 || rtl > 1) { - if (rtl == 1) continue + if (rtl == 1) { continue } const [[element, path]] = matrix[x][tdIndex - (rtl - 1)] const colSpan = element.colSpan || 1 @@ -85,7 +97,7 @@ class InsertCol implements IButtonMenu { { colSpan: colSpan + 1, }, - { at: path } + { at: path }, ) } } @@ -98,10 +110,12 @@ class InsertCol implements IButtonMenu { hidden: exitMerge.includes(x), children: [{ text: '' }], } + if (x === 0 && isTableWithHeader(tableNode)) { newCell.isHeader = true } const [[, insertPath]] = matrix[x][tdIndex] + Transforms.insertNodes(editor, newCell, { at: insertPath }) } @@ -110,12 +124,14 @@ class InsertCol implements IButtonMenu { match: n => DomEditor.checkNodeType(n, 'table'), universal: true, }) + if (tableEntry) { const [elemNode, tablePath] = tableEntry const { columnWidths = [] } = elemNode as TableElement const adjustColumnWidths = [...columnWidths] const { minWidth = 60 } = editor.getMenuConfig('insertTable') + adjustColumnWidths.splice(tdIndex, 0, parseInt(minWidth) || 60) Transforms.setNodes(editor, { columnWidths: adjustColumnWidths } as TableElement, { diff --git a/packages/table-module/src/module/menu/InsertRow.ts b/packages/table-module/src/module/menu/InsertRow.ts index 25ee2154e..0c267ae88 100644 --- a/packages/table-module/src/module/menu/InsertRow.ts +++ b/packages/table-module/src/module/menu/InsertRow.ts @@ -3,15 +3,22 @@ * @author wangfupeng */ -import { Editor, Transforms, Range, Path } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { + Editor, Path, Range, Transforms, +} from 'slate' + import { ADD_ROW_SVG } from '../../constants/svg' -import { TableRowElement, TableCellElement } from '../custom-types' import { filledMatrix } from '../../utils' +import { TableCellElement, TableRowElement } from '../custom-types' class InsertRow implements IButtonMenu { readonly title = t('tableModule.insertRow') + readonly iconSvg = ADD_ROW_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -26,10 +33,12 @@ class InsertRow implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') + if (tableNode == null) { // 选区未处于 table cell node ,则禁用 return true @@ -38,7 +47,7 @@ class InsertRow implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } const [cellEntry] = Editor.nodes(editor, { match: n => DomEditor.checkNodeType(n, 'table-cell'), @@ -49,15 +58,18 @@ class InsertRow implements IButtonMenu { // 获取 cell length ,即多少列 const rowNode = DomEditor.getParentNode(editor, cellNode) const cellsLength = rowNode?.children.length || 0 - if (cellsLength === 0) return + + if (cellsLength === 0) { return } const matrix = filledMatrix(editor) // 向下插入行为,先找到 // 当前选区所在的 tr 索引 let trIndex = 0 + outer: for (let x = 0; x < matrix.length; x++) { for (let y = 0; y < matrix[x].length; y++) { const [[, path]] = matrix[x][y] + if (!Path.equals(cellPath, path)) { continue } @@ -77,7 +89,7 @@ class InsertRow implements IButtonMenu { // 向上找到 1 元素为止 if (ttb > 1 || btt > 1) { - if (btt == 1) continue + if (btt == 1) { continue } const [[element, path]] = matrix[trIndex - (ttb - 1)][y] const rowSpan = element.rowSpan || 1 @@ -88,7 +100,7 @@ class InsertRow implements IButtonMenu { { rowSpan: rowSpan + 1, }, - { at: path } + { at: path }, ) } } @@ -96,18 +108,21 @@ class InsertRow implements IButtonMenu { // 拼接新的 row const newRow: TableRowElement = { type: 'table-row', children: [] } + for (let i = 0; i < cellsLength; i++) { const cell: TableCellElement = { type: 'table-cell', hidden: exitMerge.includes(i), children: [{ text: '' }], } + newRow.children.push(cell) } // 插入 row const rowPath = Path.parent(cellPath) // 获取 tr 的 path const newRowPath = Path.next(rowPath) + Transforms.insertNodes(editor, newRow, { at: newRowPath }) }) } diff --git a/packages/table-module/src/module/menu/InsertTable.ts b/packages/table-module/src/module/menu/InsertTable.ts index e69c82f5b..0ef0c3e00 100644 --- a/packages/table-module/src/module/menu/InsertTable.ts +++ b/packages/table-module/src/module/menu/InsertTable.ts @@ -3,26 +3,34 @@ * @author wangfupeng */ -import { Editor, Transforms, Range, Node } from 'slate' -import { IDropPanelMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IDomEditor, IDropPanelMenu, t, +} from '@wangeditor-next/core' +import { + Editor, Node, Range, Transforms, +} from 'slate' + +import { TABLE_SVG } from '../../constants/svg' import $, { Dom7Array, DOMElement } from '../../utils/dom' import { genRandomStr } from '../../utils/util' -import { TABLE_SVG } from '../../constants/svg' -import { TableElement, TableCellElement, TableRowElement } from '../custom-types' +import { TableCellElement, TableElement, TableRowElement } from '../custom-types' function genTableNode(editor: IDomEditor, rowNum: number, colNum: number): TableElement { // 拼接 rows const rows: TableRowElement[] = [] const { minWidth = 60, tableFullWidth, tableHeader } = editor.getMenuConfig('insertTable') const columnWidths: number[] = Array(colNum).fill(parseInt(minWidth) || 60) + for (let i = 0; i < rowNum; i++) { // 拼接 cells const cells: TableCellElement[] = [] + for (let j = 0; j < colNum; j++) { const cellNode: TableCellElement = { type: 'table-cell', children: [{ text: '' }], } + if (i === 0) { cellNode.isHeader = tableHeader?.selected ?? true // 第一行默认是 th } @@ -53,9 +61,13 @@ function genDomID(): string { class InsertTable implements IDropPanelMenu { title = t('tableModule.insertTable') + iconSvg = TABLE_SVG + tag = 'button' + showDropPanel = true // 点击 button 时显示 dropPanel + private $content: Dom7Array | null = null getValue(editor: IDomEditor): string | boolean { @@ -75,19 +87,22 @@ class InsertTable implements IDropPanelMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用 + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } // 选区非折叠,禁用 const selectedElems = DomEditor.getSelectedElems(editor) const hasVoidOrPreOrTable = selectedElems.some(elem => { const type = DomEditor.getNodeType(elem) - if (type === 'pre') return true - if (type === 'table') return true - if (type === 'list-item') return true - if (editor.isVoid(elem)) return true + + if (type === 'pre') { return true } + if (type === 'table') { return true } + if (type === 'list-item') { return true } + if (editor.isVoid(elem)) { return true } return false }) - if (hasVoidOrPreOrTable) return true // 匹配到,禁用 + + if (hasVoidOrPreOrTable) { return true } // 匹配到,禁用 return false } @@ -98,7 +113,7 @@ class InsertTable implements IDropPanelMenu { */ getPanelContentElem(editor: IDomEditor): DOMElement { // 已有,直接返回 - if (this.$content) return this.$content[0] + if (this.$content) { return this.$content[0] } // 初始化 const $content = $('<div class="w-e-panel-content-table"></div>') @@ -106,10 +121,13 @@ class InsertTable implements IDropPanelMenu { // 渲染 10 * 10 table ,以快速创建表格 const $table = $('<table></table>') + for (let i = 0; i < 10; i++) { const $tr = $('<tr></tr>') + for (let j = 0; j < 10; j++) { const $td = $('<td></td>') + $td.attr('data-x', j.toString()) $td.attr('data-y', i.toString()) $tr.append($td) @@ -117,7 +135,8 @@ class InsertTable implements IDropPanelMenu { // 绑定 mouseenter $td.on('mouseenter', (e: Event) => { const { target } = e - if (target == null) return + + if (target == null) { return } const $focusTd = $(target) const { x: focusX, y: focusY } = $focusTd.dataset() @@ -131,6 +150,7 @@ class InsertTable implements IDropPanelMenu { .each(td => { const $td = $(td) const { x, y } = $td.dataset() + if (x <= focusX && y <= focusY) { $td.addClass('active') } else { @@ -144,9 +164,11 @@ class InsertTable implements IDropPanelMenu { $td.on('click', (e: Event) => { e.preventDefault() const { target } = e - if (target == null) return + + if (target == null) { return } const $td = $(target) const { x, y } = $td.dataset() + this.insertTable(editor, y + 1, x + 1) }) } @@ -163,8 +185,9 @@ class InsertTable implements IDropPanelMenu { private insertTable(editor: IDomEditor, rowNumStr: string, colNumStr: string) { const rowNum = parseInt(rowNumStr, 10) const colNum = parseInt(colNumStr, 10) - if (!rowNum || !colNum) return - if (rowNum <= 0 || colNum <= 0) return + + if (!rowNum || !colNum) { return } + if (rowNum <= 0 || colNum <= 0) { return } // 如果当前是空 p ,则删除该 p if (DomEditor.isSelectedEmptyParagraph(editor)) { @@ -175,10 +198,12 @@ class InsertTable implements IDropPanelMenu { // table 作为第一个 children 时会导致无法正常删除 // 在当前位置插入空行,当前元素下移 const newElem = { type: 'paragraph', children: [{ text: '' }] } + Transforms.insertNodes(editor, newElem, { mode: 'highest' }) } // 插入表格 const tableNode = genTableNode(editor, rowNum, colNum) + Transforms.insertNodes(editor, tableNode, { mode: 'highest' }) } } diff --git a/packages/table-module/src/module/menu/MergeCell.ts b/packages/table-module/src/module/menu/MergeCell.ts index 8d0c983b9..7a68412c9 100644 --- a/packages/table-module/src/module/menu/MergeCell.ts +++ b/packages/table-module/src/module/menu/MergeCell.ts @@ -1,14 +1,21 @@ -import { Editor, Path, Transforms, Node } from 'slate' import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' +import { + Editor, Node, Path, Transforms, +} from 'slate' + import { MERGE_CELL_SVG } from '../../constants/svg' -import { EDITOR_TO_SELECTION } from '../weak-maps' -import { TableCursor } from '../table-cursor' -import { hasCommon, filledMatrix, isOfType, CellElement } from '../../utils' +import { + CellElement, filledMatrix, hasCommon, isOfType, +} from '../../utils' import { TableCellElement } from '../custom-types' +import { TableCursor } from '../table-cursor' +import { EDITOR_TO_SELECTION } from '../weak-maps' class MergeCell implements IButtonMenu { readonly title = t('tableModule.mergeCell') + readonly iconSvg = MERGE_CELL_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -26,7 +33,7 @@ class MergeCell implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } this.merge(editor) // 释放选区 @@ -48,7 +55,7 @@ class MergeCell implements IButtonMenu { } // prettier-ignore - const [[, lastPath]] = matrix[matrix.length - 1][matrix[matrix.length - 1].length - 1]; + const [[, lastPath]] = matrix[matrix.length - 1][matrix[matrix.length - 1].length - 1] const [[, firstPath]] = matrix[0][0] // cannot merge when selection is not in common section @@ -82,6 +89,7 @@ class MergeCell implements IButtonMenu { Editor.withoutNormalizing(editor, () => { let rowSpan = 0 let colSpan = 0 + for (let x = selection.length - 1; x >= 0; x--, rowSpan++) { colSpan = 0 for (let y = selection[x].length - 1; y >= 0; y--, colSpan++) { @@ -97,7 +105,7 @@ class MergeCell implements IButtonMenu { Transforms.moveNodes(editor, { to: Path.next(lastPath), at: childPath, - }); + }) } const [[, trPath]] = Editor.nodes(editor, { diff --git a/packages/table-module/src/module/menu/SplitCell.ts b/packages/table-module/src/module/menu/SplitCell.ts index a2b57f788..e140b8d76 100644 --- a/packages/table-module/src/module/menu/SplitCell.ts +++ b/packages/table-module/src/module/menu/SplitCell.ts @@ -1,13 +1,16 @@ -import { Editor, Path, Transforms } from 'slate' import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' +import { Editor, Path, Transforms } from 'slate' + import { SPLIT_CELL_SVG } from '../../constants/svg' +import { CellElement, filledMatrix, isOfType } from '../../utils' import { EDITOR_TO_SELECTION } from '../weak-maps' -import { filledMatrix, isOfType, CellElement } from '../../utils' // import { DEFAULT_WITH_TABLE_OPTIONS } from "../../utils/options"; class SplitCell implements IButtonMenu { readonly title = t('tableModule.splitCell') + readonly iconSvg = SPLIT_CELL_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -35,7 +38,7 @@ class SplitCell implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } this.split(editor) } @@ -69,7 +72,9 @@ class SplitCell implements IButtonMenu { for (let x = matrix.length - 1; x >= 0; x--) { for (let y = matrix[x].length - 1; y >= 0; y--) { const [[, path], context] = matrix[x][y] - const { ltr: colSpan, rtl, btt: rowSpan, ttb } = context + const { + ltr: colSpan, rtl, btt: rowSpan, ttb, + } = context if (rtl > 1) { // get to the start of the colspan @@ -100,6 +105,7 @@ class SplitCell implements IButtonMenu { } } else { const [, tdPath] = td + if (Path.equals(tdPath, path)) { found = true } @@ -123,7 +129,7 @@ class SplitCell implements IButtonMenu { } for (let c = 0; c < colSpan; c++) { - let [[, nextPath]] = matrix[x + r][i + c] + const [[, nextPath]] = matrix[x + r][i + c] Transforms.unsetNodes(editor, ['hidden', 'colSpan', 'rowSpan'], { at: nextPath }) } @@ -132,7 +138,7 @@ class SplitCell implements IButtonMenu { } for (let c = 1; c < colSpan; c++) { - let [[, nextPath]] = matrix[x][y + c] + const [[, nextPath]] = matrix[x][y + c] Transforms.unsetNodes(editor, ['hidden', 'colSpan', 'rowSpan'], { at: nextPath }) } diff --git a/packages/table-module/src/module/menu/TableHeader.ts b/packages/table-module/src/module/menu/TableHeader.ts index 68809db1c..33ecb0184 100644 --- a/packages/table-module/src/module/menu/TableHeader.ts +++ b/packages/table-module/src/module/menu/TableHeader.ts @@ -3,21 +3,27 @@ * @author wangfupeng */ -import { Transforms, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { Range, Transforms } from 'slate' + import { TABLE_HEADER_SVG } from '../../constants/svg' import { TableElement } from '../custom-types' import { getFirstRowCells, isTableWithHeader } from '../helpers' class TableHeader implements IButtonMenu { readonly title = t('tableModule.header') + readonly iconSvg = TABLE_HEADER_SVG + readonly tag = 'button' // 是否已设置表头 getValue(editor: IDomEditor): string | boolean { const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') as TableElement - if (tableNode == null) return false + + if (tableNode == null) { return false } return isTableWithHeader(tableNode) } @@ -28,10 +34,12 @@ class TableHeader implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') + if (tableNode == null) { // 选区未处于 table node ,则禁用 return true @@ -40,26 +48,25 @@ class TableHeader implements IButtonMenu { } exec(editor: IDomEditor, value: string | boolean) { - if (this.isDisabled(editor)) return + if (this.isDisabled(editor)) { return } // 已经设置了表头,则取消。未设置表头,则设置 - const newValue = value ? false : true + const newValue = !value // 获取第一行所有 cell const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') as TableElement - if (tableNode == null) return + + if (tableNode == null) { return } const firstRowCells = getFirstRowCells(tableNode) // 设置 isHeader 属性 - firstRowCells.forEach(cell => - Transforms.setNodes( - editor, - { isHeader: newValue }, - { - at: DomEditor.findPath(editor, cell), - } - ) - ) + firstRowCells.forEach(cell => Transforms.setNodes( + editor, + { isHeader: newValue }, + { + at: DomEditor.findPath(editor, cell), + }, + )) } } diff --git a/packages/table-module/src/module/menu/TableProperty.ts b/packages/table-module/src/module/menu/TableProperty.ts index a4df0f177..44e83ccf2 100644 --- a/packages/table-module/src/module/menu/TableProperty.ts +++ b/packages/table-module/src/module/menu/TableProperty.ts @@ -3,8 +3,11 @@ * @author hsuna */ -import { Editor, Transforms, Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' +import { Editor, Range, Transforms } from 'slate' + import { CLEAN_SVG, JUSTIFY_CENTER_SVG, @@ -18,10 +21,15 @@ import $ from '../../utils/dom' class TableProperty implements IButtonMenu { readonly title = t('tableModule.tableProperty') + iconSvg = TABLE_PROPERTY_SVG + readonly tag = 'button' + readonly showModal = true + readonly modalWidth = 300 + readonly borderStyle = [ { value: 'none', label: t('tableModule.borderStyle.none') }, { value: 'solid', label: t('tableModule.borderStyle.solid') }, @@ -33,6 +41,7 @@ class TableProperty implements IButtonMenu { { value: 'inset', label: t('tableModule.borderStyle.inset') }, { value: 'outset', label: t('tableModule.borderStyle.outset') }, ] + readonly textAlignOptions = [ { value: 'left', label: t('justify.left'), svg: JUSTIFY_LEFT_SVG }, { value: 'center', label: t('justify.center'), svg: JUSTIFY_CENTER_SVG }, @@ -50,10 +59,12 @@ class TableProperty implements IButtonMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } const tableNode = DomEditor.getSelectedNodeByType(editor, 'table') + if (tableNode == null) { return true } @@ -68,6 +79,7 @@ class TableProperty implements IButtonMenu { const [node] = Editor.nodes(editor, { match: isOfType(editor, 'table'), }) + return node } @@ -77,7 +89,8 @@ class TableProperty implements IButtonMenu { getModalContentElem(editor: IDomEditor) { const node = this.getModalContentNode(editor) - if (!node) return null + + if (!node) { return null } const [data, path] = node const $content = $(`<div> @@ -86,16 +99,16 @@ class TableProperty implements IButtonMenu { <span class="babel-container-border"> <select name="borderStyle"> ${this.borderStyle - .map(item => `<option value="${item.value}">${item.label}</option>`) - .join('')} + .map(item => `<option value="${item.value}">${item.label}</option>`) + .join('')} </select> <span class="color-group" data-mark="color"> <span class="color-group-block"></span> <input name="borderColor" type="hidden"> </span> <input name="borderWidth" type="number" placeholder="${t( - 'tableModule.modal.borderWidth' - )}"> + 'tableModule.modal.borderWidth', + )}"> </span> </label> <div class="babel-container"> @@ -112,8 +125,8 @@ class TableProperty implements IButtonMenu { <span class="babel-container-align"> <select name="textAlign"> ${this.textAlignOptions - .map(item => `<option value="${item.value}">${item.label}</option>`) - .join('')} + .map(item => `<option value="${item.value}">${item.label}</option>`) + .join('')} </select> </span> </label> @@ -134,14 +147,18 @@ class TableProperty implements IButtonMenu { $('.color-group-block', elem).css('background-color', '').html(CLEAN_SVG) } } + $content.find('.color-group').each(elem => { const selectedColor = $('[type="hidden"]', elem).val() || '' + setSelectedColor(elem, selectedColor) const $elem = $(elem) + $elem.on('click', () => { $content.find('.color-group .w-e-drop-panel').hide() let $panel = $elem.data('panel') + if (!$panel) { $panel = this.getPanelContentElem(editor, { mark: $elem.data('mark'), @@ -161,11 +178,13 @@ class TableProperty implements IButtonMenu { }) const $button = $content.find('button') + $button.on('click', () => { const props = Array.from($content.find('[name]')).reduce((obj, elem) => { obj[$(elem).attr('name')] = $(elem).val() return obj }, {}) + Transforms.setNodes(editor, props, { at: path }) setTimeout(() => { @@ -181,12 +200,14 @@ class TableProperty implements IButtonMenu { $colorPanel.on('click', 'li', e => { const { target } = e - if (!target) return + + if (!target) { return } e.preventDefault() e.stopPropagation() const $li = $(target) const val = $li.attr('data-value') + callback(val) }) @@ -195,9 +216,11 @@ class TableProperty implements IButtonMenu { colors.forEach(color => { const $block = $(`<div class="color-block" data-value="${color}"></div>`) + $block.css('background-color', color) const $li = $(`<li data-value="${color}"></li>`) + if (selectedColor === color) { $li.addClass('active') } @@ -207,17 +230,20 @@ class TableProperty implements IButtonMenu { }) let clearText = '' - if (mark === 'color') clearText = t('tableModule.color.default') - if (mark === 'bgColor') clearText = t('tableModule.color.clear') + + if (mark === 'color') { clearText = t('tableModule.color.default') } + if (mark === 'bgColor') { clearText = t('tableModule.color.clear') } const $clearLi = $(` <li data-value="" class="clear"> ${CLEAN_SVG} ${clearText} </li> `) + $colorPanel.prepend($clearLi) const $panel = $('<div class="w-e-drop-panel"></div>') + $panel.append($colorPanel) return $panel } diff --git a/packages/table-module/src/module/menu/index.ts b/packages/table-module/src/module/menu/index.ts index a05270308..51dcd1f22 100644 --- a/packages/table-module/src/module/menu/index.ts +++ b/packages/table-module/src/module/menu/index.ts @@ -3,18 +3,18 @@ * @author wangfupeng */ -import InsertTable from './InsertTable' -import DeleteTable from './DeleteTable' -import InsertRow from './InsertRow' -import DeleteRow from './DeleteRow' -import InsertCol from './InsertCol' +import CellProperty from './CellProperty' import DeleteCol from './DeleteCol' -import TableHander from './TableHeader' +import DeleteRow from './DeleteRow' +import DeleteTable from './DeleteTable' import FullWidth from './FullWidth' +import InsertCol from './InsertCol' +import InsertRow from './InsertRow' +import InsertTable from './InsertTable' import MergeCell from './MergeCell' import SplitCell from './SplitCell' +import TableHander from './TableHeader' import TableProperty from './TableProperty' -import CellProperty from './CellProperty' export const insertTableMenuConf = { key: 'insertTable', diff --git a/packages/table-module/src/module/parse-elem-html.ts b/packages/table-module/src/module/parse-elem-html.ts index 9a0361f91..ef7765b6e 100644 --- a/packages/table-module/src/module/parse-elem-html.ts +++ b/packages/table-module/src/module/parse-elem-html.ts @@ -3,22 +3,23 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Descendant, Text } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' -import { TableCellElement, TableRowElement, TableElement } from './custom-types' -import $, { getTagName, getStyleValue, DOMElement } from '../utils/dom' + +import $, { DOMElement, getStyleValue, getTagName } from '../utils/dom' +import { TableCellElement, TableElement, TableRowElement } from './custom-types' function parseCellHtml( elem: DOMElement, children: Descendant[], - editor: IDomEditor + editor: IDomEditor, ): TableCellElement { const $elem = $(elem) children = children.filter(child => { - if (DomEditor.getNodeType(child) === 'paragraph') return true - if (Text.isText(child)) return true - if (editor.isInline(child)) return true + if (DomEditor.getNodeType(child) === 'paragraph') { return true } + if (Text.isText(child)) { return true } + if (editor.isInline(child)) { return true } return false }) @@ -52,7 +53,7 @@ export const parseCellHtmlConf = { function parseRowHtml( elem: DOMElement, children: Descendant[], - editor: IDomEditor + editor: IDomEditor, ): TableRowElement { return { type: 'table-row', @@ -69,17 +70,18 @@ export const parseRowHtmlConf = { function parseTableHtml( elem: DOMElement, children: Descendant[], - editor: IDomEditor + editor: IDomEditor, ): TableElement { const $elem = $(elem) // 计算宽度 let width = 'auto' - if (getStyleValue($elem, 'width') === '100%') width = '100%' - if ($elem.attr('width') === '100%') width = '100%' // 兼容 v4 格式 + + if (getStyleValue($elem, 'width') === '100%') { width = '100%' } + if ($elem.attr('width') === '100%') { width = '100%' } // 兼容 v4 格式 // 计算高度 - let height = parseInt(getStyleValue($elem, 'height') || '0') + const height = parseInt(getStyleValue($elem, 'height') || '0') const tableELement: TableElement = { type: 'table', @@ -90,6 +92,7 @@ function parseTableHtml( } const tdList = $elem.find('tr')[0]?.children || [] const colgroupElments: HTMLCollection = $elem.find('colgroup')[0]?.children || null + if (colgroupElments) { tableELement.columnWidths = Array.from(colgroupElments).map((col: any) => { return parseInt(col.getAttribute('width')) diff --git a/packages/table-module/src/module/parse-style-html.ts b/packages/table-module/src/module/parse-style-html.ts index 977b1f241..d0ec2075b 100644 --- a/packages/table-module/src/module/parse-style-html.ts +++ b/packages/table-module/src/module/parse-style-html.ts @@ -2,8 +2,9 @@ * @description parse style html * @author hsuna */ -import { Descendant } from 'slate' import { IDomEditor } from '@wangeditor-next/core' +import { Descendant } from 'slate' + import $, { DOMElement, getStyleValue } from '../utils/dom' import { TableCellElement } from './custom-types' @@ -13,18 +14,20 @@ const DEFAULT_BORDER_COLOR = window ?.getPropertyValue('--w-e-textarea-border-color') export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomEditor): Descendant { - if (elem.tagName !== 'TABLE' && elem.tagName !== 'TD') return node + if (elem.tagName !== 'TABLE' && elem.tagName !== 'TD') { return node } const $elem = $(elem) - let tableNode = node as TableCellElement + const tableNode = node as TableCellElement let backgroundColor = getStyleValue($elem, 'background-color') - if (!backgroundColor) backgroundColor = getStyleValue($elem, 'background') // word 背景色 + + if (!backgroundColor) { backgroundColor = getStyleValue($elem, 'background') } // word 背景色 if (backgroundColor) { tableNode.backgroundColor = backgroundColor } let border = getStyleValue($elem, 'border') + if (!border && elem.tagName === 'TD') { // https://github.com/cycleccc/wangEditor-next/blob/master/packages/table-module/src/assets/index.less#L20 // TD存在默认的css样式,尝试用getComputedStyle获取不到,只能写死 @@ -32,6 +35,7 @@ export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomE } let [borderWidth, borderStyle, borderColor] = border?.split(' ') || [] + borderWidth = getStyleValue($elem, 'border-width') || borderWidth // border 宽度 if (borderWidth) { tableNode.borderWidth = borderWidth.replace(/[^\d]/g, '') @@ -46,6 +50,7 @@ export function parseStyleHtml(elem: DOMElement, node: Descendant, editor: IDomE } let textAlign = getStyleValue($elem, 'text-align') + textAlign = getStyleValue($elem, 'text-align') || textAlign // 文本 对齐 if (textAlign) { tableNode.textAlign = textAlign diff --git a/packages/table-module/src/module/plugin.ts b/packages/table-module/src/module/plugin.ts index 7afd92259..c3adcf8da 100644 --- a/packages/table-module/src/module/plugin.ts +++ b/packages/table-module/src/module/plugin.ts @@ -3,29 +3,32 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { + BaseText, + Descendant, Editor, - Transforms, - Location, - Point, Element as SlateElement, - Descendant, - NodeEntry, + Location, Node, - BaseText, + NodeEntry, Path, + Point, + Transforms, } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' + import { withSelection } from './with-selection' // table cell 内部的删除处理 function deleteHandler(newEditor: IDomEditor): boolean { const { selection } = newEditor - if (selection == null) return false + + if (selection == null) { return false } const [cellNodeEntry] = Editor.nodes(newEditor, { match: n => DomEditor.checkNodeType(n, 'table-cell'), }) + if (cellNodeEntry) { const [, cellPath] = cellNodeEntry const start = Editor.start(newEditor, cellPath) @@ -48,10 +51,12 @@ function isTableLocation(editor: IDomEditor, location: Location): boolean { at: location, match: n => { const type = DomEditor.getNodeType(n) + return type === 'table' }, }) let hasTable = false + for (const table of tables) { hasTable = true // 找到了 table } @@ -73,6 +78,7 @@ function withTable<T extends IDomEditor>(editor: T): T { // 重写 insertBreak - cell 内换行,只换行文本,不拆分 node newEditor.insertBreak = () => { const selectedNode = DomEditor.getSelectedNodeByType(newEditor, 'table') + if (selectedNode != null) { // 选中了 table ,则在 cell 内换行 newEditor.insertText('\n') @@ -86,15 +92,19 @@ function withTable<T extends IDomEditor>(editor: T): T { // 重写 delete - cell 内删除,只删除文字,不删除 node newEditor.deleteBackward = unit => { const res = deleteHandler(newEditor) - if (res) return // 命中 table cell ,自己处理删除 + + if (res) { return } // 命中 table cell ,自己处理删除 // 防止从 table 后面的 p 删除时,删除最后一个 cell - issues/4221 const { selection } = newEditor + if (selection) { const before = Editor.before(newEditor, selection) // 前一个 location + if (before) { const isTableOnBeforeLocation = isTableLocation(newEditor, before) // before 是否是 table // 如果前面是 table, 当前是 paragraph ,则不执行删除。否则会删除 table 最后一个 cell + if (isTableOnBeforeLocation && DomEditor.getSelectedNodeByType(newEditor, 'paragraph')) { return } @@ -108,6 +118,7 @@ function withTable<T extends IDomEditor>(editor: T): T { // 重写 handleTab 在table内按tab时跳到下一个单元格 newEditor.handleTab = () => { const selectedNode = DomEditor.getSelectedNodeByType(newEditor, 'table') + if (selectedNode) { const above = Editor.above(editor) as NodeEntry<SlateElement> @@ -117,6 +128,7 @@ function withTable<T extends IDomEditor>(editor: T): T { } let next = Editor.next(editor) + if (next) { if (next[0] && (next[0] as BaseText).text) { // 多个单元格同时选中按 tab 导致错位修复 @@ -127,8 +139,10 @@ function withTable<T extends IDomEditor>(editor: T): T { const topLevelNodes = newEditor.children || [] const topLevelNodesLength = topLevelNodes.length // 在最后一个单元格按tab时table末尾如果没有p则插入p后光标切到p上 + if (DomEditor.checkNodeType(topLevelNodes[topLevelNodesLength - 1], 'table')) { const p = DomEditor.genEmptyParagraph() + Transforms.insertNodes(newEditor, p, { at: [topLevelNodesLength] }) // 在表格末尾插入p后再次执行使光标切到p上 newEditor.handleTab() @@ -142,7 +156,8 @@ function withTable<T extends IDomEditor>(editor: T): T { newEditor.deleteForward = unit => { const res = deleteHandler(newEditor) - if (res) return // 命中 table cell ,自己处理删除 + + if (res) { return } // 命中 table cell ,自己处理删除 // 执行默认的删除 deleteForward(unit) @@ -151,6 +166,7 @@ function withTable<T extends IDomEditor>(editor: T): T { // 重新 normalize newEditor.normalizeNode = ([node, path]) => { const type = DomEditor.getNodeType(node) + if (type !== 'table') { // 未命中 table ,执行默认的 normalizeNode return normalizeNode([node, path]) @@ -158,8 +174,10 @@ function withTable<T extends IDomEditor>(editor: T): T { // -------------- table 是 editor 最后一个节点,需要后面插入 p -------------- const isLast = DomEditor.isLastNode(newEditor, node) + if (isLast) { const p = DomEditor.genEmptyParagraph() + Transforms.insertNodes(newEditor, p, { at: [path[0] + 1] }) } } @@ -167,6 +185,7 @@ function withTable<T extends IDomEditor>(editor: T): T { // 重写 insertData - 粘贴文本 newEditor.insertData = (data: DataTransfer) => { const tableNode = DomEditor.getSelectedNodeByType(newEditor, 'table') + if (tableNode == null) { insertData(data) // 执行默认的 insertData return @@ -187,18 +206,21 @@ function withTable<T extends IDomEditor>(editor: T): T { // 重写 table-cell 中的全选 newEditor.selectAll = () => { const selection = newEditor.selection + if (selection == null) { selectAll() return } const cell = DomEditor.getSelectedNodeByType(newEditor, 'table-cell') + if (cell == null) { selectAll() return } const { anchor, focus } = selection + if (!Path.equals(anchor.path.slice(0, 3), focus.path.slice(0, 3))) { // 选中了多个 cell ,忽略 selectAll() @@ -207,6 +229,7 @@ function withTable<T extends IDomEditor>(editor: T): T { const text = Node.string(cell) const textLength = text.length + if (textLength === 0) { selectAll() return @@ -219,6 +242,7 @@ function withTable<T extends IDomEditor>(editor: T): T { anchor: start, focus: end, } + newEditor.select(newSelection) // 选中 table-cell 内部的全部文字 } diff --git a/packages/table-module/src/module/pre-parse-html.ts b/packages/table-module/src/module/pre-parse-html.ts index 162976e8e..964d41e47 100644 --- a/packages/table-module/src/module/pre-parse-html.ts +++ b/packages/table-module/src/module/pre-parse-html.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -import $, { getTagName, DOMElement } from '../utils/dom' +import $, { DOMElement, getTagName } from '../utils/dom' /** * pre-prase table ,去掉 <tbody> @@ -12,14 +12,17 @@ import $, { getTagName, DOMElement } from '../utils/dom' function preParse(tableElem: DOMElement): DOMElement { const $table = $(tableElem) const tagName = getTagName($table) - if (tagName !== 'table') return tableElem + + if (tagName !== 'table') { return tableElem } // 没有 <tbody> 则直接返回 const $tbody = $table.find('tbody') - if ($tbody.length === 0) return tableElem + + if ($tbody.length === 0) { return tableElem } // 去掉 <tbody> ,把 <tr> 移动到 <table> 下面 const $tr = $table.find('tr') + $table.append($tr) $tbody.remove() diff --git a/packages/table-module/src/module/render-elem/index.ts b/packages/table-module/src/module/render-elem/index.ts index 98f89c924..0b78950bd 100644 --- a/packages/table-module/src/module/render-elem/index.ts +++ b/packages/table-module/src/module/render-elem/index.ts @@ -3,9 +3,9 @@ * @author wangfupeng */ -import renderTable from './render-table' -import renderTableRow from './render-row' import renderTableCell from './render-cell' +import renderTableRow from './render-row' +import renderTable from './render-table' export const renderTableConf = { type: 'table', diff --git a/packages/table-module/src/module/render-elem/render-cell.tsx b/packages/table-module/src/module/render-elem/render-cell.tsx index 7edc2fb38..e2f0102d7 100644 --- a/packages/table-module/src/module/render-elem/render-cell.tsx +++ b/packages/table-module/src/module/render-elem/render-cell.tsx @@ -3,9 +3,10 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor-next/core' + import { TableCellElement } from '../custom-types' import { isCellInFirstRow } from '../helpers' import { TableCursor } from '../table-cursor' @@ -13,7 +14,7 @@ import { TableCursor } from '../table-cursor' function renderTableCell( cellNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { const isFirstRow = isCellInFirstRow(editor, cellNode as TableCellElement) const { @@ -63,6 +64,7 @@ function renderTableCell( {children} </Tag> ) + return vnode } diff --git a/packages/table-module/src/module/render-elem/render-row.tsx b/packages/table-module/src/module/render-elem/render-row.tsx index b9fe04ffe..73ff8aafd 100644 --- a/packages/table-module/src/module/render-elem/render-row.tsx +++ b/packages/table-module/src/module/render-elem/render-row.tsx @@ -3,16 +3,17 @@ * @author wangfupeng */ +import { IDomEditor } from '@wangeditor-next/core' import { Element as SlateElement } from 'slate' import { jsx, VNode } from 'snabbdom' -import { IDomEditor } from '@wangeditor-next/core' function renderTableRow( elemNode: SlateElement, children: VNode[] | null, - editor: IDomEditor + editor: IDomEditor, ): VNode { const vnode = <tr>{children}</tr> + return vnode } diff --git a/packages/table-module/src/module/render-elem/render-table.tsx b/packages/table-module/src/module/render-elem/render-table.tsx index 0f3306d7e..7a58d9df3 100644 --- a/packages/table-module/src/module/render-elem/render-table.tsx +++ b/packages/table-module/src/module/render-elem/render-table.tsx @@ -3,20 +3,23 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import debounce from 'lodash.debounce' -import { Editor, Element as SlateElement, Range, Point, Path } from 'slate' +import { + Editor, Element as SlateElement, Path, Point, Range, +} from 'slate' import { h, jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' -import { TableElement } from '../custom-types' -import { TableCursor } from '../table-cursor' + import { - observerTableResize, - unObserveTableResize, - handleCellBorderVisible, + getColumnWidthRatios, handleCellBorderHighlight, handleCellBorderMouseDown, - getColumnWidthRatios, + handleCellBorderVisible, + observerTableResize, + unObserveTableResize, } from '../column-resize' +import { TableElement } from '../custom-types' +import { TableCursor } from '../table-cursor' /** * 计算 table 是否可编辑。如果选区跨域 table 和外部内容,删除,会导致 table 结构打乱。所以,有时要让 table 不可编辑 @@ -24,21 +27,20 @@ import { * @param tableElem table elem */ function getContentEditable(editor: IDomEditor, tableElem: SlateElement): boolean { - if (editor.isDisabled()) return false + if (editor.isDisabled()) { return false } const { selection } = editor - if (selection == null) return true - if (Range.isCollapsed(selection)) return true + + if (selection == null) { return true } + if (Range.isCollapsed(selection)) { return true } const { anchor, focus } = selection const tablePath = DomEditor.findPath(editor, tableElem) const tableStart = Editor.start(editor, tablePath) const tableEnd = Editor.end(editor, tablePath) - const isAnchorInTable = - Point.compare(anchor, tableEnd) <= 0 && Point.compare(anchor, tableStart) >= 0 - const isFocusInTable = - Point.compare(focus, tableEnd) <= 0 && Point.compare(focus, tableStart) >= 0 + const isAnchorInTable = Point.compare(anchor, tableEnd) <= 0 && Point.compare(anchor, tableStart) >= 0 + const isFocusInTable = Point.compare(focus, tableEnd) <= 0 && Point.compare(focus, tableStart) >= 0 // 选区在 table 内部,且选中了同一个单元格。表格可以编辑 if (isAnchorInTable && isFocusInTable) { @@ -79,9 +81,9 @@ function renderTable(elemNode: SlateElement, children: VNode[] | null, editor: I on={{ mousedown: (e: MouseEvent) => { // @ts-ignore 阻止光标定位到 table 后面 - if (e.target.tagName === 'DIV') e.preventDefault() + if (e.target.tagName === 'DIV') { e.preventDefault() } - if (editor.isDisabled()) return + if (editor.isDisabled()) { return } // @ts-ignore 如果用户行为是获取焦点输入文本时,需释放选区 if (e.target.closest('[data-block-type="table-cell"]')) { @@ -92,12 +94,14 @@ function renderTable(elemNode: SlateElement, children: VNode[] | null, editor: I const tablePath = DomEditor.findPath(editor, elemNode) const tableStart = Editor.start(editor, tablePath) const { selection } = editor + if (selection == null) { editor.select(tableStart) // 选中 table 内部 return } const { path } = selection.anchor - if (path[0] === tablePath[0]) return // 当前选区,就在 table 内部 + + if (path[0] === tablePath[0]) { return } // 当前选区,就在 table 内部 editor.select(tableStart) // 选中 table 内部 }, @@ -111,14 +115,14 @@ function renderTable(elemNode: SlateElement, children: VNode[] | null, editor: I * 2. table 宽度为 auto 时,宽度为 列宽之和 * 3. 鼠标移动到 单元格 边缘,设置 visible className */ - className={'table ' + (isSelecting ? 'table-selection-none' : '')} + className={`table ${isSelecting ? 'table-selection-none' : ''}`} style={{ - width: tableWidth == '100%' ? tableWidth : columnWidths.reduce((a, b) => a + b, 0) + 'px', + width: tableWidth == '100%' ? tableWidth : `${columnWidths.reduce((a, b) => a + b, 0)}px`, }} on={{ mousemove: debounce( (e: MouseEvent) => handleCellBorderVisible(editor, elemNode, e, scrollWidth), - 25 + 25, ), }} > @@ -145,6 +149,7 @@ function renderTable(elemNode: SlateElement, children: VNode[] | null, editor: I * columnWidths 表示的是比例 * 1. 需要计算出真实的宽度 */ + if (tableWidth == '100%') { minWidth = columnWidthRatios[index] * scrollWidth } @@ -153,11 +158,11 @@ function renderTable(elemNode: SlateElement, children: VNode[] | null, editor: I <div className="column-resizer-item" style={{ minWidth: `${minWidth}px` }}> <div className={ - 'resizer-line-hotzone ' + - (isHoverCellBorder && index == resizingIndex ? 'visible ' : '') + - (isResizing && index == resizingIndex ? 'highlight' : '') + `resizer-line-hotzone ${ + isHoverCellBorder && index == resizingIndex ? 'visible ' : '' + }${isResizing && index == resizingIndex ? 'highlight' : ''}` } - style={{ height: height + 'px' }} + style={{ height: `${height}px` }} on={{ mouseenter: (e: MouseEvent) => handleCellBorderHighlight(editor, e), mouseleave: (e: MouseEvent) => handleCellBorderHighlight(editor, e), @@ -188,8 +193,9 @@ function renderTable(elemNode: SlateElement, children: VNode[] | null, editor: I }, }, }, - vnode + vnode, ) + return containerVnode } diff --git a/packages/table-module/src/module/render-style.ts b/packages/table-module/src/module/render-style.ts index 3ede0d530..9c24fcdef 100644 --- a/packages/table-module/src/module/render-style.ts +++ b/packages/table-module/src/module/render-style.ts @@ -1,5 +1,6 @@ import { Descendant, Element } from 'slate' import { VNode } from 'snabbdom' + import { addVnodeStyle } from '../utils/vdom' import { TableCellElement, TableCellProperty } from './custom-types' @@ -10,19 +11,22 @@ import { TableCellElement, TableCellProperty } from './custom-types' * @returns vnode */ export function renderStyle(node: Descendant, vnode: VNode): VNode { - if (!Element.isElement(node)) return vnode + if (!Element.isElement(node)) { return vnode } - const { backgroundColor, borderWidth, borderStyle, borderColor, textAlign } = - node as TableCellElement + const { + backgroundColor, borderWidth, borderStyle, borderColor, textAlign, + } = node as TableCellElement const props: TableCellProperty = {} - if (backgroundColor) props.backgroundColor = backgroundColor - if (borderWidth) props.borderWidth = `${borderWidth}px` - if (borderStyle) props.borderStyle = borderStyle - if (borderColor) props.borderColor = borderColor - if (textAlign) props.textAlign = textAlign - let styleVnode: VNode = vnode + if (backgroundColor) { props.backgroundColor = backgroundColor } + if (borderWidth) { props.borderWidth = `${borderWidth}px` } + if (borderStyle) { props.borderStyle = borderStyle } + if (borderColor) { props.borderColor = borderColor } + if (textAlign) { props.textAlign = textAlign } + + const styleVnode: VNode = vnode + if (node.type === 'table') { addVnodeStyle((styleVnode.children?.[0] as VNode).children?.[0] as VNode, props) } else { diff --git a/packages/table-module/src/module/style-to-html.ts b/packages/table-module/src/module/style-to-html.ts index c7944ff9e..53813c597 100644 --- a/packages/table-module/src/module/style-to-html.ts +++ b/packages/table-module/src/module/style-to-html.ts @@ -6,14 +6,17 @@ import $, { getOuterHTML } from '../utils/dom' export function styleToHtml(node, elemHtml) { - if (node.type !== 'table' && node.type !== 'table-cell') return elemHtml + if (node.type !== 'table' && node.type !== 'table-cell') { return elemHtml } - const { backgroundColor, borderWidth, borderStyle, borderColor, textAlign } = node + const { + backgroundColor, borderWidth, borderStyle, borderColor, textAlign, + } = node - if (!(backgroundColor || borderWidth || borderStyle || borderColor || textAlign)) return elemHtml + if (!(backgroundColor || borderWidth || borderStyle || borderColor || textAlign)) { return elemHtml } // 设置样式 const $elem = $(elemHtml) + backgroundColor && $elem.css('background-color', backgroundColor) borderWidth && $elem.css('border-width', `${borderWidth}px`) borderStyle && $elem.css('border-style', borderStyle) diff --git a/packages/table-module/src/module/table-cursor.ts b/packages/table-module/src/module/table-cursor.ts index dc849dc63..dab70b705 100644 --- a/packages/table-module/src/module/table-cursor.ts +++ b/packages/table-module/src/module/table-cursor.ts @@ -10,8 +10,9 @@ import { Range, Transforms, } from 'slate' -import { EDITOR_TO_SELECTION, EDITOR_TO_SELECTION_SET } from './weak-maps' + import { isOfType } from '../utils' +import { EDITOR_TO_SELECTION, EDITOR_TO_SELECTION_SET } from './weak-maps' export const TableCursor = { /** @returns {boolean} `true` if the selection is inside a table, otherwise `false`. */ @@ -27,10 +28,12 @@ export const TableCursor = { * Retrieves a matrix representing the selected cells within a table. * @returns {NodeEntry<T>[][]} A matrix containing the selected cells. */ - *selection(editor: Editor): Generator<NodeEntry[]> { + * selection(editor: Editor): Generator<NodeEntry[]> { const matrix = EDITOR_TO_SELECTION.get(editor) + for (let x = 0; matrix && x < matrix.length; x++) { const cells: NodeEntry[] = [] + for (let y = 0; y < matrix[x].length; y++) { const [entry, { ltr: colSpan, ttb }] = matrix[x][y] diff --git a/packages/table-module/src/module/weak-maps.ts b/packages/table-module/src/module/weak-maps.ts index 1f2b47108..ecc307b09 100644 --- a/packages/table-module/src/module/weak-maps.ts +++ b/packages/table-module/src/module/weak-maps.ts @@ -1,4 +1,5 @@ import { Editor, Element } from 'slate' + import { NodeEntryWithContext } from '../utils' /** Weak reference between the `Editor` and the selected elements */ diff --git a/packages/table-module/src/module/with-selection.ts b/packages/table-module/src/module/with-selection.ts index c6ccf3858..db7729550 100644 --- a/packages/table-module/src/module/with-selection.ts +++ b/packages/table-module/src/module/with-selection.ts @@ -1,7 +1,12 @@ -import { Editor, Element, Operation, Path, Range } from 'slate' +import { + Editor, Element, Operation, Path, Range, +} from 'slate' + +import { + filledMatrix, hasCommon, isOfType, NodeEntryWithContext, Point, +} from '../utils' import { TableCursor } from './table-cursor' import { EDITOR_TO_SELECTION, EDITOR_TO_SELECTION_SET } from './weak-maps' -import { Point, filledMatrix, hasCommon, isOfType, NodeEntryWithContext } from '../utils' export function withSelection<T extends Editor>(editor: T) { const { apply } = editor @@ -52,6 +57,7 @@ export function withSelection<T extends Editor>(editor: T) { // find initial bounds const from = Point.valueOf(0, 0) const to = Point.valueOf(0, 0) + outer: for (let x = 0; x < filled.length; x++) { for (let y = 0; y < filled[x].length; y++) { const [[, path]] = filled[x][y] @@ -79,7 +85,9 @@ export function withSelection<T extends Editor>(editor: T) { for (let x = nextStart.x; x <= nextEnd.x; x++) { for (let y = nextStart.y; y <= nextEnd.y; y++) { - const [, { rtl, ltr, btt, ttb }] = filled[x][y] + const [, { + rtl, ltr, btt, ttb, + }] = filled[x][y] nextStart.x = Math.min(nextStart.x, x - (ttb - 1)) nextStart.y = Math.min(nextStart.y, y - (rtl - 1)) @@ -102,8 +110,10 @@ export function withSelection<T extends Editor>(editor: T) { for (let x = start.x; x <= end.x; x++) { const cells: NodeEntryWithContext[] = [] + for (let y = start.y; y <= end.y; y++) { const [[element]] = filled[x][y] + selectedSet.add(element) cells.push(filled[x][y]) } diff --git a/packages/table-module/src/utils/dom.ts b/packages/table-module/src/utils/dom.ts index 3f673b40a..b79d61707 100644 --- a/packages/table-module/src/utils/dom.ts +++ b/packages/table-module/src/utils/dom.ts @@ -4,40 +4,52 @@ */ import $, { + addClass, append, - on, - focus, attr, - val, - html, - dataset, - addClass, - removeClass, children, + data, + dataset, + Dom7Array, each, find, - data, + focus, hide, + html, + on, + removeClass, show, - Dom7Array, + val, } from 'dom7' + +// COMPAT: This is required to prevent TypeScript aliases from doing some very +// weird things for Slate's types with the same name as globals. (2019/11/27) +// https://github.com/microsoft/TypeScript/issues/35002 +import DOMNode = globalThis.Node +import DOMComment = globalThis.Comment +import DOMElement = globalThis.Element +import DOMText = globalThis.Text +import DOMRange = globalThis.Range +import DOMSelection = globalThis.Selection +import DOMStaticRange = globalThis.StaticRange + export { Dom7Array } from 'dom7' -if (append) $.fn.append = append -if (on) $.fn.on = on -if (focus) $.fn.focus = focus -if (attr) $.fn.attr = attr -if (val) $.fn.val = val -if (html) $.fn.html = html -if (dataset) $.fn.dataset = dataset -if (addClass) $.fn.addClass = addClass -if (removeClass) $.fn.removeClass = removeClass -if (children) $.fn.children = children -if (each) $.fn.each = each -if (find) $.fn.find = find -if (data) $.fn.data = data -if (hide) $.fn.hide = hide -if (show) $.fn.show = show +if (append) { $.fn.append = append } +if (on) { $.fn.on = on } +if (focus) { $.fn.focus = focus } +if (attr) { $.fn.attr = attr } +if (val) { $.fn.val = val } +if (html) { $.fn.html = html } +if (dataset) { $.fn.dataset = dataset } +if (addClass) { $.fn.addClass = addClass } +if (removeClass) { $.fn.removeClass = removeClass } +if (children) { $.fn.children = children } +if (each) { $.fn.each = each } +if (find) { $.fn.find = find } +if (data) { $.fn.data = data } +if (hide) { $.fn.hide = hide } +if (show) { $.fn.show = show } export default $ @@ -46,7 +58,7 @@ export default $ * @param $elem $elem */ export function getTagName($elem: Dom7Array): string { - if ($elem.length) return $elem[0].tagName.toLowerCase() + if ($elem.length) { return $elem[0].tagName.toLowerCase() } return '' } @@ -61,10 +73,13 @@ export function getStyleValue($elem: Dom7Array, styleKey: string): string { const styleStr = $elem.attr('style') || '' // 如 'line-height: 2.5; color: red;' const styleArr = styleStr.split(';') // 如 ['line-height: 2.5', ' color: red', ''] const length = styleArr.length + for (let i = 0; i < length; i++) { const styleItemStr = styleArr[i] // 如 'line-height: 2.5' + if (styleItemStr) { const arr = styleItemStr.split(':') // ['line-height', ' 2.5'] + if (arr[0].trim() === styleKey) { res = arr[1].trim() } @@ -79,18 +94,9 @@ export function getStyleValue($elem: Dom7Array, styleKey: string): string { * @param $elem dom7 elem */ export function getOuterHTML($elem: Dom7Array) { - if ($elem.length === 0) return '' + if ($elem.length === 0) { return '' } return $elem[0].outerHTML } - -// COMPAT: This is required to prevent TypeScript aliases from doing some very -// weird things for Slate's types with the same name as globals. (2019/11/27) -// https://github.com/microsoft/TypeScript/issues/35002 -import DOMNode = globalThis.Node -import DOMComment = globalThis.Comment -import DOMElement = globalThis.Element -import DOMText = globalThis.Text -import DOMRange = globalThis.Range -import DOMSelection = globalThis.Selection -import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } +export { + DOMComment, DOMElement, DOMNode, DOMRange, DOMSelection, DOMStaticRange, DOMText, +} diff --git a/packages/table-module/src/utils/has-common.ts b/packages/table-module/src/utils/has-common.ts index d7a793612..c9d62c3de 100644 --- a/packages/table-module/src/utils/has-common.ts +++ b/packages/table-module/src/utils/has-common.ts @@ -1,6 +1,7 @@ import { Editor, Node, Span } from 'slate' -import { WithTableOptions } from './options' + import { isOfType } from './is-of-type' +import { WithTableOptions } from './options' /** * Determines whether two paths belong to the same types by checking diff --git a/packages/table-module/src/utils/is-of-type.ts b/packages/table-module/src/utils/is-of-type.ts index bd47d6b59..02c1b2bb7 100644 --- a/packages/table-module/src/utils/is-of-type.ts +++ b/packages/table-module/src/utils/is-of-type.ts @@ -1,6 +1,9 @@ -import { Editor, Element, Node, NodeMatch } from 'slate' +import { + Editor, Element, Node, NodeMatch, +} from 'slate' + +import { DEFAULT_WITH_TABLE_OPTIONS, WithTableOptions } from './options' import { WithType } from './types' -import { WithTableOptions, DEFAULT_WITH_TABLE_OPTIONS } from './options' export function isElement<T extends Element>(node: Node): node is WithType<T> { return !Editor.isEditor(node) && Element.isElement(node) && 'type' in node @@ -11,8 +14,8 @@ export function isOfType<T extends WithType<Element>>( editor: Editor, ...types: Array<keyof WithTableOptions['blocks']> ): NodeMatch<T> { - const options = DEFAULT_WITH_TABLE_OPTIONS, - elementTypes = types.map(type => options?.blocks?.[type]) + const options = DEFAULT_WITH_TABLE_OPTIONS + const elementTypes = types.map(type => options?.blocks?.[type]) return (node: Node): boolean => isElement(node) && elementTypes.includes(node.type as any) } diff --git a/packages/table-module/src/utils/matrices.ts b/packages/table-module/src/utils/matrices.ts index efacfc0f0..d9b0781d7 100644 --- a/packages/table-module/src/utils/matrices.ts +++ b/packages/table-module/src/utils/matrices.ts @@ -1,11 +1,12 @@ import { Editor, Location, NodeEntry } from 'slate' -import { NodeEntryWithContext, CellElement } from './types' + import { isOfType } from './is-of-type' +import { CellElement, NodeEntryWithContext } from './types' /** Generates a matrix for each table section (`thead`, `tbody`, `tfoot`) */ export function* matrices( editor: Editor, - options: { at?: Location } = {} + options: { at?: Location } = {}, ): Generator<NodeEntry<CellElement>[][]> { const [table] = Editor.nodes(editor, { match: isOfType(editor, 'table'), @@ -43,7 +44,7 @@ export function* matrices( export function filledMatrix( editor: Editor, - options: { at?: Location } = {} + options: { at?: Location } = {}, ): NodeEntryWithContext[][] { const filled: NodeEntryWithContext[][] = [] diff --git a/packages/table-module/src/utils/point.ts b/packages/table-module/src/utils/point.ts index c80af781b..29b88563d 100644 --- a/packages/table-module/src/utils/point.ts +++ b/packages/table-module/src/utils/point.ts @@ -1,5 +1,6 @@ export class Point { public x: number + public y: number constructor(x: number, y: number) { diff --git a/packages/table-module/src/utils/types.ts b/packages/table-module/src/utils/types.ts index c890b5b1e..2d47e8b65 100644 --- a/packages/table-module/src/utils/types.ts +++ b/packages/table-module/src/utils/types.ts @@ -14,7 +14,7 @@ export type NodeEntryWithContext = [ ltr: number // left-to-right (colspan) ttb: number // top-to-bottom (rowspan) btt: number // bottom-to-top (rowspan) - } + }, ] export type SelectionMode = 'start' | 'end' | 'all' diff --git a/packages/table-module/src/utils/util.ts b/packages/table-module/src/utils/util.ts index 84ef78add..ca37e8480 100644 --- a/packages/table-module/src/utils/util.ts +++ b/packages/table-module/src/utils/util.ts @@ -10,6 +10,6 @@ import { nanoid } from 'nanoid' * @param prefix 前缀 * @returns 随机数字符串 */ -export function genRandomStr(prefix: string = 'r'): string { +export function genRandomStr(prefix = 'r'): string { return `${prefix}-${nanoid()}` } diff --git a/packages/table-module/src/utils/vdom.ts b/packages/table-module/src/utils/vdom.ts index 26808c117..bbd35048c 100644 --- a/packages/table-module/src/utils/vdom.ts +++ b/packages/table-module/src/utils/vdom.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -import { VNode, VNodeStyle, Dataset } from 'snabbdom' +import { Dataset, VNode, VNodeStyle } from 'snabbdom' // /** // * 给 vnode 添加 dataset @@ -24,9 +24,10 @@ import { VNode, VNodeStyle, Dataset } from 'snabbdom' * @param newStyle { key: val } */ export function addVnodeStyle(vnode: VNode, newStyle: VNodeStyle) { - if (vnode.data == null) vnode.data = {} + if (vnode.data == null) { vnode.data = {} } const data = vnode.data - if (data.style == null) data.style = {} + + if (data.style == null) { data.style = {} } Object.assign(data.style, newStyle) } diff --git a/packages/upload-image-module/__tests__/plugin.test.ts b/packages/upload-image-module/__tests__/plugin.test.ts index b681357fa..870cde276 100644 --- a/packages/upload-image-module/__tests__/plugin.test.ts +++ b/packages/upload-image-module/__tests__/plugin.test.ts @@ -1,5 +1,6 @@ -import { IDomEditor } from '@wangeditor-next/core' import * as basicModule from '@wangeditor-next/basic-modules' +import { IDomEditor } from '@wangeditor-next/core' + import createEditor from '../../../tests/utils/create-editor' import withUploadImage from '../src/module/plugin' import * as uploadImage from '../src/module/upload-images' @@ -18,9 +19,11 @@ describe('withUploadImage plugin', () => { jest.spyOn(basicModule, 'isInsertImageMenuDisabled').mockImplementation(() => true) const fn = jest.fn() + editor.insertData = fn const newEditor = withUploadImage(editor) + newEditor.insertData(new DataTransfer()) expect(fn).toBeCalled() @@ -28,11 +31,14 @@ describe('withUploadImage plugin', () => { test('withUploadImage plugin should invoke insertData with text data if transfer data contains plain text ', () => { const fn = jest.fn() + editor.insertData = fn const newEditor = withUploadImage(editor) + jest.spyOn(DataTransfer.prototype, 'getData').mockImplementation(() => 'plain text') const transfer = new DataTransfer() + newEditor.insertData(transfer) expect(transfer.getData('text/plain')).toBe('plain text') @@ -44,9 +50,11 @@ describe('withUploadImage plugin', () => { test('withUploadImage plugin should invoke insertData with transfer data if transfer data contains empty files', () => { const fn = jest.fn() + editor.insertData = fn const newEditor = withUploadImage(editor) + jest.spyOn(DataTransfer.prototype, 'files', 'get').mockReturnValue([] as any) newEditor.insertData(new DataTransfer()) @@ -55,9 +63,11 @@ describe('withUploadImage plugin', () => { test('withUploadImage plugin should invoke uploadImage method with image files if transfer data contains file which mime type is image', () => { const fn = jest.fn() + jest.spyOn(uploadImage, 'default').mockImplementation(fn) const newEditor = withUploadImage(editor) + jest .spyOn(DataTransfer.prototype, 'files', 'get') .mockReturnValue([{ type: 'image/png', size: 10 }] as any) @@ -69,14 +79,17 @@ describe('withUploadImage plugin', () => { test('withUploadImage plugin should invoke insertData method with transfer data if transfer data contains file which mime type is not image', () => { const fn = jest.fn() + editor.insertData = fn const newEditor = withUploadImage(editor) + jest .spyOn(DataTransfer.prototype, 'files', 'get') .mockReturnValue([{ type: 'text/html', size: 10 }] as any) const transfer = new DataTransfer() + newEditor.insertData(transfer) expect(fn).toBeCalledWith(transfer) diff --git a/packages/upload-image-module/__tests__/upload-files.test.ts b/packages/upload-image-module/__tests__/upload-files.test.ts index f22125f11..0d8f47a33 100644 --- a/packages/upload-image-module/__tests__/upload-files.test.ts +++ b/packages/upload-image-module/__tests__/upload-files.test.ts @@ -1,9 +1,11 @@ -import uploadImages from '../src/module/upload-images' -import createEditor from '../../../tests/utils/create-editor' import * as core from '@wangeditor-next/core' +import createEditor from '../../../tests/utils/create-editor' +import uploadImages from '../src/module/upload-images' + function mockFile(filename: string) { const file = new File(['123'], filename) + return file } @@ -11,6 +13,7 @@ describe('Upload image menu upload files util', () => { test('uploadImages should do nothing if give null value to fileList argument', async () => { const editor = createEditor() const res = await uploadImages(editor, null) + expect(res).toBeUndefined() }) @@ -58,7 +61,7 @@ describe('Upload image menu upload files util', () => { ({ addFile: jest.fn(), upload: jest.fn(), - } as any) + } as any), ) const editor = createEditor() diff --git a/packages/upload-image-module/__tests__/upload-image-menu.test.ts b/packages/upload-image-module/__tests__/upload-image-menu.test.ts index 7cf4fed2f..ed27990aa 100644 --- a/packages/upload-image-module/__tests__/upload-image-menu.test.ts +++ b/packages/upload-image-module/__tests__/upload-image-menu.test.ts @@ -1,6 +1,6 @@ import { IDomEditor } from '../../../packages/editor/src' -import UploadImageMenu from '../src/module/menu/UploadImageMenu' import createEditor from '../../../tests/utils/create-editor' +import UploadImageMenu from '../src/module/menu/UploadImageMenu' import uploadImages from '../src/module/upload-images' let editor: IDomEditor @@ -41,6 +41,7 @@ describe('Upload image menu', () => { }, }, }) + menu.exec(editor, 'test.jpg') expect(jestFn).toBeCalled() }) diff --git a/packages/upload-image-module/rollup.config.js b/packages/upload-image-module/rollup.config.js index bc80f5ce7..04c1e6965 100644 --- a/packages/upload-image-module/rollup.config.js +++ b/packages/upload-image-module/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorUploadImageModule' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/upload-image-module/src/constants/svg.ts b/packages/upload-image-module/src/constants/svg.ts index e7323973b..7f56cab64 100644 --- a/packages/upload-image-module/src/constants/svg.ts +++ b/packages/upload-image-module/src/constants/svg.ts @@ -10,5 +10,4 @@ */ // 上传图片 -export const UPLOAD_IMAGE_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M828.708571 585.045333a48.761905 48.761905 0 0 0-48.737523 48.761905v18.529524l-72.143238-72.167619a135.972571 135.972571 0 0 0-191.585524 0l-34.133334 34.133333-120.880762-120.953905a138.898286 138.898286 0 0 0-191.585523 0l-72.167619 72.167619V292.400762a48.786286 48.786286 0 0 1 48.761904-48.761905h341.23581a48.737524 48.737524 0 0 0 34.474667-83.285333 48.737524 48.737524 0 0 0-34.474667-14.287238H146.236952A146.212571 146.212571 0 0 0 0 292.400762v585.289143A146.358857 146.358857 0 0 0 146.236952 1024h584.996572a146.212571 146.212571 0 0 0 146.236952-146.310095V633.807238a48.786286 48.786286 0 0 0-48.761905-48.761905zM146.261333 926.45181a48.737524 48.737524 0 0 1-48.761904-48.761905v-174.128762l141.409523-141.458286a38.497524 38.497524 0 0 1 53.126096 0l154.526476 154.624 209.627428 209.724953H146.236952z m633.734096-48.761905c-0.073143 9.337905-3.145143 18.383238-8.777143 25.843809l-219.843048-220.94019 34.133333-34.133334a37.546667 37.546667 0 0 1 53.613715 0l140.873143 141.897143V877.714286zM1009.615238 160.231619L863.329524 13.897143a48.737524 48.737524 0 0 0-16.091429-10.24c-11.849143-4.87619-25.161143-4.87619-37.059047 0a48.761905 48.761905 0 0 0-16.067048 10.24l-146.236952 146.334476a49.005714 49.005714 0 0 0 69.217523 69.241905l62.902858-63.390476v272.627809a48.761905 48.761905 0 1 0 97.475047 0V166.083048l62.902857 63.390476a48.737524 48.737524 0 0 0 69.217524 0 48.761905 48.761905 0 0 0 0-69.241905z"></path></svg>' +export const UPLOAD_IMAGE_SVG = '<svg viewBox="0 0 1024 1024"><path d="M828.708571 585.045333a48.761905 48.761905 0 0 0-48.737523 48.761905v18.529524l-72.143238-72.167619a135.972571 135.972571 0 0 0-191.585524 0l-34.133334 34.133333-120.880762-120.953905a138.898286 138.898286 0 0 0-191.585523 0l-72.167619 72.167619V292.400762a48.786286 48.786286 0 0 1 48.761904-48.761905h341.23581a48.737524 48.737524 0 0 0 34.474667-83.285333 48.737524 48.737524 0 0 0-34.474667-14.287238H146.236952A146.212571 146.212571 0 0 0 0 292.400762v585.289143A146.358857 146.358857 0 0 0 146.236952 1024h584.996572a146.212571 146.212571 0 0 0 146.236952-146.310095V633.807238a48.786286 48.786286 0 0 0-48.761905-48.761905zM146.261333 926.45181a48.737524 48.737524 0 0 1-48.761904-48.761905v-174.128762l141.409523-141.458286a38.497524 38.497524 0 0 1 53.126096 0l154.526476 154.624 209.627428 209.724953H146.236952z m633.734096-48.761905c-0.073143 9.337905-3.145143 18.383238-8.777143 25.843809l-219.843048-220.94019 34.133333-34.133334a37.546667 37.546667 0 0 1 53.613715 0l140.873143 141.897143V877.714286zM1009.615238 160.231619L863.329524 13.897143a48.737524 48.737524 0 0 0-16.091429-10.24c-11.849143-4.87619-25.161143-4.87619-37.059047 0a48.761905 48.761905 0 0 0-16.067048 10.24l-146.236952 146.334476a49.005714 49.005714 0 0 0 69.217523 69.241905l62.902858-63.390476v272.627809a48.761905 48.761905 0 1 0 97.475047 0V166.083048l62.902857 63.390476a48.737524 48.737524 0 0 0 69.217524 0 48.761905 48.761905 0 0 0 0-69.241905z"></path></svg>' diff --git a/packages/upload-image-module/src/index.ts b/packages/upload-image-module/src/index.ts index 81f332799..cf9d02ca7 100644 --- a/packages/upload-image-module/src/index.ts +++ b/packages/upload-image-module/src/index.ts @@ -4,9 +4,9 @@ */ import './assets/index.less' - // 配置多语言 import './locale/index' import wangEditorUploadImageModule from './module/index' + export default wangEditorUploadImageModule diff --git a/packages/upload-image-module/src/locale/index.ts b/packages/upload-image-module/src/locale/index.ts index f3b7049e9..120f32884 100644 --- a/packages/upload-image-module/src/locale/index.ts +++ b/packages/upload-image-module/src/locale/index.ts @@ -4,6 +4,7 @@ */ import { i18nAddResources } from '@wangeditor-next/core' + import enResources from './en' import zhResources from './zh-CN' diff --git a/packages/upload-image-module/src/module/index.ts b/packages/upload-image-module/src/module/index.ts index ac8a04450..aef544f28 100644 --- a/packages/upload-image-module/src/module/index.ts +++ b/packages/upload-image-module/src/module/index.ts @@ -4,8 +4,9 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import withUploadImage from './plugin' + import { uploadImageMenuConf } from './menu/index' +import withUploadImage from './plugin' const uploadImage: Partial<IModuleConf> = { menus: [uploadImageMenuConf], diff --git a/packages/upload-image-module/src/module/menu/UploadImageMenu.ts b/packages/upload-image-module/src/module/menu/UploadImageMenu.ts index 76072f3fb..700dd022a 100644 --- a/packages/upload-image-module/src/module/menu/UploadImageMenu.ts +++ b/packages/upload-image-module/src/module/menu/UploadImageMenu.ts @@ -3,16 +3,19 @@ * @author wangfupeng */ -import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' import { insertImageNode, isInsertImageMenuDisabled } from '@wangeditor-next/basic-modules' +import { IButtonMenu, IDomEditor, t } from '@wangeditor-next/core' + import { UPLOAD_IMAGE_SVG } from '../../constants/svg' import $ from '../../utils/dom' -import { IUploadConfigForImage } from './config' import uploadImages from '../upload-images' +import { IUploadConfigForImage } from './config' class UploadImage implements IButtonMenu { readonly title = t('uploadImgModule.uploadImage') + readonly iconSvg = UPLOAD_IMAGE_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -45,6 +48,7 @@ class UploadImage implements IButtonMenu { // 设置选择文件的类型 let acceptAttr = '' + if (allowedFileTypes.length > 0) { acceptAttr = `accept="${allowedFileTypes.join(', ')}"` } @@ -52,12 +56,14 @@ class UploadImage implements IButtonMenu { // 添加 file input(每次重新创建 input) const $body = $('body') const $inputFile = $(`<input type="file" ${acceptAttr} multiple/>`) + $inputFile.hide() $body.append($inputFile) $inputFile.click() // 选中文件 $inputFile.on('change', () => { const files = ($inputFile[0] as HTMLInputElement).files + uploadImages(editor, files) // 上传文件 }) } diff --git a/packages/upload-image-module/src/module/menu/index.ts b/packages/upload-image-module/src/module/menu/index.ts index 21c880cb3..2be12a638 100644 --- a/packages/upload-image-module/src/module/menu/index.ts +++ b/packages/upload-image-module/src/module/menu/index.ts @@ -3,8 +3,8 @@ * @author wangfupeng */ -import UploadImageMenu from './UploadImageMenu' import { genUploadImageConfig } from './config' +import UploadImageMenu from './UploadImageMenu' export const uploadImageMenuConf = { key: 'uploadImage', diff --git a/packages/upload-image-module/src/module/plugin.ts b/packages/upload-image-module/src/module/plugin.ts index 8e97a2368..e7d53f46d 100644 --- a/packages/upload-image-module/src/module/plugin.ts +++ b/packages/upload-image-module/src/module/plugin.ts @@ -3,8 +3,9 @@ * @author wangfupeng */ -import { IDomEditor } from '@wangeditor-next/core' import { isInsertImageMenuDisabled } from '@wangeditor-next/basic-modules' +import { IDomEditor } from '@wangeditor-next/core' + import uploadImages from './upload-images' function withUploadImage<T extends IDomEditor>(editor: T): T { @@ -20,6 +21,7 @@ function withUploadImage<T extends IDomEditor>(editor: T): T { // 如有 text ,则优先粘贴 text const text = data.getData('text/plain') + if (text) { insertData(data) return @@ -27,6 +29,7 @@ function withUploadImage<T extends IDomEditor>(editor: T): T { // 获取文件 const { files } = data + if (files.length <= 0) { insertData(data) return @@ -34,8 +37,9 @@ function withUploadImage<T extends IDomEditor>(editor: T): T { // 判断是否有图片文件(可能是其他类型的文件) const fileList = Array.prototype.slice.call(files) - let _hasImageFiles = fileList.some(file => { + const _hasImageFiles = fileList.some(file => { const [mime] = file.type.split('/') + return mime === 'image' }) diff --git a/packages/upload-image-module/src/module/upload-images.ts b/packages/upload-image-module/src/module/upload-images.ts index ea22eb1fe..8c8e56de2 100644 --- a/packages/upload-image-module/src/module/upload-images.ts +++ b/packages/upload-image-module/src/module/upload-images.ts @@ -4,8 +4,9 @@ */ import Uppy, { UppyFile } from '@uppy/core' -import { IDomEditor, createUploader } from '@wangeditor-next/core' import { insertImageNode } from '@wangeditor-next/basic-modules' +import { createUploader, IDomEditor } from '@wangeditor-next/core' + import { IUploadConfigForImage } from './menu/config' // 存储 editor uppy 的关系 - 缓存 uppy ,不重复创建 @@ -18,10 +19,13 @@ const EDITOR_TO_UPPY_MAP = new WeakMap<IDomEditor, Uppy>() function getUppy(editor: IDomEditor): Uppy { // 从缓存中获取 let uppy = EDITOR_TO_UPPY_MAP.get(editor) - if (uppy != null) return uppy + + if (uppy != null) { return uppy } const menuConfig = getMenuConfig(editor) - const { onSuccess, onProgress, onFailed, customInsert, onError } = menuConfig + const { + onSuccess, onProgress, onFailed, customInsert, onError, + } = menuConfig // 上传完成之后 const successHandler = (file: UppyFile, res: any) => { @@ -37,7 +41,8 @@ function getUppy(editor: IDomEditor): Uppy { return } - let { errno = 1, data = {} } = res + const { errno = 1, data = {} } = res + if (errno !== 0) { // failed 回调 onFailed(file, res) @@ -49,11 +54,13 @@ function getUppy(editor: IDomEditor): Uppy { data.forEach((item: { url: string; alt?: string; href?: string }) => { const { url = '', alt = '', href = '' } = item // 使用 basic-module 的 insertImageNode 方法插入图片,其中有用户配置的校验和 callback + insertImageNode(editor, url, alt, href) }) } else { // 返回的对象 const { url = '', alt = '', href = '' } = data + insertImageNode(editor, url, alt, href) } @@ -100,12 +107,15 @@ function getMenuConfig(editor: IDomEditor) { async function insertBase64(editor: IDomEditor, file: File) { return new Promise(resolve => { const reader = new FileReader() + reader.readAsDataURL(file) reader.onload = () => { const { result } = reader - if (!result) return + + if (!result) { return } const src = result.toString() - let href = src.indexOf('data:image') === 0 ? '' : src // base64 格式则不设置 href + const href = src.indexOf('data:image') === 0 ? '' : src // base64 格式则不设置 href + insertImageNode(editor, src, file.name, href) resolve('ok') @@ -122,6 +132,7 @@ async function uploadFile(editor: IDomEditor, file: File) { const uppy = getUppy(editor) const { name, type, size } = file + uppy.addFile({ name, type, @@ -137,7 +148,7 @@ async function uploadFile(editor: IDomEditor, file: File) { * @param files files */ export default async function (editor: IDomEditor, files: FileList | null) { - if (files == null) return + if (files == null) { return } const fileList = Array.prototype.slice.call(files) // 获取菜单配置 @@ -146,6 +157,7 @@ export default async function (editor: IDomEditor, files: FileList | null) { // 按顺序上传 for await (const file of fileList) { const size = file.size // size kb + if (base64LimitSize && size <= base64LimitSize) { // 允许 base64 ,而且 size 在 base64 限制之内,则插入 base64 格式 await insertBase64(editor, file) diff --git a/packages/upload-image-module/src/utils/dom.ts b/packages/upload-image-module/src/utils/dom.ts index 195645487..3ab5e1876 100644 --- a/packages/upload-image-module/src/utils/dom.ts +++ b/packages/upload-image-module/src/utils/dom.ts @@ -3,14 +3,17 @@ * @author wangfupeng */ -import $, { append, on, remove, val, click, hide } from 'dom7' +import $, { + append, click, hide, on, remove, val, +} from 'dom7' + export { Dom7Array } from 'dom7' -if (append) $.fn.append = append -if (on) $.fn.on = on -if (remove) $.fn.remove = remove -if (val) $.fn.val = val -if (click) $.fn.click = click -if (hide) $.fn.hide = hide +if (append) { $.fn.append = append } +if (on) { $.fn.on = on } +if (remove) { $.fn.remove = remove } +if (val) { $.fn.val = val } +if (click) { $.fn.click = click } +if (hide) { $.fn.hide = hide } export default $ diff --git a/packages/video-module/__tests__/elem-to-html.test.ts b/packages/video-module/__tests__/elem-to-html.test.ts index 058d4d992..f3c61fe5b 100644 --- a/packages/video-module/__tests__/elem-to-html.test.ts +++ b/packages/video-module/__tests__/elem-to-html.test.ts @@ -3,7 +3,7 @@ * @author luochao */ -import videoModule from '../src/' +import videoModule from '../src' const videoToHtmlConf = videoModule.elemsToHtml![0] @@ -24,7 +24,7 @@ describe('videoModule module', () => { const res = videoToHtmlConf.elemToHtml(element, '') expect(res).toEqual( - '<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">\n<video poster="xxx.png" controls="true" width="auto" height="auto" style=""><source src="test.mp4" type="video/mp4"/></video>\n</div>' + '<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">\n<video poster="xxx.png" controls="true" width="auto" height="auto" style=""><source src="test.mp4" type="video/mp4"/></video>\n</div>', ) }) @@ -40,7 +40,7 @@ describe('videoModule module', () => { const res = videoToHtmlConf.elemToHtml(element, '') expect(res).toEqual( - '<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">\n<iframe src="test.mp4" width="500" height="300" style=""></iframe>\n</div>' + '<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">\n<iframe src="test.mp4" width="500" height="300" style=""></iframe>\n</div>', ) }) }) diff --git a/packages/video-module/__tests__/helpler.test.ts b/packages/video-module/__tests__/helpler.test.ts index 77b8f124f..29ca6a34b 100644 --- a/packages/video-module/__tests__/helpler.test.ts +++ b/packages/video-module/__tests__/helpler.test.ts @@ -1,12 +1,14 @@ +import nock from 'nock' +import * as slate from 'slate' + import createEditor from '../../../tests/utils/create-editor' import insertVideo from '../src/module/helper/insert-video' import uploadVideos from '../src/module/helper/upload-videos' -import * as slate from 'slate' -import nock from 'nock' const server = 'https://fake-endpoint.wangeditor-v5.com' let editor: ReturnType<typeof createEditor> + describe('Video module helper', () => { beforeEach(() => { editor = createEditor() @@ -28,6 +30,7 @@ describe('Video module helper', () => { }, }) const fn = jest.fn() + editor.alert = fn await insertVideo(editor, 'test.mp4', 'xxx.png') @@ -51,6 +54,7 @@ describe('Video module helper', () => { test('it should invoke slate insertNodes method if give right src', done => { const fn = jest.fn() + jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(fn) insertVideo(editor, 'test.mp4', 'xxx.png').then(() => { @@ -82,6 +86,7 @@ describe('Video module helper', () => { test('it should parse iframe if give iframe element', done => { const fn = jest.fn() + jest.spyOn(slate.Transforms, 'insertNodes').mockImplementation(fn) insertVideo(editor, '<iframe src="test.mp4"></iframe>').then(() => { @@ -117,6 +122,7 @@ describe('Video module helper', () => { test('it should invoke onSuccess callback if give the option when create editor', async () => { const fn = jest.fn() + nock(server) .defaultReplyHeaders({ 'access-control-allow-method': 'POST', @@ -145,6 +151,7 @@ describe('Video module helper', () => { test('it should invoke onProgress callback and show progress bar if uploading', async () => { const mockOnProgress = jest.fn() + nock(server) .defaultReplyHeaders({ 'access-control-allow-method': 'POST', @@ -167,6 +174,7 @@ describe('Video module helper', () => { }) const mockShowProgressBar = jest.fn() + editor.showProgressBar = mockShowProgressBar await uploadVideos(editor, [new File(['test123'], 'foo.jpg')] as unknown as FileList) @@ -177,6 +185,7 @@ describe('Video module helper', () => { test('it should invoke onError callback if upload failed', () => { const fn = jest.fn() + nock(server) .defaultReplyHeaders({ 'access-control-allow-method': 'POST', @@ -205,6 +214,7 @@ describe('Video module helper', () => { test('it should invoke onFail callback if upload result with error', async () => { const fn = jest.fn() + nock(server) .defaultReplyHeaders({ 'access-control-allow-method': 'POST', @@ -233,6 +243,7 @@ describe('Video module helper', () => { test('it should invoke customInsert callback if upload successfully', async () => { const fn = jest.fn() + nock(server) .defaultReplyHeaders({ 'access-control-allow-method': 'POST', diff --git a/packages/video-module/__tests__/menu/insert-video-menu.test.ts b/packages/video-module/__tests__/menu/insert-video-menu.test.ts index 951d05750..60a590f62 100644 --- a/packages/video-module/__tests__/menu/insert-video-menu.test.ts +++ b/packages/video-module/__tests__/menu/insert-video-menu.test.ts @@ -3,18 +3,19 @@ * @author luochao */ +import * as core from '@wangeditor-next/core' +import * as slate from 'slate' + import { isHTMLElememt } from '../../../../packages/core/src/utils/dom' import createEditor from '../../../../tests/utils/create-editor' import InsertVideoMenu from '../../src/module/menu/InsertVideoMenu' -import * as core from '@wangeditor-next/core' -import * as slate from 'slate' function setEditorSelection( editor: core.IDomEditor, selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } diff --git a/packages/video-module/__tests__/menu/upload-video-menu.test.ts b/packages/video-module/__tests__/menu/upload-video-menu.test.ts index 53188db63..70c5d9252 100644 --- a/packages/video-module/__tests__/menu/upload-video-menu.test.ts +++ b/packages/video-module/__tests__/menu/upload-video-menu.test.ts @@ -3,10 +3,11 @@ * @author luochao */ -import createEditor from '../../../../tests/utils/create-editor' -import UploadVideoMenu from '../../src/module/menu/UploadVideoMenu' import * as core from '@wangeditor-next/core' import * as slate from 'slate' + +import createEditor from '../../../../tests/utils/create-editor' +import UploadVideoMenu from '../../src/module/menu/UploadVideoMenu' import $ from '../../src/utils/dom' function setEditorSelection( @@ -14,7 +15,7 @@ function setEditorSelection( selection: slate.Selection = { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, - } + }, ) { editor.selection = selection } diff --git a/packages/video-module/__tests__/parse-html.test.ts b/packages/video-module/__tests__/parse-html.test.ts index b92548d68..cda5840e1 100644 --- a/packages/video-module/__tests__/parse-html.test.ts +++ b/packages/video-module/__tests__/parse-html.test.ts @@ -4,6 +4,7 @@ */ import { $ } from 'dom7' + import createEditor from '../../../tests/utils/create-editor' import videoModule from '../src' @@ -20,8 +21,9 @@ describe('video - pre parse html', () => { // pre parse const res = preParseHtmlConf.preParseHtml($iframe[0]) + expect(res.outerHTML).toBe( - '<div data-w-e-type="video" data-w-e-is-void="" style="text-align: center;"><iframe></iframe></div>' + '<div data-w-e-type="video" data-w-e-is-void="" style="text-align: center;"><iframe></iframe></div>', ) }) @@ -33,8 +35,9 @@ describe('video - pre parse html', () => { // pre parse const res = preParseHtmlConf.preParseHtml($video[0]) + expect(res.outerHTML).toBe( - '<div data-w-e-type="video" data-w-e-is-void="" style="text-align: center;"><video></video></div>' + '<div data-w-e-type="video" data-w-e-is-void="" style="text-align: center;"><video></video></div>', ) }) @@ -46,8 +49,9 @@ describe('video - pre parse html', () => { // pre parse const res = preParseHtmlConf.preParseHtml($video[0]) + expect(res.outerHTML).toBe( - '<div data-w-e-type="video" data-w-e-is-void="" style="text-align: center;"><video></video></div>' + '<div data-w-e-type="video" data-w-e-is-void="" style="text-align: center;"><video></video></div>', ) }) }) @@ -58,7 +62,7 @@ describe('video - parse html', () => { it('iframe', () => { const iframeHtml = '<iframe src="xxx" width="500" height="300"></iframe>' const $container = $( - `<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">${iframeHtml}</div>` + `<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">${iframeHtml}</div>`, ) // match selector @@ -81,7 +85,7 @@ describe('video - parse html', () => { const poster = 'xxx.png' const videoHtml = `<video poster="${poster}"><source src="${src}"/></video>` const $container = $( - `<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">${videoHtml}</div>` + `<div data-w-e-type="video" data-w-e-is-void style="text-align: center;">${videoHtml}</div>`, ) // match selector diff --git a/packages/video-module/__tests__/plugin.test.ts b/packages/video-module/__tests__/plugin.test.ts index ff36b69b2..85b52b6f3 100644 --- a/packages/video-module/__tests__/plugin.test.ts +++ b/packages/video-module/__tests__/plugin.test.ts @@ -3,8 +3,8 @@ * @author luochao */ -import withVideo from '../src/module/plugin' import createEditor from '../../../tests/utils/create-editor' +import withVideo from '../src/module/plugin' describe('videoModule module', () => { describe('module plugin', () => { @@ -27,12 +27,14 @@ describe('videoModule module', () => { src: 'test.mp4', children: [], } + expect(newEditor.isVoid(videoElem)).toBeTruthy() }) test('使用 withVideo 插件后,对于非 video 元素,直接调用 original isVoid 方法', () => { const editor = createEditor() const fn = jest.fn() + editor.isVoid = fn const newEditor = withVideo(editor) @@ -40,6 +42,7 @@ describe('videoModule module', () => { type: 'paragraph', children: [{ text: '' }], } + newEditor.isVoid(videoElem) expect(fn).toBeCalled() diff --git a/packages/video-module/__tests__/render-elem.test.ts b/packages/video-module/__tests__/render-elem.test.ts index 4e1d190ea..fccc5cf4f 100644 --- a/packages/video-module/__tests__/render-elem.test.ts +++ b/packages/video-module/__tests__/render-elem.test.ts @@ -12,8 +12,11 @@ describe('video module - render elem', () => { it('render video elem', () => { expect(renderVideoConf.type).toBe('video') - const elem = { type: 'video', src: 'test.mp4', poster: 'xxx.png', children: [] } + const elem = { + type: 'video', src: 'test.mp4', poster: 'xxx.png', children: [], + } const vnode = renderVideoConf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('div') }) @@ -22,6 +25,7 @@ describe('video module - render elem', () => { const elem = { type: 'video', src: '<iframe src="test.mp4"></iframe>', children: [] } const vnode = renderVideoConf.renderElem(elem, null, editor) + expect(vnode.sel).toBe('div') }) }) diff --git a/packages/video-module/rollup.config.js b/packages/video-module/rollup.config.js index 38b2b7a10..2adab3754 100644 --- a/packages/video-module/rollup.config.js +++ b/packages/video-module/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorVideoModule' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/video-module/src/constants/svg.ts b/packages/video-module/src/constants/svg.ts index 4be7e46f3..48fc157e9 100644 --- a/packages/video-module/src/constants/svg.ts +++ b/packages/video-module/src/constants/svg.ts @@ -10,16 +10,13 @@ */ // 视频 -export const VIDEO_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M981.184 160.096C837.568 139.456 678.848 128 512 128S186.432 139.456 42.816 160.096C15.296 267.808 0 386.848 0 512s15.264 244.16 42.816 351.904C186.464 884.544 345.152 896 512 896s325.568-11.456 469.184-32.096C1008.704 756.192 1024 637.152 1024 512s-15.264-244.16-42.816-351.904zM384 704V320l320 192-320 192z"></path></svg>' +export const VIDEO_SVG = '<svg viewBox="0 0 1024 1024"><path d="M981.184 160.096C837.568 139.456 678.848 128 512 128S186.432 139.456 42.816 160.096C15.296 267.808 0 386.848 0 512s15.264 244.16 42.816 351.904C186.464 884.544 345.152 896 512 896s325.568-11.456 469.184-32.096C1008.704 756.192 1024 637.152 1024 512s-15.264-244.16-42.816-351.904zM384 704V320l320 192-320 192z"></path></svg>' // 上传视频 -export const UPLOAD_VIDEO_SVG = - '<svg viewBox="0 0 1056 1024"><path d="M805.902261 521.819882a251.441452 251.441452 0 0 0-251.011972 246.600033 251.051015 251.051015 0 1 0 502.023944 8.823877 253.237463 253.237463 0 0 0-251.011972-255.42391z m59.463561 240.001647v129.898403h-116.701631v-129.898403h-44.041298l101.279368-103.504859 101.279368 103.504859z" p-id="6802"></path><path d="M788.254507 0.000781H99.094092A98.663439 98.663439 0 0 0 0.001171 99.093701v590.067495a98.663439 98.663439 0 0 0 99.092921 99.092921h411.7549a266.434235 266.434235 0 0 1-2.186448-41.815807 275.843767 275.843767 0 0 1 275.180024-270.729042 270.650955 270.650955 0 0 1 103.504859 19.834201V99.093701A101.51363 101.51363 0 0 0 788.254507 0.000781zM295.054441 640.747004V147.507894l394.146189 246.600033z"></path></svg>' +export const UPLOAD_VIDEO_SVG = '<svg viewBox="0 0 1056 1024"><path d="M805.902261 521.819882a251.441452 251.441452 0 0 0-251.011972 246.600033 251.051015 251.051015 0 1 0 502.023944 8.823877 253.237463 253.237463 0 0 0-251.011972-255.42391z m59.463561 240.001647v129.898403h-116.701631v-129.898403h-44.041298l101.279368-103.504859 101.279368 103.504859z" p-id="6802"></path><path d="M788.254507 0.000781H99.094092A98.663439 98.663439 0 0 0 0.001171 99.093701v590.067495a98.663439 98.663439 0 0 0 99.092921 99.092921h411.7549a266.434235 266.434235 0 0 1-2.186448-41.815807 275.843767 275.843767 0 0 1 275.180024-270.729042 270.650955 270.650955 0 0 1 103.504859 19.834201V99.093701A101.51363 101.51363 0 0 0 788.254507 0.000781zM295.054441 640.747004V147.507894l394.146189 246.600033z"></path></svg>' // 编辑 -export const PENCIL_SVG = - '<svg viewBox="0 0 1024 1024"><path d="M864 0a160 160 0 0 1 128 256l-64 64-224-224 64-64c26.752-20.096 59.968-32 96-32zM64 736l-64 288 288-64 592-592-224-224L64 736z m651.584-372.416l-448 448-55.168-55.168 448-448 55.168 55.168z"></path></svg>' +export const PENCIL_SVG = '<svg viewBox="0 0 1024 1024"><path d="M864 0a160 160 0 0 1 128 256l-64 64-224-224 64-64c26.752-20.096 59.968-32 96-32zM64 736l-64 288 288-64 592-592-224-224L64 736z m651.584-372.416l-448 448-55.168-55.168 448-448 55.168 55.168z"></path></svg>' // // 垃圾桶(删除) // export const TRASH_SVG = diff --git a/packages/video-module/src/index.ts b/packages/video-module/src/index.ts index 6df002395..b61cf3aa5 100644 --- a/packages/video-module/src/index.ts +++ b/packages/video-module/src/index.ts @@ -4,9 +4,9 @@ */ import './assets/index.less' - // 配置多语言 import './locale/index' import wangEditorVideoModule from './module/index' + export default wangEditorVideoModule diff --git a/packages/video-module/src/locale/index.ts b/packages/video-module/src/locale/index.ts index f3b7049e9..120f32884 100644 --- a/packages/video-module/src/locale/index.ts +++ b/packages/video-module/src/locale/index.ts @@ -4,6 +4,7 @@ */ import { i18nAddResources } from '@wangeditor-next/core' + import enResources from './en' import zhResources from './zh-CN' diff --git a/packages/video-module/src/module/custom-types.ts b/packages/video-module/src/module/custom-types.ts index e89da8794..9e3a354f5 100644 --- a/packages/video-module/src/module/custom-types.ts +++ b/packages/video-module/src/module/custom-types.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -//【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts +// 【注意】需要把自定义的 Element 引入到最外层的 custom-types.d.ts type EmptyText = { text: '' diff --git a/packages/video-module/src/module/elem-to-html.ts b/packages/video-module/src/module/elem-to-html.ts index a116b5faa..4e597a695 100644 --- a/packages/video-module/src/module/elem-to-html.ts +++ b/packages/video-module/src/module/elem-to-html.ts @@ -4,8 +4,9 @@ */ import { Element } from 'slate' -import { VideoElement } from './custom-types' + import { genSizeStyledIframeHtml } from '../utils/dom' +import { VideoElement } from './custom-types' function videoToHtml(elemNode: Element, childrenHtml?: string): string { const { @@ -21,13 +22,15 @@ function videoToHtml(elemNode: Element, childrenHtml?: string): string { if (src.trim().indexOf('<iframe ') === 0) { // iframe 形式 const iframeHtml = genSizeStyledIframeHtml(src, width, height, style) + res += iframeHtml } else { // 其他,mp4 等 url 格式 const { width: styleWidth = '', height: styleHeight = '' } = style let styleStr = '' - if (styleWidth) styleStr += `width: ${styleWidth};` - if (styleHeight) styleStr += `height: ${styleHeight};` + + if (styleWidth) { styleStr += `width: ${styleWidth};` } + if (styleHeight) { styleStr += `height: ${styleHeight};` } res += `<video poster="${poster}" controls="true" width="${width}" height="${height}" style="${styleStr}"><source src="${src}" type="video/mp4"/></video>` } res += '\n</div>' diff --git a/packages/video-module/src/module/helper/insert-video.ts b/packages/video-module/src/module/helper/insert-video.ts index ef423f116..611cedec9 100644 --- a/packages/video-module/src/module/helper/insert-video.ts +++ b/packages/video-module/src/module/helper/insert-video.ts @@ -3,8 +3,9 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Transforms } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' + import { replaceSymbols } from '../../utils/util' import { VideoElement } from '../custom-types' @@ -19,9 +20,9 @@ export default async function ( src: string, poster = '', width = '', - height = '' + height = '', ) { - if (!src) return + if (!src) { return } // 还原选区 editor.restoreSelection() @@ -29,6 +30,7 @@ export default async function ( // 校验 const { onInsertedVideo, checkVideo, parseVideoSrc } = editor.getMenuConfig('insertVideo') const checkRes = await checkVideo(src, poster) + if (typeof checkRes === 'string') { // 校验失败,给出提示 editor.alert(checkRes, 'error') diff --git a/packages/video-module/src/module/helper/upload-videos.ts b/packages/video-module/src/module/helper/upload-videos.ts index 404889971..6d2755aae 100644 --- a/packages/video-module/src/module/helper/upload-videos.ts +++ b/packages/video-module/src/module/helper/upload-videos.ts @@ -4,9 +4,10 @@ */ import Uppy, { UppyFile } from '@uppy/core' -import { IDomEditor, createUploader } from '@wangeditor-next/core' -import insertVideo from './insert-video' +import { createUploader, IDomEditor } from '@wangeditor-next/core' + import { IUploadConfigForVideo } from '../menu/config' +import insertVideo from './insert-video' function getMenuConfig(editor: IDomEditor): IUploadConfigForVideo { // 获取配置,见 `./config.js` @@ -23,10 +24,13 @@ const EDITOR_TO_UPPY_MAP = new WeakMap<IDomEditor, Uppy>() function getUppy(editor: IDomEditor): Uppy { // 从缓存中获取 let uppy = EDITOR_TO_UPPY_MAP.get(editor) - if (uppy != null) return uppy + + if (uppy != null) { return uppy } const menuConfig = getMenuConfig(editor) - const { onSuccess, onProgress, onFailed, customInsert, onError } = menuConfig + const { + onSuccess, onProgress, onFailed, customInsert, onError, + } = menuConfig // 上传完成之后 const successHandler = (file: UppyFile, res: any) => { @@ -42,7 +46,8 @@ function getUppy(editor: IDomEditor): Uppy { return } - let { errno = 1, data = {} } = res + const { errno = 1, data = {} } = res + if (errno !== 0) { // failed 回调 onFailed(file, res) @@ -50,6 +55,7 @@ function getUppy(editor: IDomEditor): Uppy { } const { url = '', poster = '' } = data + insertVideo(editor, url, poster) // success 回调 @@ -91,6 +97,7 @@ async function uploadFile(editor: IDomEditor, file: File) { const uppy = getUppy(editor) const { name, type, size } = file + uppy.addFile({ name, type, @@ -101,7 +108,7 @@ async function uploadFile(editor: IDomEditor, file: File) { } export default async function (editor: IDomEditor, files: FileList | null) { - if (files == null) return + if (files == null) { return } const fileList = Array.prototype.slice.call(files) // 获取菜单配置 diff --git a/packages/video-module/src/module/index.ts b/packages/video-module/src/module/index.ts index fef144656..0cccb26ff 100644 --- a/packages/video-module/src/module/index.ts +++ b/packages/video-module/src/module/index.ts @@ -4,17 +4,18 @@ */ import { IModuleConf } from '@wangeditor-next/core' -import withVideo from './plugin' -import { renderVideoConf } from './render-elem' + import { videoToHtmlConf } from './elem-to-html' -import { preParseHtmlConf } from './pre-parse-html' -import { parseHtmlConf } from './parse-elem-html' import { - insertVideoMenuConf, - uploadVideoMenuConf, editorVideoSizeMenuConf, editorVideoSrcMenuConf, + insertVideoMenuConf, + uploadVideoMenuConf, } from './menu/index' +import { parseHtmlConf } from './parse-elem-html' +import withVideo from './plugin' +import { preParseHtmlConf } from './pre-parse-html' +import { renderVideoConf } from './render-elem' const video: Partial<IModuleConf> = { renderElems: [renderVideoConf], diff --git a/packages/video-module/src/module/menu/EditVideoSizeMenu.ts b/packages/video-module/src/module/menu/EditVideoSizeMenu.ts index b1d69938c..613f00970 100644 --- a/packages/video-module/src/module/menu/EditVideoSizeMenu.ts +++ b/packages/video-module/src/module/menu/EditVideoSizeMenu.ts @@ -3,15 +3,16 @@ * @author wangfupeng */ -import { Node as SlateNode, Transforms } from 'slate' import { - IModalMenu, - IDomEditor, DomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Node as SlateNode, Transforms } from 'slate' + import $, { Dom7Array, DOMElement } from '../../utils/dom' import { genRandomStr } from '../../utils/util' import { VideoElement } from '../custom-types' @@ -25,12 +26,19 @@ function genDomID(): string { class EditorVideoSizeMenu implements IModalMenu { readonly title = t('videoModule.editSize') + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 320 + private $content: Dom7Array | null = null + private readonly widthInputId = genDomID() + private readonly heightInputId = genDomID() + private readonly buttonId = genDomID() private getSelectedVideoNode(editor: IDomEditor): SlateNode | null { @@ -53,9 +61,10 @@ class EditorVideoSizeMenu implements IModalMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const videoNode = this.getSelectedVideoNode(editor) + if (videoNode == null) { // 选区未处于 video node ,则禁用 return true @@ -73,13 +82,13 @@ class EditorVideoSizeMenu implements IModalMenu { const [widthContainerElem, inputWidthElem] = genModalInputElems( t('videoModule.width'), widthInputId, - 'auto' + 'auto', ) const $inputWidth = $(inputWidthElem) const [heightContainerElem, inputHeightElem] = genModalInputElems( t('videoModule.height'), heightInputId, - 'auto' + 'auto', ) const $inputHeight = $(inputHeightElem) const [buttonContainerElem] = genModalButtonElems(buttonId, t('videoModule.ok')) @@ -104,13 +113,13 @@ class EditorVideoSizeMenu implements IModalMenu { if (isPercentage(rawWidth)) { width = rawWidth } else if (isNumeric(rawWidth)) { - width = parseInt(rawWidth) + 'px' + width = `${parseInt(rawWidth)}px` } if (isPercentage(rawHeight)) { height = rawHeight } else if (isNumeric(rawHeight)) { - height = parseInt(rawHeight) + 'px' + height = `${parseInt(rawHeight)}px` } const { style = {} } = videoNode as VideoElement @@ -119,11 +128,12 @@ class EditorVideoSizeMenu implements IModalMenu { const props: Partial<VideoElement> = { style: { ...style, - width: width, - height: height, + width, + height, }, } // 修改尺寸 + Transforms.setNodes(editor, props, { match: n => DomEditor.checkNodeType(n, 'video'), }) @@ -143,11 +153,13 @@ class EditorVideoSizeMenu implements IModalMenu { $content.append(buttonContainerElem) const videoNode = this.getSelectedVideoNode(editor) as VideoElement - if (videoNode == null) return $content[0] + + if (videoNode == null) { return $content[0] } // 初始化 input 值 const { style = {} } = videoNode const { width = '', height = '' } = style + $inputWidth.val(width) $inputHeight.val(height) setTimeout(() => { diff --git a/packages/video-module/src/module/menu/EditVideoSrcMenu.ts b/packages/video-module/src/module/menu/EditVideoSrcMenu.ts index 67a15996a..773c2d6c6 100644 --- a/packages/video-module/src/module/menu/EditVideoSrcMenu.ts +++ b/packages/video-module/src/module/menu/EditVideoSrcMenu.ts @@ -3,18 +3,19 @@ * @author */ -import { Node as SlateNode, Transforms } from 'slate' import { - IModalMenu, - IDomEditor, DomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Node as SlateNode, Transforms } from 'slate' + +import { PENCIL_SVG } from '../../constants/svg' import $, { Dom7Array, DOMElement } from '../../utils/dom' import { genRandomStr } from '../../utils/util' -import { PENCIL_SVG } from '../../constants/svg' import { VideoElement } from '../custom-types' /** @@ -26,13 +27,21 @@ function genDomID(): string { class Editvideo implements IModalMenu { readonly title = t('videoModule.edit') + readonly iconSvg = PENCIL_SVG + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 300 + private $content: Dom7Array | null = null + private readonly srcInputId = genDomID() + private readonly posterInputId = genDomID() + private readonly buttonId = genDomID() private getSelectedVideoNode(editor: IDomEditor): SlateNode | null { @@ -55,9 +64,10 @@ class Editvideo implements IModalMenu { } isDisabled(editor: IDomEditor): boolean { - if (editor.selection == null) return true + if (editor.selection == null) { return true } const videoNode = this.getSelectedVideoNode(editor) + if (videoNode == null) { // 选区未处于 video node ,则禁用 return true @@ -76,12 +86,12 @@ class Editvideo implements IModalMenu { const [srcContainerElem, inputSrcElem] = genModalInputElems( t('videoModule.videoSrc'), srcInputId, - t('videoModule.videoSrcPlaceHolder') + t('videoModule.videoSrcPlaceHolder'), ) const [posterContainerElem, inputPosterElem] = genModalInputElems( t('videoModule.videoPoster'), posterInputId, - t('videoModule.videoPosterPlaceHolder') + t('videoModule.videoPosterPlaceHolder'), ) const $inputSrc = $(inputSrcElem) const $inputPoster = $(inputPosterElem) @@ -101,8 +111,8 @@ class Editvideo implements IModalMenu { const videoId = genRandomStr('video-') const props: Partial<VideoElement> = { - src: src, - poster: poster, + src, + poster, key: videoId, } @@ -119,6 +129,7 @@ class Editvideo implements IModalMenu { } const $content = this.$content + $content.empty() // 先清空内容 // append inputs and button @@ -127,10 +138,12 @@ class Editvideo implements IModalMenu { $content.append(buttonContainerElem) const videoNode = this.getSelectedVideoNode(editor) as VideoElement - if (videoNode == null) return $content[0] + + if (videoNode == null) { return $content[0] } // 初始化 input 值 const { src = '', poster = '' } = videoNode + $inputSrc.val(src) $inputPoster.val(poster) diff --git a/packages/video-module/src/module/menu/InsertVideoMenu.ts b/packages/video-module/src/module/menu/InsertVideoMenu.ts index 1e45e6956..6b468d3f5 100644 --- a/packages/video-module/src/module/menu/InsertVideoMenu.ts +++ b/packages/video-module/src/module/menu/InsertVideoMenu.ts @@ -3,18 +3,19 @@ * @author wangfupeng */ -import { Range, Node } from 'slate' import { - IModalMenu, - IDomEditor, DomEditor, - genModalInputElems, genModalButtonElems, + genModalInputElems, + IDomEditor, + IModalMenu, t, } from '@wangeditor-next/core' +import { Node, Range } from 'slate' + +import { VIDEO_SVG } from '../../constants/svg' import $, { Dom7Array, DOMElement } from '../../utils/dom' import { genRandomStr } from '../../utils/util' -import { VIDEO_SVG } from '../../constants/svg' import insertVideo from '../helper/insert-video' /** @@ -26,13 +27,21 @@ function genDomID(): string { class InsertVideoMenu implements IModalMenu { readonly title = t('videoModule.insertVideo') + readonly iconSvg = VIDEO_SVG + readonly tag = 'button' + readonly showModal = true // 点击 button 时显示 modal + readonly modalWidth = 320 + private $content: Dom7Array | null = null + private readonly srcInputId = genDomID() + private readonly posterInputId = genDomID() + private readonly buttonId = genDomID() getValue(editor: IDomEditor): string | boolean { @@ -52,18 +61,21 @@ class InsertVideoMenu implements IModalMenu { isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用 + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } // 选区非折叠,禁用 const selectedElems = DomEditor.getSelectedElems(editor) const hasVoidOrPre = selectedElems.some(elem => { const type = DomEditor.getNodeType(elem) - if (type === 'pre') return true - if (type === 'list-item') return true - if (editor.isVoid(elem)) return true + + if (type === 'pre') { return true } + if (type === 'list-item') { return true } + if (editor.isVoid(elem)) { return true } return false }) - if (hasVoidOrPre) return true // void 或 pre ,禁用 + + if (hasVoidOrPre) { return true } // void 或 pre ,禁用 return false } @@ -79,12 +91,12 @@ class InsertVideoMenu implements IModalMenu { const [srcContainerElem, inputSrcElem] = genModalInputElems( t('videoModule.videoSrc'), srcInputId, - t('videoModule.videoSrcPlaceHolder') + t('videoModule.videoSrcPlaceHolder'), ) const [posterContainerElem, inputPosterElem] = genModalInputElems( t('videoModule.videoPoster'), posterInputId, - t('videoModule.videoPosterPlaceHolder') + t('videoModule.videoPosterPlaceHolder'), ) const $inputSrc = $(inputSrcElem) const $inputPoster = $(inputPosterElem) @@ -99,6 +111,7 @@ class InsertVideoMenu implements IModalMenu { e.preventDefault() const src = $content.find(`#${srcInputId}`).val().trim() const poster = $content.find(`#${posterInputId}`).val().trim() + await insertVideo(editor, src, poster) editor.hidePanelOrModal() // 隐藏 modal }) @@ -108,6 +121,7 @@ class InsertVideoMenu implements IModalMenu { } const $content = this.$content + $content.empty() // 先清空内容 // append inputs and button diff --git a/packages/video-module/src/module/menu/UploadVideoMenu.ts b/packages/video-module/src/module/menu/UploadVideoMenu.ts index 8953cc56b..a8e60dac6 100644 --- a/packages/video-module/src/module/menu/UploadVideoMenu.ts +++ b/packages/video-module/src/module/menu/UploadVideoMenu.ts @@ -3,17 +3,22 @@ * @author wangfupeng */ +import { + DomEditor, IButtonMenu, IDomEditor, t, +} from '@wangeditor-next/core' import { Range } from 'slate' -import { IButtonMenu, IDomEditor, DomEditor, t } from '@wangeditor-next/core' -import $ from '../../utils/dom' + import { UPLOAD_VIDEO_SVG } from '../../constants/svg' -import { IUploadConfigForVideo } from './config' +import $ from '../../utils/dom' import insertVideo from '../helper/insert-video' import uploadVideos from '../helper/upload-videos' +import { IUploadConfigForVideo } from './config' class UploadVideoMenu implements IButtonMenu { readonly title = t('videoModule.uploadVideo') + readonly iconSvg = UPLOAD_VIDEO_SVG + readonly tag = 'button' getValue(editor: IDomEditor): string | boolean { @@ -37,6 +42,7 @@ class UploadVideoMenu implements IButtonMenu { // 设置选择文件的类型 let acceptAttr = '' + if (allowedFileTypes.length > 0) { acceptAttr = `accept="${allowedFileTypes.join(', ')}"` } @@ -44,30 +50,35 @@ class UploadVideoMenu implements IButtonMenu { // 添加 file input(每次重新创建 input) const $body = $('body') const $inputFile = $(`<input type="file" ${acceptAttr} multiple/>`) + $inputFile.hide() $body.append($inputFile) $inputFile.click() // 选中文件 $inputFile.on('change', () => { const files = ($inputFile[0] as HTMLInputElement).files + uploadVideos(editor, files) // 上传文件 }) } isDisabled(editor: IDomEditor): boolean { const { selection } = editor - if (selection == null) return true - if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用 + + if (selection == null) { return true } + if (!Range.isCollapsed(selection)) { return true } // 选区非折叠,禁用 const selectedElems = DomEditor.getSelectedElems(editor) const hasVoidOrPre = selectedElems.some(elem => { const type = DomEditor.getNodeType(elem) - if (type === 'pre') return true - if (type === 'list-item') return true - if (editor.isVoid(elem)) return true + + if (type === 'pre') { return true } + if (type === 'list-item') { return true } + if (editor.isVoid(elem)) { return true } return false }) - if (hasVoidOrPre) return true // void 或 pre ,禁用 + + if (hasVoidOrPre) { return true } // void 或 pre ,禁用 return false } diff --git a/packages/video-module/src/module/menu/config.ts b/packages/video-module/src/module/menu/config.ts index 3da89f909..3981ff80d 100644 --- a/packages/video-module/src/module/menu/config.ts +++ b/packages/video-module/src/module/menu/config.ts @@ -4,6 +4,7 @@ */ import { IUploadConfig } from '@wangeditor-next/core' + import { VideoElement } from '../custom-types' type InsertFn = (src: string, poster: string) => void diff --git a/packages/video-module/src/module/menu/index.ts b/packages/video-module/src/module/menu/index.ts index 9efe4edb3..292cf8bac 100644 --- a/packages/video-module/src/module/menu/index.ts +++ b/packages/video-module/src/module/menu/index.ts @@ -3,12 +3,12 @@ * @author wangfupeng */ +import { genInsertVideoMenuConfig, genUploadVideoMenuConfig } from './config' +import EditorVideoSizeMenu from './EditVideoSizeMenu' +import EditorVideoSrcMenu from './EditVideoSrcMenu' import InsertVideoMenu from './InsertVideoMenu' // import DeleteVideoMenu from './DeleteVideoMenu' import UploadVideoMenu from './UploadVideoMenu' -import EditorVideoSizeMenu from './EditVideoSizeMenu' -import EditorVideoSrcMenu from './EditVideoSrcMenu' -import { genInsertVideoMenuConfig, genUploadVideoMenuConfig } from './config' export const insertVideoMenuConf = { key: 'insertVideo', diff --git a/packages/video-module/src/module/parse-elem-html.ts b/packages/video-module/src/module/parse-elem-html.ts index 923949587..63fa45958 100644 --- a/packages/video-module/src/module/parse-elem-html.ts +++ b/packages/video-module/src/module/parse-elem-html.ts @@ -3,18 +3,19 @@ * @author wangfupeng */ -import { Descendant } from 'slate' import { IDomEditor } from '@wangeditor-next/core' -import { VideoElement, videoStyle } from './custom-types' +import { Descendant } from 'slate' + import $, { DOMElement } from '../utils/dom' import { styleStringToObject } from '../utils/util' +import { VideoElement, videoStyle } from './custom-types' function genVideoElem( src: string, - poster: string = '', + poster = '', width = 'auto', height = 'auto', - style: videoStyle = {} + style: videoStyle = {}, ): VideoElement { return { type: 'video', @@ -37,6 +38,7 @@ function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor) // <iframe> 形式 const $iframe = $elem.find('iframe') + if ($iframe.length > 0) { width = $iframe.attr('width') || 'auto' height = $iframe.attr('height') || 'auto' @@ -48,10 +50,12 @@ function parseHtml(elem: DOMElement, children: Descendant[], editor: IDomEditor) // <video> 形式 const $video = $elem.find('video') + src = $video.attr('src') || '' if (!src) { if ($video.length > 0) { const $source = $video.find('source') + src = $source.attr('src') || '' } } diff --git a/packages/video-module/src/module/plugin.ts b/packages/video-module/src/module/plugin.ts index 188ad53a7..c76587961 100644 --- a/packages/video-module/src/module/plugin.ts +++ b/packages/video-module/src/module/plugin.ts @@ -3,8 +3,9 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Transforms } from 'slate' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' + import { CustomElement } from '../../../custom-types' function withVideo<T extends IDomEditor>(editor: T): T { @@ -30,6 +31,7 @@ function withVideo<T extends IDomEditor>(editor: T): T { if (type === 'video') { // -------------- video 是 editor 最后一个节点,需要后面插入 p -------------- const isLast = DomEditor.isLastNode(newEditor, node) + if (isLast) { Transforms.insertNodes(newEditor, DomEditor.genEmptyParagraph(), { at: [path[0] + 1] }) } diff --git a/packages/video-module/src/module/pre-parse-html.ts b/packages/video-module/src/module/pre-parse-html.ts index 673d921a7..66584acd0 100644 --- a/packages/video-module/src/module/pre-parse-html.ts +++ b/packages/video-module/src/module/pre-parse-html.ts @@ -3,7 +3,7 @@ * @author wangfupeng */ -import $, { getTagName, DOMElement } from '../utils/dom' +import $, { DOMElement, getTagName } from '../utils/dom' /** * pre-prase video ,兼容 V4 @@ -14,12 +14,15 @@ function preParse(elem: DOMElement): DOMElement { let $video = $elem const elemTagName = getTagName($elem) + if (elemTagName === 'p') { // v4 的 video 或 iframe 是被 p 包裹的 const children = $elem.children() + if (children.length === 1) { const firstChild = children[0] const firstChildTagName = firstChild.tagName.toLowerCase() + if (['iframe', 'video'].includes(firstChildTagName)) { // p 下面包含 iframe 或 video $video = $(firstChild) @@ -28,15 +31,18 @@ function preParse(elem: DOMElement): DOMElement { } const videoTagName = getTagName($video) - if (videoTagName !== 'iframe' && videoTagName !== 'video') return $video[0] + + if (videoTagName !== 'iframe' && videoTagName !== 'video') { return $video[0] } // 已经符合 V5 格式 const $parent = $video.parent() - if ($parent.attr('data-w-e-type') === 'video') return $video[0] + + if ($parent.attr('data-w-e-type') === 'video') { return $video[0] } const $container = $( - `<div data-w-e-type="video" data-w-e-is-void style="text-align: center;"></div>` + '<div data-w-e-type="video" data-w-e-is-void style="text-align: center;"></div>', ) + $container.append($video) return $container[0] diff --git a/packages/video-module/src/module/render-elem.tsx b/packages/video-module/src/module/render-elem.tsx index 06267b864..71fcf8b22 100644 --- a/packages/video-module/src/module/render-elem.tsx +++ b/packages/video-module/src/module/render-elem.tsx @@ -3,11 +3,12 @@ * @author wangfupeng */ +import { DomEditor, IDomEditor } from '@wangeditor-next/core' import { Element } from 'slate' import { h, jsx, VNode } from 'snabbdom' -import { IDomEditor, DomEditor } from '@wangeditor-next/core' -import { VideoElement } from './custom-types' + import { genSizeStyledIframeHtml } from '../utils/dom' +import { VideoElement } from './custom-types' function renderVideo(elemNode: Element, children: VNode[] | null, editor: IDomEditor): VNode { const { @@ -24,6 +25,7 @@ function renderVideo(elemNode: Element, children: VNode[] | null, editor: IDomEd const selected = DomEditor.isNodeSelected(editor, elemNode) let vnode: VNode + if (src.trim().indexOf('<iframe ') === 0) { // 增加尺寸样式 const iframeHtml = genSizeStyledIframeHtml(src, width, height, style) @@ -42,13 +44,14 @@ function renderVideo(elemNode: Element, children: VNode[] | null, editor: IDomEd const videoVnode = ( <video key={key} poster={poster} controls style={style}> <source src={src} type="video/mp4" /> - {`Sorry, your browser doesn't support embedded videos.\n 抱歉,浏览器不支持 video 视频`} + {'Sorry, your browser doesn\'t support embedded videos.\n 抱歉,浏览器不支持 video 视频'} </video> ) // @ts-ignore 添加尺寸 - if (width !== 'auto') videoVnode.data.width = width + + if (width !== 'auto') { videoVnode.data.width = width } // @ts-ignore - if (height !== 'auto') videoVnode.data.height = height + if (height !== 'auto') { videoVnode.data.height = height } vnode = ( <div @@ -73,7 +76,7 @@ function renderVideo(elemNode: Element, children: VNode[] | null, editor: IDomEd mousedown: e => e.preventDefault(), }, }, - vnode + vnode, ) return containerVnode diff --git a/packages/video-module/src/utils/dom.ts b/packages/video-module/src/utils/dom.ts index 62337c477..d436b6389 100644 --- a/packages/video-module/src/utils/dom.ts +++ b/packages/video-module/src/utils/dom.ts @@ -3,19 +3,34 @@ * @author wangfupeng */ -import $, { append, on, focus, attr, val, html, parent, hasClass, Dom7Array, empty } from 'dom7' +import $, { + append, attr, Dom7Array, empty, focus, hasClass, html, on, parent, val, +} from 'dom7' + import { videoStyle } from '../module/custom-types' + +// COMPAT: This is required to prevent TypeScript aliases from doing some very +// weird things for Slate's types with the same name as globals. (2019/11/27) +// https://github.com/microsoft/TypeScript/issues/35002 +import DOMNode = globalThis.Node +import DOMComment = globalThis.Comment +import DOMElement = globalThis.Element +import DOMText = globalThis.Text +import DOMRange = globalThis.Range +import DOMSelection = globalThis.Selection +import DOMStaticRange = globalThis.StaticRange + export { Dom7Array } from 'dom7' -if (append) $.fn.append = append -if (on) $.fn.on = on -if (focus) $.fn.focus = focus -if (attr) $.fn.attr = attr -if (val) $.fn.val = val -if (html) $.fn.html = html -if (parent) $.fn.parent = parent -if (hasClass) $.fn.hasClass = hasClass -if (empty) $.fn.empty = empty +if (append) { $.fn.append = append } +if (on) { $.fn.on = on } +if (focus) { $.fn.focus = focus } +if (attr) { $.fn.attr = attr } +if (val) { $.fn.val = val } +if (html) { $.fn.html = html } +if (parent) { $.fn.parent = parent } +if (hasClass) { $.fn.hasClass = hasClass } +if (empty) { $.fn.empty = empty } export default $ @@ -24,7 +39,7 @@ export default $ * @param $elem $elem */ export function getTagName($elem: Dom7Array): string { - if ($elem.length) return $elem[0].tagName.toLowerCase() + if ($elem.length) { return $elem[0].tagName.toLowerCase() } return '' } @@ -37,29 +52,21 @@ export function getTagName($elem: Dom7Array): string { */ export function genSizeStyledIframeHtml( iframeHtml: string, - width: string = 'auto', - height: string = 'auto', - style: videoStyle = {} + width = 'auto', + height = 'auto', + style: videoStyle = {}, ): string { const $iframe = $(iframeHtml) const { width: styleWidth = '', height: styleHeight = '' } = style let styleStr = '' - if (styleWidth) styleStr += `width: ${styleWidth};` - if (styleHeight) styleStr += `height: ${styleHeight};` + + if (styleWidth) { styleStr += `width: ${styleWidth};` } + if (styleHeight) { styleStr += `height: ${styleHeight};` } $iframe.attr('width', width) $iframe.attr('height', height) $iframe.attr('style', styleStr) return $iframe[0].outerHTML } - -// COMPAT: This is required to prevent TypeScript aliases from doing some very -// weird things for Slate's types with the same name as globals. (2019/11/27) -// https://github.com/microsoft/TypeScript/issues/35002 -import DOMNode = globalThis.Node -import DOMComment = globalThis.Comment -import DOMElement = globalThis.Element -import DOMText = globalThis.Text -import DOMRange = globalThis.Range -import DOMSelection = globalThis.Selection -import DOMStaticRange = globalThis.StaticRange -export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange } +export { + DOMComment, DOMElement, DOMNode, DOMRange, DOMSelection, DOMStaticRange, DOMText, +} diff --git a/packages/video-module/src/utils/util.ts b/packages/video-module/src/utils/util.ts index 1ba66e8cd..df814fc7c 100644 --- a/packages/video-module/src/utils/util.ts +++ b/packages/video-module/src/utils/util.ts @@ -10,7 +10,7 @@ import { nanoid } from 'nanoid' * @param prefix 前缀 * @returns 随机数字符串 */ -export function genRandomStr(prefix: string = 'r'): string { +export function genRandomStr(prefix = 'r'): string { return `${prefix}-${nanoid()}` } @@ -29,6 +29,7 @@ export function styleStringToObject(styleString) { if (style) { // 忽略空字符串 const [property, value] = style.split(':') + if (property && value) { // 去掉两端的空格并将结果存储在对象中 styleObject[property.trim()] = value.trim() diff --git a/packages/yjs-for-react/rollup.config.js b/packages/yjs-for-react/rollup.config.js index 636793c53..7a55f44cf 100644 --- a/packages/yjs-for-react/rollup.config.js +++ b/packages/yjs-for-react/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorCodeHighLight' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/yjs-for-react/src/hooks/use-editor-static.tsx b/packages/yjs-for-react/src/hooks/use-editor-static.tsx index 76da4c0ff..b3d4501cf 100644 --- a/packages/yjs-for-react/src/hooks/use-editor-static.tsx +++ b/packages/yjs-for-react/src/hooks/use-editor-static.tsx @@ -1,16 +1,17 @@ -import { createContext, useContext } from 'react' import { IDomEditor } from '@wangeditor-next/editor' +import { createContext, useContext } from 'react' export const EditorContext = createContext<IDomEditor | null>(null) export const useEditorStatic = (): IDomEditor | null => { const editor = useContext(EditorContext) + if (!editor) { // throw new Error( // `The \`useEditorStatic\` hook must be used inside the <EditorContext> component's context.` // ) console.warn( - "The `useEditorStatic` hook must be used inside the <EditorContext> component's context." + "The `useEditorStatic` hook must be used inside the <EditorContext> component's context.", ) } diff --git a/packages/yjs-for-react/src/hooks/useRemoteCursorEditor.ts b/packages/yjs-for-react/src/hooks/useRemoteCursorEditor.ts index f8d845ac3..aecba4f9e 100644 --- a/packages/yjs-for-react/src/hooks/useRemoteCursorEditor.ts +++ b/packages/yjs-for-react/src/hooks/useRemoteCursorEditor.ts @@ -1,11 +1,13 @@ -import { CursorEditor } from '@wangeditor-next/yjs' import { IDomEditor } from '@wangeditor-next/editor' +import { CursorEditor } from '@wangeditor-next/yjs' + import { useEditorStatic } from './use-editor-static' export function useRemoteCursorEditor< - TCursorData extends Record<string, unknown> = Record<string, unknown> + TCursorData extends Record<string, unknown> = Record<string, unknown>, >(): CursorEditor<TCursorData> & IDomEditor { const editor = useEditorStatic() + if (!CursorEditor.isCursorEditor(editor)) { console.warn('Cannot use useSyncExternalStore outside the context of a RemoteCursorEditor') } diff --git a/packages/yjs-for-react/src/hooks/useRemoteCursorOverlayPositions.tsx b/packages/yjs-for-react/src/hooks/useRemoteCursorOverlayPositions.tsx index 2d3caaeaa..a3043ba1b 100644 --- a/packages/yjs-for-react/src/hooks/useRemoteCursorOverlayPositions.tsx +++ b/packages/yjs-for-react/src/hooks/useRemoteCursorOverlayPositions.tsx @@ -1,6 +1,9 @@ import { CursorState } from '@wangeditor-next/yjs' -import { RefObject, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' +import { + RefObject, useCallback, useLayoutEffect, useMemo, useRef, useState, +} from 'react' import { BaseRange, NodeMatch, Text } from 'slate' + import { getCursorRange } from '../utils/getCursorRange' import { CaretPosition, @@ -42,7 +45,7 @@ export type CursorOverlayData<TCursorData extends Record<string, unknown>> = export function useRemoteCursorOverlayPositions< TCursorData extends Record<string, unknown>, - TContainer extends HTMLElement = HTMLDivElement + TContainer extends HTMLElement = HTMLDivElement, >({ containerRef, shouldGenerateOverlay, @@ -74,8 +77,7 @@ export function useRemoteCursorOverlayPositions< const xOffset = containerRect?.x ?? 0 const yOffset = containerRect?.y ?? 0 - let overlayPositionsChanged = - Object.keys(overlayPositions).length !== Object.keys(cursorStates).length + let overlayPositionsChanged = Object.keys(overlayPositions).length !== Object.keys(cursorStates).length const updated = Object.fromEntries( Object.entries(cursorStates).map(([key, state]) => { @@ -86,6 +88,7 @@ export function useRemoteCursorOverlayPositions< } const cached = overlayPositionCache.current.get(range) + if (cached) { return [key, cached] } @@ -95,10 +98,11 @@ export function useRemoteCursorOverlayPositions< yOffset, shouldGenerateOverlay, }) + overlayPositionsChanged = true overlayPositionCache.current.set(range, overlayPosition) return [key, overlayPosition] - }) + }), ) if (overlayPositionsChanged) { @@ -107,19 +111,18 @@ export function useRemoteCursorOverlayPositions< }) const overlayData = useMemo<CursorOverlayData<TCursorData>[]>( - () => - Object.entries(cursorStates).map(([clientId, state]) => { - const range = state.relativeSelection && getCursorRange(editor, state) - const overlayPosition = overlayPositions[clientId] - - return { - ...state, - range, - caretPosition: overlayPosition?.caretPosition ?? null, - selectionRects: overlayPosition?.selectionRects ?? FROZEN_EMPTY_ARRAY, - } - }), - [cursorStates, editor, overlayPositions] + () => Object.entries(cursorStates).map(([clientId, state]) => { + const range = state.relativeSelection && getCursorRange(editor, state) + const overlayPosition = overlayPositions[clientId] + + return { + ...state, + range, + caretPosition: overlayPosition?.caretPosition ?? null, + selectionRects: overlayPosition?.selectionRects ?? FROZEN_EMPTY_ARRAY, + } + }), + [cursorStates, editor, overlayPositions], ) const refresh = useCallback(() => { diff --git a/packages/yjs-for-react/src/hooks/useRemoteCursorStateStore.ts b/packages/yjs-for-react/src/hooks/useRemoteCursorStateStore.ts index 3016ec319..cd8703f2f 100644 --- a/packages/yjs-for-react/src/hooks/useRemoteCursorStateStore.ts +++ b/packages/yjs-for-react/src/hooks/useRemoteCursorStateStore.ts @@ -1,5 +1,6 @@ import { CursorEditor, CursorState, RemoteCursorChangeEventListener } from '@wangeditor-next/yjs' import { BaseEditor } from 'slate' + import { Store } from '../types' import { useRemoteCursorEditor } from './useRemoteCursorEditor' @@ -9,7 +10,7 @@ export type CursorStore<TCursorData extends Record<string, unknown> = Record<str const EDITOR_TO_CURSOR_STORE: WeakMap<BaseEditor, CursorStore> = new WeakMap() function createRemoteCursorStateStore<TCursorData extends Record<string, unknown>>( - editor: CursorEditor<TCursorData> + editor: CursorEditor<TCursorData>, ): CursorStore<TCursorData> { let cursors: Record<string, CursorState<TCursorData>> = {} @@ -47,6 +48,7 @@ function createRemoteCursorStateStore<TCursorData extends Record<string, unknown changed.forEach(clientId => { const state = CursorEditor.cursorState(editor, clientId) + if (state === null) { delete cursors[clientId.toString()] return @@ -64,22 +66,24 @@ function createRemoteCursorStateStore<TCursorData extends Record<string, unknown } function getCursorStateStore<TCursorData extends Record<string, unknown>>( - editor: CursorEditor<TCursorData> + editor: CursorEditor<TCursorData>, ): CursorStore<TCursorData> { const existing = EDITOR_TO_CURSOR_STORE.get(editor) + if (existing) { return existing as CursorStore<TCursorData> } const store = createRemoteCursorStateStore(editor) - if (editor) EDITOR_TO_CURSOR_STORE.set(editor, store) + if (editor) { EDITOR_TO_CURSOR_STORE.set(editor, store) } return store } export function useRemoteCursorStateStore< - TCursorData extends Record<string, unknown> = Record<string, unknown> + TCursorData extends Record<string, unknown> = Record<string, unknown>, >() { const editor = useRemoteCursorEditor<TCursorData>() + return getCursorStateStore(editor) } diff --git a/packages/yjs-for-react/src/hooks/useRemoteCursorStates.ts b/packages/yjs-for-react/src/hooks/useRemoteCursorStates.ts index 2fcdd05b7..f1c3ce141 100644 --- a/packages/yjs-for-react/src/hooks/useRemoteCursorStates.ts +++ b/packages/yjs-for-react/src/hooks/useRemoteCursorStates.ts @@ -1,22 +1,25 @@ import { CursorState } from '@wangeditor-next/yjs' import { useSyncExternalStore } from 'use-sync-external-store/shim' import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector' + import { useRemoteCursorStateStore } from './useRemoteCursorStateStore' export function useRemoteCursorStates< - TCursorData extends Record<string, unknown> = Record<string, unknown> + TCursorData extends Record<string, unknown> = Record<string, unknown>, >() { const [subscribe, getSnapshot] = useRemoteCursorStateStore<TCursorData>() + return useSyncExternalStore(subscribe, getSnapshot) } export function useRemoteCursorStatesSelector< TCursorData extends Record<string, unknown> = Record<string, unknown>, - TSelection = unknown + TSelection = unknown, >( selector: (cursors: Record<string, CursorState<TCursorData>>) => TSelection, - isEqual?: (a: TSelection, b: TSelection) => boolean + isEqual?: (a: TSelection, b: TSelection) => boolean, ): TSelection { const [subscribe, getSnapshot] = useRemoteCursorStateStore<TCursorData>() + return useSyncExternalStoreWithSelector(subscribe, getSnapshot, null, selector, isEqual) } diff --git a/packages/yjs-for-react/src/hooks/utils.ts b/packages/yjs-for-react/src/hooks/utils.ts index 1a5bdc6f5..2eb8d5e99 100644 --- a/packages/yjs-for-react/src/hooks/utils.ts +++ b/packages/yjs-for-react/src/hooks/utils.ts @@ -1,4 +1,6 @@ -import { RefObject, useCallback, useEffect, useReducer, useRef, useState } from 'react' +import { + RefObject, useCallback, useEffect, useReducer, useRef, useState, +} from 'react' export function useRequestRerender() { const [, rerender] = useReducer(s => s + 1, 0) @@ -30,16 +32,16 @@ export function useRequestRerender() { export function useOnResize<T extends HTMLElement>( ref: RefObject<T> | undefined, - onResize: () => void + onResize: () => void, ) { const onResizeRef = useRef(onResize) + onResizeRef.current = onResize const [observer] = useState( - () => - new ResizeObserver(() => { - onResizeRef.current() - }) + () => new ResizeObserver(() => { + onResizeRef.current() + }), ) useEffect(() => { @@ -48,6 +50,7 @@ export function useOnResize<T extends HTMLElement>( } const { current: element } = ref + observer.observe(element) return () => observer.unobserve(element) }, [observer, ref]) diff --git a/packages/yjs-for-react/src/index.ts b/packages/yjs-for-react/src/index.ts index 1905242b5..dc92f55f1 100644 --- a/packages/yjs-for-react/src/index.ts +++ b/packages/yjs-for-react/src/index.ts @@ -1,11 +1,8 @@ export { EditorContext, useEditorStatic } from './hooks/use-editor-static' - -export { useRemoteCursorStatesSelector, useRemoteCursorStates } from './hooks/useRemoteCursorStates' - -export { getCursorRange } from './utils/getCursorRange' - export { CursorOverlayData, - UseRemoteCursorOverlayPositionsOptions, useRemoteCursorOverlayPositions, + UseRemoteCursorOverlayPositionsOptions, } from './hooks/useRemoteCursorOverlayPositions' +export { useRemoteCursorStates, useRemoteCursorStatesSelector } from './hooks/useRemoteCursorStates' +export { getCursorRange } from './utils/getCursorRange' diff --git a/packages/yjs-for-react/src/utils/getCursorRange.ts b/packages/yjs-for-react/src/utils/getCursorRange.ts index 9a21ddfaf..e68b50d45 100644 --- a/packages/yjs-for-react/src/utils/getCursorRange.ts +++ b/packages/yjs-for-react/src/utils/getCursorRange.ts @@ -7,19 +7,21 @@ const CHILDREN_TO_CURSOR_STATE_TO_RANGE: WeakMap< > = new WeakMap() export function getCursorRange< - TCursorData extends Record<string, unknown> = Record<string, unknown> + TCursorData extends Record<string, unknown> = Record<string, unknown>, >(editor: CursorEditor<TCursorData>, cursorState: CursorState<TCursorData>): BaseRange | null { if (!cursorState.relativeSelection) { return null } let cursorStates = CHILDREN_TO_CURSOR_STATE_TO_RANGE.get(editor.children) + if (!cursorStates) { cursorStates = new WeakMap() CHILDREN_TO_CURSOR_STATE_TO_RANGE.set(editor.children, cursorStates) } let range = cursorStates.get(cursorState) + if (range === undefined) { try { range = relativeRangeToSlateRange(editor.sharedRoot, editor, cursorState.relativeSelection) diff --git a/packages/yjs-for-react/src/utils/getOverlayPosition.ts b/packages/yjs-for-react/src/utils/getOverlayPosition.ts index ef21359fa..6656c169f 100644 --- a/packages/yjs-for-react/src/utils/getOverlayPosition.ts +++ b/packages/yjs-for-react/src/utils/getOverlayPosition.ts @@ -1,5 +1,8 @@ -import { BaseRange, Editor, Path, Range, Text } from 'slate' import { DomEditor, IDomEditor } from '@wangeditor-next/editor' +import { + BaseRange, Editor, Path, Range, Text, +} from 'slate' + import { reactEditorToDomRangeSafe } from './react-editor-to-dom-range-safe' export type SelectionRect = { @@ -29,10 +32,11 @@ export type GetSelectionRectsOptions = { export function getOverlayPosition( editor: IDomEditor, range: BaseRange, - { yOffset, xOffset, shouldGenerateOverlay }: GetSelectionRectsOptions + { yOffset, xOffset, shouldGenerateOverlay }: GetSelectionRectsOptions, ): OverlayPosition { const [start, end] = Range.edges(range) const domRange = reactEditorToDomRangeSafe(editor, range) + if (!domRange) { return { caretPosition: null, @@ -48,6 +52,7 @@ export function getOverlayPosition( let caretPosition: CaretPosition | null = null const isBackward = Range.isBackward(range) + for (const [node, path] of nodeIterator) { const domNode = DomEditor.toDOMNode(editor, node) @@ -55,8 +60,10 @@ export function getOverlayPosition( const isEndNode = Path.equals(path, end.path) let clientRects: DOMRectList | null = null + if (isStartNode || isEndNode) { const nodeRange = document.createRange() + nodeRange.selectNode(domNode) if (isStartNode) { @@ -72,8 +79,10 @@ export function getOverlayPosition( } const isCaret = isBackward ? isStartNode : isEndNode + for (let i = 0; i < clientRects.length; i++) { const clientRect = clientRects.item(i) + if (!clientRect) { continue } diff --git a/packages/yjs-for-react/src/utils/react-editor-to-dom-range-safe.ts b/packages/yjs-for-react/src/utils/react-editor-to-dom-range-safe.ts index bab5a63e6..be308f628 100644 --- a/packages/yjs-for-react/src/utils/react-editor-to-dom-range-safe.ts +++ b/packages/yjs-for-react/src/utils/react-editor-to-dom-range-safe.ts @@ -1,5 +1,5 @@ -import { BaseRange } from 'slate' import { DomEditor, IDomEditor } from '@wangeditor-next/editor' +import { BaseRange } from 'slate' export function reactEditorToDomRangeSafe(editor: IDomEditor, range: BaseRange): Range | null { try { diff --git a/packages/yjs/examples/frontend/src/index.tsx b/packages/yjs/examples/frontend/src/index.tsx index ce78e76cd..99ef5a349 100644 --- a/packages/yjs/examples/frontend/src/index.tsx +++ b/packages/yjs/examples/frontend/src/index.tsx @@ -1,10 +1,14 @@ -import React, { StrictMode } from 'react' -import ReactDOM from 'react-dom' -import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom' import 'virtual:windi.css' import 'virtual:windi-devtools' -import { SimplePage } from './pages/Simple' + +import React, { StrictMode } from 'react' +import ReactDOM from 'react-dom' +import { + BrowserRouter, Navigate, Route, Routes, +} from 'react-router-dom' + import { RemoteCursorsOverlayPage } from './pages/RemoteCursorOverlay' +import { SimplePage } from './pages/Simple' ReactDOM.render( <StrictMode> @@ -15,5 +19,5 @@ ReactDOM.render( </Routes> </BrowserRouter> </StrictMode>, - document.getElementById('root') + document.getElementById('root'), ) diff --git a/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/Overlay.tsx b/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/Overlay.tsx index 802b09b4e..7212e3bd1 100644 --- a/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/Overlay.tsx +++ b/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/Overlay.tsx @@ -1,10 +1,11 @@ import { CursorOverlayData, - useRemoteCursorOverlayPositions, useEditorStatic, + useRemoteCursorOverlayPositions, } from '@wangeditor-next/yjs-for-react' import clsx from 'clsx' import React, { CSSProperties, PropsWithChildren, useRef } from 'react' + import { CursorData } from '../../types' import { addAlpha } from '../../utils' diff --git a/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/index.tsx b/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/index.tsx index 5d5609a23..51c552908 100644 --- a/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/index.tsx +++ b/packages/yjs/examples/frontend/src/pages/RemoteCursorOverlay/index.tsx @@ -1,19 +1,22 @@ -import { WebsocketProvider } from 'y-websocket' +import '@wangeditor-next/editor/dist/css/style.css' + import { + Boot, IDomEditor, IEditorConfig, IToolbarConfig, +} from '@wangeditor-next/editor' +import { Editor, Toolbar } from '@wangeditor-next/editor-for-react' +import { + slateNodesToInsertDelta, + withCursors, withYHistory, withYjs, - withCursors, YjsEditor, - slateNodesToInsertDelta, } from '@wangeditor-next/yjs' import { EditorContext } from '@wangeditor-next/yjs-for-react' import React, { useEffect, useState } from 'react' import { Descendant } from 'slate' +import { WebsocketProvider } from 'y-websocket' import * as Y from 'yjs' -import '@wangeditor-next/editor/dist/css/style.css' -import { Editor, Toolbar } from '@wangeditor-next/editor-for-react' -import { IDomEditor, IEditorConfig, IToolbarConfig, Boot } from '@wangeditor-next/editor' import { randomCursorData } from '../../utils' import { RemoteCursorOverlay } from './Overlay' @@ -21,11 +24,12 @@ const yDoc = new Y.Doc() const wsProvider = new WebsocketProvider('ws://localhost:1234', 'wangeditor-next-yjs', yDoc) const sharedType = yDoc.get('content', Y.XmlText) // console.log('🚀 ~ SimplePage ~ sharedType:', sharedType.toJSON()) + Boot.registerPlugin(withYjs(sharedType)) Boot.registerPlugin( withCursors(wsProvider.awareness, { data: randomCursorData(), - }) + }), ) Boot.registerPlugin(withYHistory()) @@ -85,7 +89,7 @@ export const RemoteCursorsOverlayPage = () => { // 及时销毁 editor ,重要! useEffect(() => { return () => { - if (editor == null) return + if (editor == null) { return } setTimeout(() => { editor.destroy() // 组件销毁时,及时销毁编辑器 }, 300) diff --git a/packages/yjs/examples/frontend/src/pages/Simple.tsx b/packages/yjs/examples/frontend/src/pages/Simple.tsx index 3873a4e81..56208fb7e 100644 --- a/packages/yjs/examples/frontend/src/pages/Simple.tsx +++ b/packages/yjs/examples/frontend/src/pages/Simple.tsx @@ -1,17 +1,21 @@ -import { WebsocketProvider } from 'y-websocket' -import { withYHistory, withYjs, YjsEditor, slateNodesToInsertDelta } from '@wangeditor-next/yjs' +import '@wangeditor-next/editor/dist/css/style.css' + +import { + Boot, IDomEditor, IEditorConfig, IToolbarConfig, +} from '@wangeditor-next/editor' +import { Editor, Toolbar } from '@wangeditor-next/editor-for-react' +import { + slateNodesToInsertDelta, withYHistory, withYjs, YjsEditor, +} from '@wangeditor-next/yjs' import React, { useEffect, useState } from 'react' import { Descendant } from 'slate' +import { WebsocketProvider } from 'y-websocket' import * as Y from 'yjs' -import '@wangeditor-next/editor/dist/css/style.css' -import { Editor, Toolbar } from '@wangeditor-next/editor-for-react' -import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor-next/editor' -import { Boot } from '@wangeditor-next/editor' - const yDoc = new Y.Doc() const wsProvider = new WebsocketProvider('ws://localhost:1234', 'wangeditor-next-yjs', yDoc) const sharedType = yDoc.get('content', Y.XmlText) + console.log('🚀 ~ SimplePage ~ sharedType:', sharedType.toJSON()) // @ts-ignore Boot.registerPlugin(withYjs(sharedType)) @@ -66,7 +70,7 @@ export const SimplePage = () => { // 及时销毁 editor ,重要! useEffect(() => { return () => { - if (editor == null) return + if (editor == null) { return } setTimeout(() => { editor.destroy() // 组件销毁时,及时销毁编辑器 }, 300) diff --git a/packages/yjs/examples/frontend/src/utils.ts b/packages/yjs/examples/frontend/src/utils.ts index 94f115adb..857ffff66 100644 --- a/packages/yjs/examples/frontend/src/utils.ts +++ b/packages/yjs/examples/frontend/src/utils.ts @@ -1,5 +1,6 @@ import { faker } from '@faker-js/faker' import randomColor from 'randomcolor' + import { CursorData } from './types' const { @@ -19,5 +20,6 @@ export function randomCursorData(): CursorData { export function addAlpha(hexColor: string, opacity: number): string { const normalized = Math.round(Math.min(Math.max(opacity, 0), 1) * 255) + return hexColor + normalized.toString(16).toUpperCase() } diff --git a/packages/yjs/examples/frontend/vite.config.ts b/packages/yjs/examples/frontend/vite.config.ts index 92ec82ae9..fedc2bd06 100644 --- a/packages/yjs/examples/frontend/vite.config.ts +++ b/packages/yjs/examples/frontend/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' import windi from 'vite-plugin-windicss' // https://vitejs.dev/config/ diff --git a/packages/yjs/rollup.config.js b/packages/yjs/rollup.config.js index 2d5ac2858..19e3bdc20 100644 --- a/packages/yjs/rollup.config.js +++ b/packages/yjs/rollup.config.js @@ -1,4 +1,5 @@ -import { createRollupConfig, IS_PRD } from '../../build/create-rollup-config' +import { createRollupConfig, IS_PRD } from '@wangeditor-next/wangeditor/build/create-rollup-config' + import pkg from './package.json' const name = 'WangEditorYjsModule' @@ -13,6 +14,7 @@ const esmConf = createRollupConfig({ name, }, }) + configList.push(esmConf) // umd @@ -23,6 +25,7 @@ const umdConf = createRollupConfig({ name, }, }) + configList.push(umdConf) export default configList diff --git a/packages/yjs/src/applyToSlate/index.ts b/packages/yjs/src/applyToSlate/index.ts index d2e5eb1ef..b40ff8a2e 100644 --- a/packages/yjs/src/applyToSlate/index.ts +++ b/packages/yjs/src/applyToSlate/index.ts @@ -1,5 +1,6 @@ import { Editor, Operation } from 'slate' import * as Y from 'yjs' + import { translateYTextEvent } from './textEvent' /** @@ -12,7 +13,7 @@ import { translateYTextEvent } from './textEvent' export function translateYjsEvent( sharedRoot: Y.XmlText, editor: Editor, - event: Y.YEvent<Y.XmlText> + event: Y.YEvent<Y.XmlText>, ): Operation[] { if (event instanceof Y.YTextEvent) { return translateYTextEvent(sharedRoot, editor, event) @@ -32,7 +33,7 @@ export function translateYjsEvent( export function applyYjsEvents( sharedRoot: Y.XmlText, editor: Editor, - events: Y.YEvent<Y.XmlText>[] + events: Y.YEvent<Y.XmlText>[], ) { Editor.withoutNormalizing(editor, () => { events.forEach(event => { diff --git a/packages/yjs/src/applyToSlate/textEvent.ts b/packages/yjs/src/applyToSlate/textEvent.ts index 076034073..01450b97c 100644 --- a/packages/yjs/src/applyToSlate/textEvent.ts +++ b/packages/yjs/src/applyToSlate/textEvent.ts @@ -1,5 +1,8 @@ -import { BaseElement, Editor, Element, Node, Operation, Path, Text } from 'slate' +import { + Editor, Element, Node, Operation, Path, Text, +} from 'slate' import * as Y from 'yjs' + import { Delta } from '../module/custom-types' import { deltaInsertToSlateNode } from '../utils/convert' import { getSlateNodeYLength, getSlatePath, yOffsetToSlateOffsets } from '../utils/location' @@ -26,7 +29,7 @@ function applyDelta(node: Element, slatePath: Path, delta: Delta): Operation[] { if ('attributes' in change && 'retain' in change) { const [startPathOffset, startTextOffset] = yOffsetToSlateOffsets( node, - yOffset - change.retain + yOffset - change.retain, ) const [endPathOffset, endTextOffset] = yOffsetToSlateOffsets(node, yOffset, { assoc: -1 }) @@ -88,7 +91,7 @@ function applyDelta(node: Element, slatePath: Path, delta: Delta): Operation[] { if ('delete' in change) { const [startPathOffset, startTextOffset] = yOffsetToSlateOffsets( node, - yOffset - change.delete + yOffset - change.delete, ) const [endPathOffset, endTextOffset] = yOffsetToSlateOffsets(node, yOffset, { assoc: -1 }) @@ -101,8 +104,8 @@ function applyDelta(node: Element, slatePath: Path, delta: Delta): Operation[] { const childPath = [...slatePath, pathOffset] if ( - Text.isText(child) && - (pathOffset === startPathOffset || pathOffset === endPathOffset) + Text.isText(child) + && (pathOffset === startPathOffset || pathOffset === endPathOffset) ) { const start = pathOffset === startPathOffset ? startTextOffset : 0 const end = pathOffset === endPathOffset ? endTextOffset : child.text.length @@ -144,17 +147,16 @@ function applyDelta(node: Element, slatePath: Path, delta: Delta): Operation[] { * Since we're not actually using slate to update the node * this is a simulation */ - const currentProps = - lastOp != null && lastOp.type === 'insert_node' ? lastOp.node : getProperties(child) + const currentProps = lastOp != null && lastOp.type === 'insert_node' ? lastOp.node : getProperties(child) let lastPath: Path = [] if ( - lastOp != null && - (lastOp.type === 'insert_node' || - lastOp.type === 'insert_text' || - lastOp.type === 'split_node' || - lastOp.type === 'set_node') + lastOp != null + && (lastOp.type === 'insert_node' + || lastOp.type === 'insert_text' + || lastOp.type === 'split_node' + || lastOp.type === 'set_node') ) { lastPath = lastOp.path } @@ -164,9 +166,9 @@ function applyDelta(node: Element, slatePath: Path, delta: Delta): Operation[] { * props at the current path, we can just insert a text node */ if ( - typeof change.insert === 'string' && - deepEquals(change.attributes ?? {}, currentProps) && - Path.equals(childPath, lastPath) + typeof change.insert === 'string' + && deepEquals(change.attributes ?? {}, currentProps) + && Path.equals(childPath, lastPath) ) { return ops.push({ type: 'insert_text', @@ -177,6 +179,7 @@ function applyDelta(node: Element, slatePath: Path, delta: Delta): Operation[] { } const toInsert = deltaInsertToSlateNode(change) + if (textOffset === 0) { return ops.push({ type: 'insert_node', @@ -215,7 +218,7 @@ function applyDelta(node: Element, slatePath: Path, delta: Delta): Operation[] { export function translateYTextEvent( sharedRoot: Y.XmlText, editor: Editor, - event: Y.YTextEvent + event: Y.YTextEvent, ): Operation[] { const { target, changes } = event const delta = event.delta as Delta @@ -233,17 +236,20 @@ export function translateYTextEvent( } const keyChanges = Array.from(changes.keys.entries()) + if (slatePath.length > 0 && keyChanges.length > 0) { const newProperties = Object.fromEntries( keyChanges.map(([key, info]) => [ key, info.action === 'delete' ? null : target.getAttribute(key), - ]) + ]), ) const properties = Object.fromEntries(keyChanges.map(([key]) => [key, targetElement[key]])) - ops.push({ type: 'set_node', newProperties, properties, path: slatePath }) + ops.push({ + type: 'set_node', newProperties, properties, path: slatePath, + }) } if (delta.length > 0) { diff --git a/packages/yjs/src/applyToYjs/index.ts b/packages/yjs/src/applyToYjs/index.ts index c0b860ef2..bfec59626 100644 --- a/packages/yjs/src/applyToYjs/index.ts +++ b/packages/yjs/src/applyToYjs/index.ts @@ -1,5 +1,6 @@ import { Node, Operation } from 'slate' import * as Y from 'yjs' + import { NODE_MAPPER } from './node' import { TEXT_MAPPER } from './text' import { ApplyFunc, OpMapper } from './types' @@ -16,6 +17,7 @@ const opMappers: OpMapper = { export function applySlateOp(sharedRoot: Y.XmlText, slateRoot: Node, op: Operation): void { const apply = opMappers[op.type] as ApplyFunc<typeof op> + if (!apply) { throw new Error(`Unknown operation: ${op.type}`) } diff --git a/packages/yjs/src/applyToYjs/node/index.ts b/packages/yjs/src/applyToYjs/node/index.ts index b98ca5482..43c081a26 100644 --- a/packages/yjs/src/applyToYjs/node/index.ts +++ b/packages/yjs/src/applyToYjs/node/index.ts @@ -1,4 +1,5 @@ import { NodeOperation } from 'slate' + import { OpMapper } from '../types' import { insertNode } from './insertNode' import { mergeNode } from './mergeNode' diff --git a/packages/yjs/src/applyToYjs/node/insertNode.ts b/packages/yjs/src/applyToYjs/node/insertNode.ts index 22f07def0..edba6c24b 100644 --- a/packages/yjs/src/applyToYjs/node/insertNode.ts +++ b/packages/yjs/src/applyToYjs/node/insertNode.ts @@ -1,5 +1,6 @@ import { InsertNodeOperation, Node, Text } from 'slate' import * as Y from 'yjs' + import { slateElementToYText } from '../../utils/convert' import { getYTarget } from '../../utils/location' import { getProperties } from '../../utils/slate' diff --git a/packages/yjs/src/applyToYjs/node/mergeNode.ts b/packages/yjs/src/applyToYjs/node/mergeNode.ts index 12a8bf62b..0b67fadf6 100644 --- a/packages/yjs/src/applyToYjs/node/mergeNode.ts +++ b/packages/yjs/src/applyToYjs/node/mergeNode.ts @@ -1,5 +1,8 @@ -import { MergeNodeOperation, Node, Path, Text } from 'slate' +import { + MergeNodeOperation, Node, Path, Text, +} from 'slate' import * as Y from 'yjs' + import { Delta } from '../../module/custom-types' import { cloneInsertDeltaDeep } from '../../utils/clone' import { yTextToInsertDelta } from '../../utils/delta' @@ -20,11 +23,13 @@ export function mergeNode(sharedRoot: Y.XmlText, slateRoot: Node, op: MergeNodeO if (!prev.yTarget || !target.yTarget) { const { yParent: parent, textRange, slateTarget } = target + if (!slateTarget) { throw new Error('Expected Slate target node for merge op.') } const prevSibling = Node.get(slateRoot, Path.previous(op.path)) + if (!Text.isText(prevSibling)) { throw new Error('Path points to Y.Text but not a Slate text node.') } @@ -33,6 +38,7 @@ export function mergeNode(sharedRoot: Y.XmlText, slateRoot: Node, op: MergeNodeO const prevSiblingProps = getProperties(prevSibling) const unsetProps = Object.keys(targetProps).reduce((acc, key) => { const prevSiblingHasProp = key in prevSiblingProps + return prevSiblingHasProp ? acc : { ...acc, [key]: null } }, {}) @@ -50,7 +56,7 @@ export function mergeNode(sharedRoot: Y.XmlText, slateRoot: Node, op: MergeNodeO sharedRoot, target.yTarget, targetDelta, - deltaApplyYOffset + deltaApplyYOffset, ) const applyDelta: Delta = [{ retain: deltaApplyYOffset }, ...clonedDelta] @@ -66,6 +72,6 @@ export function mergeNode(sharedRoot: Y.XmlText, slateRoot: Node, op: MergeNodeO prev.yTarget, storedPositions, clonedDelta, - deltaApplyYOffset + deltaApplyYOffset, ) } diff --git a/packages/yjs/src/applyToYjs/node/moveNode.ts b/packages/yjs/src/applyToYjs/node/moveNode.ts index 47f152ace..c24e7bbd0 100644 --- a/packages/yjs/src/applyToYjs/node/moveNode.ts +++ b/packages/yjs/src/applyToYjs/node/moveNode.ts @@ -1,5 +1,8 @@ -import { MoveNodeOperation, Node, Path, Text } from 'slate' +import { + MoveNodeOperation, Node, Path, Text, +} from 'slate' import * as Y from 'yjs' + import { Delta } from '../../module/custom-types' import { cloneInsertDeltaDeep } from '../../utils/clone' import { getInsertDeltaLength, yTextToInsertDelta } from '../../utils/delta' @@ -13,6 +16,7 @@ export function moveNode(sharedRoot: Y.XmlText, slateRoot: Node, op: MoveNodeOpe const newParentPath = Path.parent(op.newPath) const newPathOffset = op.newPath[op.newPath.length - 1] const parent = Node.get(slateRoot, newParentPath) + if (Text.isText(parent)) { throw new Error('Cannot move slate node into text element') } @@ -25,7 +29,7 @@ export function moveNode(sharedRoot: Y.XmlText, slateRoot: Node, op: MoveNodeOpe const storedPositions = getStoredPositionsInDeltaAbsolute( sharedRoot, origin.yParent, - origin.targetDelta + origin.targetDelta, ) origin.yParent.delete(origin.textRange.start, origin.textRange.end - origin.textRange.start) @@ -42,6 +46,6 @@ export function moveNode(sharedRoot: Y.XmlText, slateRoot: Node, op: MoveNodeOpe storedPositions, insertDelta, deltaApplyYOffset, - origin.textRange.start + origin.textRange.start, ) } diff --git a/packages/yjs/src/applyToYjs/node/removeNode.ts b/packages/yjs/src/applyToYjs/node/removeNode.ts index 78268f415..0c12cc8ee 100644 --- a/packages/yjs/src/applyToYjs/node/removeNode.ts +++ b/packages/yjs/src/applyToYjs/node/removeNode.ts @@ -1,8 +1,10 @@ import { Node, RemoveNodeOperation } from 'slate' import * as Y from 'yjs' + import { getYTarget } from '../../utils/location' export function removeNode(sharedRoot: Y.XmlText, slateRoot: Node, op: RemoveNodeOperation): void { const { yParent: parent, textRange } = getYTarget(sharedRoot, slateRoot, op.path) + parent.delete(textRange.start, textRange.end - textRange.start) } diff --git a/packages/yjs/src/applyToYjs/node/setNode.ts b/packages/yjs/src/applyToYjs/node/setNode.ts index 2ceba0d26..ca52da000 100644 --- a/packages/yjs/src/applyToYjs/node/setNode.ts +++ b/packages/yjs/src/applyToYjs/node/setNode.ts @@ -1,5 +1,6 @@ import { Node, SetNodeOperation } from 'slate' import * as Y from 'yjs' + import { getYTarget } from '../../utils/location' export function setNode(sharedRoot: Y.XmlText, slateRoot: Node, op: SetNodeOperation): void { diff --git a/packages/yjs/src/applyToYjs/node/splitNode.ts b/packages/yjs/src/applyToYjs/node/splitNode.ts index a90cf666b..105677f7a 100644 --- a/packages/yjs/src/applyToYjs/node/splitNode.ts +++ b/packages/yjs/src/applyToYjs/node/splitNode.ts @@ -1,5 +1,6 @@ import { Node, SplitNodeOperation, Text } from 'slate' import * as Y from 'yjs' + import { cloneInsertDeltaDeep } from '../../utils/clone' import { sliceInsertDelta, yTextToInsertDelta } from '../../utils/delta' import { getSlateNodeYLength, getYTarget } from '../../utils/location' @@ -21,6 +22,7 @@ export function splitNode(sharedRoot: Y.XmlText, slateRoot: Node, op: SplitNodeO } const unset: Record<string, null> = {} + target.targetDelta.forEach(element => { if (element.attributes) { Object.keys(element.attributes).forEach(key => { @@ -32,7 +34,7 @@ export function splitNode(sharedRoot: Y.XmlText, slateRoot: Node, op: SplitNodeO return target.yParent.format( target.textRange.start, target.textRange.end - target.textRange.start, - { ...unset, ...op.properties } + { ...unset, ...op.properties }, ) } @@ -48,13 +50,13 @@ export function splitNode(sharedRoot: Y.XmlText, slateRoot: Node, op: SplitNodeO const length = target.slateTarget.children.reduce( (current, child) => current + getSlateNodeYLength(child), - 0 + 0, ) const splitDelta = sliceInsertDelta( yTextToInsertDelta(target.yTarget), ySplitOffset, - length - ySplitOffset + length - ySplitOffset, ) const clonedDelta = cloneInsertDeltaDeep(splitDelta) @@ -62,10 +64,11 @@ export function splitNode(sharedRoot: Y.XmlText, slateRoot: Node, op: SplitNodeO sharedRoot, target.yTarget, splitDelta, - ySplitOffset + ySplitOffset, ) const toInsert = new Y.XmlText() + toInsert.applyDelta(clonedDelta, { sanitize: false, }) @@ -76,7 +79,7 @@ export function splitNode(sharedRoot: Y.XmlText, slateRoot: Node, op: SplitNodeO target.yTarget.delete( splitTarget.textRange.start, - target.yTarget.length - splitTarget.textRange.start + target.yTarget.length - splitTarget.textRange.start, ) target.yParent.insertEmbed(target.textRange.end, toInsert) @@ -87,6 +90,6 @@ export function splitNode(sharedRoot: Y.XmlText, slateRoot: Node, op: SplitNodeO storedPositions, clonedDelta, 0, - ySplitOffset + ySplitOffset, ) } diff --git a/packages/yjs/src/applyToYjs/text/index.ts b/packages/yjs/src/applyToYjs/text/index.ts index 63cfccb1e..41beb5c9e 100644 --- a/packages/yjs/src/applyToYjs/text/index.ts +++ b/packages/yjs/src/applyToYjs/text/index.ts @@ -1,4 +1,5 @@ import { TextOperation } from 'slate' + import { OpMapper } from '../types' import { insertText } from './insertText' import { removeText } from './removeText' diff --git a/packages/yjs/src/applyToYjs/text/insertText.ts b/packages/yjs/src/applyToYjs/text/insertText.ts index 9dc5bb115..192e73289 100644 --- a/packages/yjs/src/applyToYjs/text/insertText.ts +++ b/packages/yjs/src/applyToYjs/text/insertText.ts @@ -1,5 +1,6 @@ import { InsertTextOperation, Node, Text } from 'slate' -import type Y from 'yjs' +import * as Y from 'yjs' + import { getYTarget } from '../../utils/location' import { getProperties } from '../../utils/slate' @@ -7,6 +8,7 @@ export function insertText(sharedRoot: Y.XmlText, slateRoot: Node, op: InsertTex const { yParent: target, textRange } = getYTarget(sharedRoot, slateRoot, op.path) const targetNode = Node.get(slateRoot, op.path) + if (!Text.isText(targetNode)) { throw new Error('Cannot insert text into non-text node') } diff --git a/packages/yjs/src/applyToYjs/text/removeText.ts b/packages/yjs/src/applyToYjs/text/removeText.ts index 24f288e60..e6a6efcbb 100644 --- a/packages/yjs/src/applyToYjs/text/removeText.ts +++ b/packages/yjs/src/applyToYjs/text/removeText.ts @@ -1,8 +1,10 @@ import { Node, RemoveTextOperation } from 'slate' -import type Y from 'yjs' +import * as Y from 'yjs' + import { getYTarget } from '../../utils/location' export function removeText(sharedRoot: Y.XmlText, slateRoot: Node, op: RemoveTextOperation): void { const { yParent: target, textRange } = getYTarget(sharedRoot, slateRoot, op.path) + target.delete(textRange.start + op.offset, op.text.length) } diff --git a/packages/yjs/src/index.ts b/packages/yjs/src/index.ts index 3c240c3e2..10d848b40 100644 --- a/packages/yjs/src/index.ts +++ b/packages/yjs/src/index.ts @@ -22,26 +22,26 @@ import { } from './utils/position' export { - withYjs, - WithYjsOptions, - YjsEditor, - // History plugin - withYHistory, - WithYHistoryOptions, - YHistoryEditor, // Base cursor plugin CursorEditor, - WithCursorsOptions, - withCursors, CursorState, - RemoteCursorChangeEventListener, CursorStateChangeEvent, + relativePositionToSlatePoint, // Utils RelativeRange, - yTextToSlateElement, - slateNodesToInsertDelta, - slateRangeToRelativeRange, relativeRangeToSlateRange, + RemoteCursorChangeEventListener, + slateNodesToInsertDelta, slatePointToRelativePosition, - relativePositionToSlatePoint, + slateRangeToRelativeRange, + withCursors, + WithCursorsOptions, + // History plugin + withYHistory, + WithYHistoryOptions, + withYjs, + WithYjsOptions, + YHistoryEditor, + YjsEditor, + yTextToSlateElement, } diff --git a/packages/yjs/src/module/custom-types.ts b/packages/yjs/src/module/custom-types.ts index 193d5d76f..a764e3837 100644 --- a/packages/yjs/src/module/custom-types.ts +++ b/packages/yjs/src/module/custom-types.ts @@ -1,5 +1,7 @@ -import type { Descendant, Editor, Element, Node } from 'slate' -import type Y from 'yjs' +import type { + Descendant, Editor, Element, Node, +} from 'slate' +import * as Y from 'yjs' export type DeltaAttributes = { retain: number diff --git a/packages/yjs/src/plugins/index.ts b/packages/yjs/src/plugins/index.ts index 22832a956..0863a405a 100644 --- a/packages/yjs/src/plugins/index.ts +++ b/packages/yjs/src/plugins/index.ts @@ -1,3 +1,3 @@ -export * from './withYjs' -export * from './withYHistory' export * from './withCursors' +export * from './withYHistory' +export * from './withYjs' diff --git a/packages/yjs/src/plugins/withCursors.ts b/packages/yjs/src/plugins/withCursors.ts index 930677361..b733c4448 100644 --- a/packages/yjs/src/plugins/withCursors.ts +++ b/packages/yjs/src/plugins/withCursors.ts @@ -1,6 +1,7 @@ import { Editor, Range } from 'slate' import { Awareness } from 'y-protocols/awareness' import * as Y from 'yjs' + import { RelativeRange } from '../module/custom-types' import { slateRangeToRelativeRange } from '../utils/position' import { YjsEditor } from './withYjs' @@ -38,25 +39,25 @@ export type CursorEditor<TCursorData extends Record<string, unknown> = Record<st export const CursorEditor = { isCursorEditor(value: unknown): value is CursorEditor { return ( - YjsEditor.isYjsEditor(value) && - (value as CursorEditor).awareness && - typeof (value as CursorEditor).cursorDataField === 'string' && - typeof (value as CursorEditor).selectionStateField === 'string' && - typeof (value as CursorEditor).sendCursorPosition === 'function' && - typeof (value as CursorEditor).sendCursorData === 'function' + YjsEditor.isYjsEditor(value) + && (value as CursorEditor).awareness + && typeof (value as CursorEditor).cursorDataField === 'string' + && typeof (value as CursorEditor).selectionStateField === 'string' + && typeof (value as CursorEditor).sendCursorPosition === 'function' + && typeof (value as CursorEditor).sendCursorData === 'function' ) }, sendCursorPosition<TCursorData extends Record<string, unknown>>( editor: CursorEditor<TCursorData>, - range: Range | null = editor.selection + range: Range | null = editor.selection, ) { editor.sendCursorPosition(range) }, sendCursorData<TCursorData extends Record<string, unknown>>( editor: CursorEditor<TCursorData>, - data: TCursorData + data: TCursorData, ) { editor.sendCursorData(data) }, @@ -64,27 +65,29 @@ export const CursorEditor = { on<TCursorData extends Record<string, unknown>>( editor: CursorEditor<TCursorData>, event: 'change', - handler: RemoteCursorChangeEventListener + handler: RemoteCursorChangeEventListener, ) { if (event !== 'change') { return } const listeners = CURSOR_CHANGE_EVENT_LISTENERS.get(editor) ?? new Set() + listeners.add(handler) - if (editor) CURSOR_CHANGE_EVENT_LISTENERS.set(editor, listeners) + if (editor) { CURSOR_CHANGE_EVENT_LISTENERS.set(editor, listeners) } }, off<TCursorData extends Record<string, unknown>>( editor: CursorEditor<TCursorData>, event: 'change', - listener: RemoteCursorChangeEventListener + listener: RemoteCursorChangeEventListener, ) { if (event !== 'change') { return } const listeners = CURSOR_CHANGE_EVENT_LISTENERS.get(editor) + if (listeners) { listeners.delete(listener) } @@ -92,13 +95,14 @@ export const CursorEditor = { cursorState<TCursorData extends Record<string, unknown>>( editor: CursorEditor<TCursorData>, - clientId: number + clientId: number, ): CursorState<TCursorData> | null { if (clientId === editor.awareness.clientID || !YjsEditor.connected(editor)) { return null } const state = editor.awareness.getStates().get(clientId) + if (!state) { return null } @@ -111,7 +115,7 @@ export const CursorEditor = { }, cursorStates<TCursorData extends Record<string, unknown>>( - editor: CursorEditor<TCursorData> + editor: CursorEditor<TCursorData>, ): Record<string, CursorState<TCursorData>> { if (!YjsEditor.connected(editor)) { return {} @@ -131,13 +135,13 @@ export const CursorEditor = { data: state[editor.cursorDataField], }, ] - }).filter(Array.isArray) + }).filter(Array.isArray), ) }, } export type WithCursorsOptions< - TCursorData extends Record<string, unknown> = Record<string, unknown> + TCursorData extends Record<string, unknown> = Record<string, unknown>, > = { // Local state field used to store the user selection cursorStateField?: string @@ -151,9 +155,9 @@ export type WithCursorsOptions< export function withCursors<TCursorData extends Record<string, unknown>>( awareness: Awareness, - options: WithCursorsOptions<TCursorData> = {} + options: WithCursorsOptions<TCursorData> = {}, ) { - return function <T extends YjsEditor>(editor: T): T & CursorEditor<TCursorData> { + return function <T extends YjsEditor> (editor: T): T & CursorEditor<TCursorData> { const { cursorStateField: selectionStateField = 'selection', cursorDataField = 'data', @@ -185,9 +189,9 @@ export function withCursors<TCursorData extends Record<string, unknown>>( const { anchor, focus } = slateRangeToRelativeRange(e.sharedRoot, e, range) if ( - !currentRange || - !Y.compareRelativePositions(anchor, currentRange) || - !Y.compareRelativePositions(focus, currentRange) + !currentRange + || !Y.compareRelativePositions(anchor, currentRange) + || !Y.compareRelativePositions(focus, currentRange) ) { e.awareness.setLocalStateField(e.selectionStateField, { anchor, focus }) } @@ -195,6 +199,7 @@ export function withCursors<TCursorData extends Record<string, unknown>>( const awarenessChangeListener: RemoteCursorChangeEventListener = yEvent => { const listeners = CURSOR_CHANGE_EVENT_LISTENERS.get(e) + if (!listeners) { return } @@ -212,6 +217,7 @@ export function withCursors<TCursorData extends Record<string, unknown>>( } const { connect, disconnect } = e + e.connect = () => { connect() @@ -229,6 +235,7 @@ export function withCursors<TCursorData extends Record<string, unknown>>( } const { onChange } = e + e.onChange = () => { onChange() diff --git a/packages/yjs/src/plugins/withYHistory.ts b/packages/yjs/src/plugins/withYHistory.ts index 7caed66bb..057d324a4 100644 --- a/packages/yjs/src/plugins/withYHistory.ts +++ b/packages/yjs/src/plugins/withYHistory.ts @@ -1,5 +1,6 @@ import { Editor, Transforms } from 'slate' import * as Y from 'yjs' + import { HistoryStackItem, RelativeRange } from '../module/custom-types' import { relativeRangeToSlateRange, slateRangeToRelativeRange } from '../utils/position' import { YjsEditor } from './withYjs' @@ -19,11 +20,11 @@ export type YHistoryEditor = YjsEditor & { export const YHistoryEditor = { isYHistoryEditor(value: unknown): value is YHistoryEditor { return ( - YjsEditor.isYjsEditor(value) && - (value as YHistoryEditor).undoManager instanceof Y.UndoManager && - typeof (value as YHistoryEditor).undo === 'function' && - typeof (value as YHistoryEditor).redo === 'function' && - 'withoutSavingOrigin' in value + YjsEditor.isYjsEditor(value) + && (value as YHistoryEditor).undoManager instanceof Y.UndoManager + && typeof (value as YHistoryEditor).undo === 'function' + && typeof (value as YHistoryEditor).redo === 'function' + && 'withoutSavingOrigin' in value ) }, @@ -49,7 +50,7 @@ export type WithYHistoryOptions = NonNullable<ConstructorParameters<typeof Y.Und } export function withYHistory(options: WithYHistoryOptions = {}) { - return function <T extends YjsEditor>(editor: T): T & YHistoryEditor { + return function <T extends YjsEditor> (editor: T): T & YHistoryEditor { const e = editor as T & YHistoryEditor // 将 trackedOrigins 的初始化放到 editor 被传入之后 @@ -68,6 +69,7 @@ export function withYHistory(options: WithYHistoryOptions = {}) { e.withoutSavingOrigin = withoutSavingOrigin const { onChange, isLocalOrigin } = e + e.onChange = () => { onChange() @@ -84,7 +86,7 @@ export function withYHistory(options: WithYHistoryOptions = {}) { }) => { stackItem.meta.set( 'selection', - e.selection && slateRangeToRelativeRange(e.sharedRoot, e, e.selection) + e.selection && slateRangeToRelativeRange(e.sharedRoot, e, e.selection), ) stackItem.meta.set('selectionBefore', LAST_SELECTION.get(e)) } @@ -97,7 +99,7 @@ export function withYHistory(options: WithYHistoryOptions = {}) { }) => { stackItem.meta.set( 'selection', - e.selection && slateRangeToRelativeRange(e.sharedRoot, e, e.selection) + e.selection && slateRangeToRelativeRange(e.sharedRoot, e, e.selection), ) } @@ -111,6 +113,7 @@ export function withYHistory(options: WithYHistoryOptions = {}) { // TODO: Change once https://github.com/yjs/yjs/issues/353 is resolved const inverseStack = type === 'undo' ? e.undoManager.redoStack : e.undoManager.undoStack const inverseItem = inverseStack[inverseStack.length - 1] + if (inverseItem) { inverseItem.meta.set('selection', stackItem.meta.get('selectionBefore')) inverseItem.meta.set('selectionBefore', stackItem.meta.get('selection')) @@ -132,6 +135,7 @@ export function withYHistory(options: WithYHistoryOptions = {}) { } const { connect, disconnect } = e + e.connect = () => { connect() diff --git a/packages/yjs/src/plugins/withYjs.ts b/packages/yjs/src/plugins/withYjs.ts index 914d3eba4..01f4e9039 100644 --- a/packages/yjs/src/plugins/withYjs.ts +++ b/packages/yjs/src/plugins/withYjs.ts @@ -1,5 +1,8 @@ -import { BaseEditor, Descendant, Editor, Operation, Point } from 'slate' +import { + BaseEditor, Descendant, Editor, Operation, Point, +} from 'slate' import * as Y from 'yjs' + import { applyYjsEvents } from '../applyToSlate' import { applySlateOp } from '../applyToYjs' import { yTextToSlateElement } from '../utils/convert' @@ -46,16 +49,16 @@ export type YjsEditor = BaseEditor & { export const YjsEditor = { isYjsEditor(value: unknown): value is YjsEditor { return ( - Editor.isEditor(value) && - (value as YjsEditor).sharedRoot instanceof Y.XmlText && - 'localOrigin' in value && - 'positionStorageOrigin' in value && - typeof (value as YjsEditor).applyRemoteEvents === 'function' && - typeof (value as YjsEditor).storeLocalChange === 'function' && - typeof (value as YjsEditor).flushLocalChanges === 'function' && - typeof (value as YjsEditor).isLocalOrigin === 'function' && - typeof (value as YjsEditor).connect === 'function' && - typeof (value as YjsEditor).disconnect === 'function' + Editor.isEditor(value) + && (value as YjsEditor).sharedRoot instanceof Y.XmlText + && 'localOrigin' in value + && 'positionStorageOrigin' in value + && typeof (value as YjsEditor).applyRemoteEvents === 'function' + && typeof (value as YjsEditor).storeLocalChange === 'function' + && typeof (value as YjsEditor).flushLocalChanges === 'function' + && typeof (value as YjsEditor).isLocalOrigin === 'function' + && typeof (value as YjsEditor).connect === 'function' + && typeof (value as YjsEditor).disconnect === 'function' ) }, @@ -93,11 +96,13 @@ export const YjsEditor = { origin(editor: YjsEditor): unknown { const origin = ORIGIN.get(editor) + return origin !== undefined ? origin : editor.localOrigin }, withOrigin(editor: YjsEditor, origin: unknown, fn: () => void): void { const prev = YjsEditor.origin(editor) + ORIGIN.set(editor, origin) fn() ORIGIN.set(editor, prev) @@ -105,6 +110,7 @@ export const YjsEditor = { storePosition(editor: YjsEditor, key: string, point: Point): void { const { sharedRoot, positionStorageOrigin: locationStorageOrigin } = editor + assertDocumentAttachment(sharedRoot) const position = slatePointToRelativePosition(sharedRoot, editor, point) @@ -116,6 +122,7 @@ export const YjsEditor = { removeStoredPosition(editor: YjsEditor, key: string): void { const { sharedRoot, positionStorageOrigin: locationStorageOrigin } = editor + assertDocumentAttachment(sharedRoot) sharedRoot.doc.transact(() => { @@ -125,6 +132,7 @@ export const YjsEditor = { position(editor: YjsEditor, key: string): Point | null | undefined { const position = getStoredPosition(editor.sharedRoot, key) + if (!position) { return undefined } @@ -148,7 +156,7 @@ export type WithYjsOptions = { } export function withYjs(sharedRoot: Y.XmlText, options: WithYjsOptions = {}) { - return function <T extends Editor>(editor: T): T & YjsEditor { + return function <T extends Editor> (editor: T): T & YjsEditor { const e = editor as T & YjsEditor e.sharedRoot = sharedRoot @@ -177,6 +185,7 @@ export function withYjs(sharedRoot: Y.XmlText, options: WithYjsOptions = {}) { } let autoConnectTimeoutId: ReturnType<typeof setTimeout> | null = null + if (options.autoConnect) { autoConnectTimeoutId = setTimeout(() => { autoConnectTimeoutId = null @@ -191,6 +200,7 @@ export function withYjs(sharedRoot: Y.XmlText, options: WithYjsOptions = {}) { e.sharedRoot.observeDeep(handleYEvents) const content = yTextToSlateElement(e.sharedRoot) + e.children = content.children CONNECTED.add(e) @@ -220,11 +230,14 @@ export function withYjs(sharedRoot: Y.XmlText, options: WithYjsOptions = {}) { e.flushLocalChanges = () => { assertDocumentAttachment(e.sharedRoot) const localChanges = YjsEditor.localChanges(e) + LOCAL_CHANGES.delete(e) const txGroups: LocalChange[][] = [] + localChanges.forEach(change => { const currentGroup = txGroups[txGroups.length - 1] + if (currentGroup && currentGroup[0].origin === change.origin) { return currentGroup.push(change) } @@ -245,6 +258,7 @@ export function withYjs(sharedRoot: Y.XmlText, options: WithYjsOptions = {}) { } const { apply, onChange } = e + e.apply = op => { if (YjsEditor.connected(e) && YjsEditor.isLocal(e)) { YjsEditor.storeLocalChange(e, op) diff --git a/packages/yjs/src/utils/clone.ts b/packages/yjs/src/utils/clone.ts index dd179da8c..87ce71687 100644 --- a/packages/yjs/src/utils/clone.ts +++ b/packages/yjs/src/utils/clone.ts @@ -1,4 +1,5 @@ import * as Y from 'yjs' + import { InsertDelta } from '../module/custom-types' import { yTextToInsertDelta } from './delta' @@ -17,6 +18,7 @@ export function cloneDeep(yText: Y.XmlText): Y.XmlText { const clone = new Y.XmlText() const attributes = yText.getAttributes() + Object.entries(attributes).forEach(([key, value]) => { clone.setAttribute(key, value) }) diff --git a/packages/yjs/src/utils/convert.ts b/packages/yjs/src/utils/convert.ts index fcfb9bfe4..6cc913f1a 100644 --- a/packages/yjs/src/utils/convert.ts +++ b/packages/yjs/src/utils/convert.ts @@ -1,5 +1,6 @@ import { Element, Node, Text } from 'slate' import * as Y from 'yjs' + import { DeltaInsert, InsertDelta } from '../module/custom-types' import { yTextToInsertDelta } from './delta' import { getProperties } from './slate' diff --git a/packages/yjs/src/utils/delta.ts b/packages/yjs/src/utils/delta.ts index 457266714..3e6abcc2f 100644 --- a/packages/yjs/src/utils/delta.ts +++ b/packages/yjs/src/utils/delta.ts @@ -1,4 +1,5 @@ import * as Y from 'yjs' + import { DeltaInsert, InsertDelta } from '../module/custom-types' import { deepEquals } from './object' @@ -11,15 +12,15 @@ export function normalizeInsertDelta(delta: InsertDelta): InsertDelta { } const prev = normalized[normalized.length - 1] + if (!prev || typeof prev.insert !== 'string' || typeof element.insert !== 'string') { normalized.push(element) continue } - const merge = - prev.attributes === element.attributes || - (!prev.attributes === !element.attributes && - deepEquals(prev.attributes ?? {}, element.attributes ?? {})) + const merge = prev.attributes === element.attributes + || (!prev.attributes === !element.attributes + && deepEquals(prev.attributes ?? {}, element.attributes ?? {})) if (merge) { prev.insert += element.insert diff --git a/packages/yjs/src/utils/location.ts b/packages/yjs/src/utils/location.ts index 1d6be3c26..090c17d0f 100644 --- a/packages/yjs/src/utils/location.ts +++ b/packages/yjs/src/utils/location.ts @@ -1,5 +1,8 @@ -import { Element, Node, Path, Text } from 'slate' +import { + Element, Node, Path, Text, +} from 'slate' import * as Y from 'yjs' + import { YTarget } from '../module/custom-types' import { sliceInsertDelta, yTextToInsertDelta } from './delta' @@ -35,11 +38,13 @@ export function getYTarget(yRoot: Y.XmlText, slateRoot: Node, path: Path): YTarg const targetLength = getSlateNodeYLength(targetNode) const targetDelta = sliceInsertDelta(delta, yOffset, targetLength) + if (targetDelta.length > 1) { throw new Error("Path doesn't match yText, yTarget spans multiple nodes") } const yTarget = targetDelta[0]?.insert + if (childPath.length > 0) { if (!(yTarget instanceof Y.XmlText)) { throw new Error("Path doesn't match yText, cannot descent into non-yText") @@ -61,12 +66,13 @@ export function getYTarget(yRoot: Y.XmlText, slateRoot: Node, path: Path): YTarg export function yOffsetToSlateOffsets( parent: Element, yOffset: number, - opts: { assoc?: number; insert?: boolean } = {} + opts: { assoc?: number; insert?: boolean } = {}, ): [number, number] { const { assoc = 0, insert = false } = opts let currentOffset = 0 let lastNonEmptyPathOffset = 0 + for (let pathOffset = 0; pathOffset < parent.children.length; pathOffset++) { const child = parent.children[pathOffset] const nodeLength = Text.isText(child) ? child.text.length : 1 @@ -76,6 +82,7 @@ export function yOffsetToSlateOffsets( } const endOffset = currentOffset + nodeLength + if (nodeLength > 0 && (assoc >= 0 ? endOffset > yOffset : endOffset >= yOffset)) { return [pathOffset, yOffset - currentOffset] } @@ -93,11 +100,13 @@ export function yOffsetToSlateOffsets( const child = parent.children[lastNonEmptyPathOffset] const textOffset = Text.isText(child) ? child.text.length : 1 + return [lastNonEmptyPathOffset, textOffset] } export function getSlatePath(sharedRoot: Y.XmlText, slateRoot: Node, yText: Y.XmlText): Path { const yNodePath = [yText] + while (yNodePath[0] !== sharedRoot) { const { parent: yParent } = yNodePath[0] @@ -117,14 +126,17 @@ export function getSlatePath(sharedRoot: Y.XmlText, slateRoot: Node, yText: Y.Xm } let slateParent = slateRoot + return yNodePath.reduce<Path>((path, yParent, idx) => { const yChild = yNodePath[idx + 1] + if (!yChild) { return path } let yOffset = 0 const currentDelta = yTextToInsertDelta(yParent) + for (const element of currentDelta) { if (element.insert === yChild) { break @@ -138,6 +150,7 @@ export function getSlatePath(sharedRoot: Y.XmlText, slateRoot: Node, yText: Y.Xm } const [pathOffset] = yOffsetToSlateOffsets(slateParent, yOffset) + slateParent = slateParent.children[pathOffset] return path.concat(pathOffset) }, []) diff --git a/packages/yjs/src/utils/object.ts b/packages/yjs/src/utils/object.ts index 0e99384fc..e5e1a550d 100644 --- a/packages/yjs/src/utils/object.ts +++ b/packages/yjs/src/utils/object.ts @@ -1,3 +1,5 @@ +import { BaseElement, Descendant } from 'slate' + type InspectableObject = Record<string | number | symbol, unknown> function isObject(o: unknown): o is InspectableObject { @@ -11,12 +13,14 @@ export function isPlainObject(o: unknown): o is InspectableObject { // If has modified constructor const ctor = o.constructor + if (ctor === undefined) { return true } // If has modified prototype const prot = ctor.prototype + if (isObject(prot) === false) { return false } @@ -44,7 +48,7 @@ export function deepEquals(node: InspectableObject, another: InspectableObject): return false } } else if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) return false + if (a.length !== b.length) { return false } for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return false @@ -64,25 +68,27 @@ export function deepEquals(node: InspectableObject, another: InspectableObject): return true } -export function pick<TObj, TKeys extends keyof TObj>( +export function pick<TObj extends Object, TKeys extends keyof TObj>( obj: TObj, ...keys: TKeys[] ): Pick<TObj, TKeys> { return Object.fromEntries( - Object.entries(obj).filter(([key]) => keys.includes(key as TKeys)) + Object.entries(obj).filter(([key]) => keys.includes(key as TKeys)), ) as Pick<TObj, TKeys> } -export function omit<TObj, TKeys extends keyof TObj>( +export function omit<TObj extends Object, TKeys extends keyof TObj>( obj: TObj, ...keys: TKeys[] ): Omit<TObj, TKeys> { return Object.fromEntries( - Object.entries(obj).filter(([key]) => !keys.includes(key as TKeys)) + Object.entries(obj).filter(([key]) => !keys.includes(key as TKeys)), ) as Omit<TObj, TKeys> } -export function omitNullEntries<TObj>(obj: TObj): { +export function omitNullEntries<TObj extends Record<string, unknown>>( + obj: TObj, +): { [K in keyof TObj]: TObj[K] extends null ? never : K } { return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== null)) as { diff --git a/packages/yjs/src/utils/position.ts b/packages/yjs/src/utils/position.ts index 6afd8c22a..746918be3 100644 --- a/packages/yjs/src/utils/position.ts +++ b/packages/yjs/src/utils/position.ts @@ -1,5 +1,8 @@ -import { BasePoint, BaseRange, Node, Text } from 'slate' +import { + BasePoint, BaseRange, Node, Text, +} from 'slate' import * as Y from 'yjs' + import { InsertDelta, RelativeRange, TextRange } from '../module/custom-types' import { getInsertDeltaLength, yTextToInsertDelta } from './delta' import { getSlatePath, getYTarget, yOffsetToSlateOffsets } from './location' @@ -10,7 +13,7 @@ export const STORED_POSITION_PREFIX = '__slateYjsStoredPosition_' export function slatePointToRelativePosition( sharedRoot: Y.XmlText, slateRoot: Node, - point: BasePoint + point: BasePoint, ): Y.RelativePosition { const { yTarget, yParent, textRange } = getYTarget(sharedRoot, slateRoot, point.path) @@ -19,13 +22,14 @@ export function slatePointToRelativePosition( } const index = textRange.start + point.offset + return Y.createRelativePositionFromTypeIndex(yParent, index, index === textRange.end ? -1 : 0) } export function absolutePositionToSlatePoint( sharedRoot: Y.XmlText, slateRoot: Node, - { type, index, assoc }: Y.AbsolutePosition + { type, index, assoc }: Y.AbsolutePosition, ): BasePoint | null { if (!(type instanceof Y.XmlText)) { throw new Error('Absolute position points to a non-XMLText') @@ -43,6 +47,7 @@ export function absolutePositionToSlatePoint( }) const target = parent.children[pathOffset] + if (!Text.isText(target)) { return null } @@ -53,7 +58,7 @@ export function absolutePositionToSlatePoint( export function relativePositionToSlatePoint( sharedRoot: Y.XmlText, slateRoot: Node, - pos: Y.RelativePosition + pos: Y.RelativePosition, ): BasePoint | null { if (!sharedRoot.doc) { throw new Error("sharedRoot isn't attach to a yDoc") @@ -66,6 +71,7 @@ export function relativePositionToSlatePoint( export function getStoredPosition(sharedRoot: Y.XmlText, key: string): Y.RelativePosition | null { const rawPosition = sharedRoot.getAttribute(STORED_POSITION_PREFIX + key) + if (!rawPosition) { return null } @@ -80,7 +86,7 @@ export function getStoredPositions(sharedRoot: Y.XmlText): Record<string, Y.Rela .map(([key, position]) => [ key.slice(STORED_POSITION_PREFIX.length), Y.createRelativePositionFromJSON(position), - ]) + ]), ) } @@ -91,16 +97,15 @@ function getStoredPositionsAbsolute(sharedRoot: Y.XmlText) { Object.entries(sharedRoot.getAttributes()) .filter(([key]) => key.startsWith(STORED_POSITION_PREFIX)) .map( - ([key, position]) => - [ - key.slice(STORED_POSITION_PREFIX.length), - Y.createAbsolutePositionFromRelativePosition( - Y.decodeRelativePosition(position), - sharedRoot.doc - ), - ] as const + ([key, position]) => [ + key.slice(STORED_POSITION_PREFIX.length), + Y.createAbsolutePositionFromRelativePosition( + Y.decodeRelativePosition(position), + sharedRoot.doc, + ), + ] as const, ) - .filter(([, position]) => position) + .filter(([, position]) => position), ) as Record<string, Y.AbsolutePosition> } @@ -111,7 +116,7 @@ export function removeStoredPosition(sharedRoot: Y.XmlText, key: string) { export function setStoredPosition( sharedRoot: Y.XmlText, key: string, - position: Y.RelativePosition + position: Y.RelativePosition, ) { sharedRoot.setAttribute(STORED_POSITION_PREFIX + key, Y.encodeRelativePosition(position)) } @@ -119,7 +124,7 @@ export function setStoredPosition( function getAbsolutePositionsInTextRange( absolutePositions: Record<string, Y.AbsolutePosition>, yTarget: Y.XmlText, - textRange?: TextRange + textRange?: TextRange, ) { return Object.fromEntries( Object.entries(absolutePositions).filter(([, position]) => { @@ -134,20 +139,21 @@ function getAbsolutePositionsInTextRange( return position.assoc >= 0 ? position.index >= textRange.start && position.index < textRange.end : position.index > textRange.start && position.index >= textRange.end - }) + }), ) } function getAbsolutePositionsInYText( absolutePositions: Record<string, Y.AbsolutePosition>, yText: Y.XmlText, - parentPath = '' + parentPath = '', ): Record<string, Record<string, Y.AbsolutePosition>> { const positions = { [parentPath]: getAbsolutePositionsInTextRange(absolutePositions, yText), } const insertDelta = yTextToInsertDelta(yText) + insertDelta.forEach(({ insert }, i) => { if (insert instanceof Y.XmlText) { Object.assign( @@ -155,8 +161,8 @@ function getAbsolutePositionsInYText( getAbsolutePositionsInYText( absolutePositions, insert, - parentPath ? `${parentPath}.${i}` : i.toString() - ) + parentPath ? `${parentPath}.${i}` : i.toString(), + ), ) } }) @@ -168,7 +174,7 @@ export function getStoredPositionsInDeltaAbsolute( sharedRoot: Y.XmlText, yText: Y.XmlText, delta: InsertDelta, - deltaOffset = 0 + deltaOffset = 0, ) { const absolutePositions = getStoredPositionsAbsolute(sharedRoot) @@ -195,7 +201,7 @@ export function restoreStoredPositionsWithDeltaAbsolute( delta: InsertDelta, newDeltaOffset = 0, previousDeltaOffset = 0, - path = '' + path = '', ) { const toRestore = absolutePositions[path] @@ -207,8 +213,8 @@ export function restoreStoredPositionsWithDeltaAbsolute( Y.createRelativePositionFromTypeIndex( yText, position.index - previousDeltaOffset + newDeltaOffset, - position.assoc - ) + position.assoc, + ), ) }) } @@ -222,7 +228,7 @@ export function restoreStoredPositionsWithDeltaAbsolute( yTextToInsertDelta(insert), 0, 0, - path ? `${path}.${i}` : i.toString() + path ? `${path}.${i}` : i.toString(), ) } }) @@ -231,7 +237,7 @@ export function restoreStoredPositionsWithDeltaAbsolute( export function slateRangeToRelativeRange( sharedRoot: Y.XmlText, slateRoot: Node, - range: BaseRange + range: BaseRange, ): RelativeRange { return { anchor: slatePointToRelativePosition(sharedRoot, slateRoot, range.anchor), @@ -242,7 +248,7 @@ export function slateRangeToRelativeRange( export function relativeRangeToSlateRange( sharedRoot: Y.XmlText, slateRoot: Node, - range: RelativeRange + range: RelativeRange, ): BaseRange | null { const anchor = relativePositionToSlatePoint(sharedRoot, slateRoot, range.anchor) diff --git a/packages/yjs/src/utils/slate.ts b/packages/yjs/src/utils/slate.ts index 555a3d476..5ea5c1abe 100644 --- a/packages/yjs/src/utils/slate.ts +++ b/packages/yjs/src/utils/slate.ts @@ -1,8 +1,9 @@ import { BaseText, Descendant, Text } from 'slate' + import { omit } from './object' export function getProperties<TNode extends Descendant>( - node: TNode + node: TNode, ): Omit<TNode, TNode extends BaseText ? 'text' : 'children'> { return omit(node, (Text.isText(node) ? 'text' : 'children') as keyof TNode) as Omit< TNode, diff --git a/packages/yjs/src/utils/yjs.ts b/packages/yjs/src/utils/yjs.ts index 2d20ea1d6..92b707616 100644 --- a/packages/yjs/src/utils/yjs.ts +++ b/packages/yjs/src/utils/yjs.ts @@ -2,7 +2,7 @@ import * as Y from 'yjs' // eslint-disable-next-line @typescript-eslint/no-explicit-any export function assertDocumentAttachment<T extends Y.AbstractType<any>>( - sharedType: T + sharedType: T, ): asserts sharedType is T & { doc: NonNullable<T['doc']> } { if (!sharedType.doc) { throw new Error("shared type isn't attached to a document") diff --git a/scripts/release-tag.js b/scripts/release-tag.js index 0e18d799b..63e9c581c 100644 --- a/scripts/release-tag.js +++ b/scripts/release-tag.js @@ -1,11 +1,13 @@ const util = require('util') const exec = util.promisify(require('child_process').exec) + const DEFAULT_RELEASE_COMMIT_MESSAGE = 'chore: release tag' function command(command) { return exec(command, { cwd: process.cwd() }) .then(resp => { const data = resp.stdout.toString() + return Promise.resolve(data) }) .catch(err => { @@ -17,6 +19,7 @@ async function run(commitMsg = DEFAULT_RELEASE_COMMIT_MESSAGE) { const timestamp = Date.now() const tagName = `v${timestamp}` // 先打触发 publish ci 的标签 + await command(`git tag -a ${tagName} -m"${commitMsg}"`) // 推送标签到远程触发 ci await command(`git push origin ${tagName}`) diff --git a/tests/setup/index.ts b/tests/setup/index.ts index 948d72223..7eecb052f 100644 --- a/tests/setup/index.ts +++ b/tests/setup/index.ts @@ -1,9 +1,17 @@ import '@testing-library/jest-dom' + import nodeCrypto from 'crypto' +jest.spyOn(global.console, 'warn').mockImplementation(() => jest.fn()) +jest.spyOn(global.console, 'error').mockImplementation(() => jest.fn()) + +jest.mock('nanoid', () => ({ + nanoid: () => '1', +})) + // @ts-ignore global.crypto = { - getRandomValues: function (buffer: any) { + getRandomValues(buffer: any) { return nodeCrypto.randomFillSync(buffer) }, } @@ -11,30 +19,35 @@ global.crypto = { // Jest environment not contains DataTransfer object, so mock a DataTransfer class // @ts-ignore global.DataTransfer = class DataTransfer { - clearData() { } + clearData() {} + getData(type: string) { - if (type === 'text/plain') return '' + if (type === 'text/plain') { return '' } return [] } - setData() { } + + setData() {} + get files() { return [new File(['124'], 'test.jpg')] } } - global.ResizeObserver = class ResizeObserver { constructor(callback) { // @ts-ignore - this.callback = callback; + this.callback = callback } + observe() { // 可以根据需要添加具体实现 } + unobserve() { // 可以根据需要添加具体实现 } + disconnect() { // 可以根据需要添加具体实现 } -} \ No newline at end of file +} diff --git a/tests/utils/create-editor.ts b/tests/utils/create-editor.ts index 571b3304a..7880561b5 100644 --- a/tests/utils/create-editor.ts +++ b/tests/utils/create-editor.ts @@ -6,6 +6,7 @@ import { createEditor as create } from '../../packages/editor/src' export default function createEditor(options: any = {}) { const container = document.createElement('div') + document.body.appendChild(container) return create({ diff --git a/tests/utils/create-toolbar.ts b/tests/utils/create-toolbar.ts index eab8fb1b8..5274b5d9f 100644 --- a/tests/utils/create-toolbar.ts +++ b/tests/utils/create-toolbar.ts @@ -6,6 +6,7 @@ import { createToolbar as create } from '../../packages/editor/src' export default function createToolbar(editor: any, config: any = {}) { const container = document.createElement('div') + document.body.appendChild(container) return create({ diff --git a/tsconfig.json b/tsconfig.json index 92961dfa4..552879314 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "es5", - "module": "ES2015", + "target": "es2017", + "module": "esnext", "lib": [ "es6", "dom", @@ -26,5 +26,8 @@ "build", "__tests__" ], - "include": ["./tests/setup/index.ts", "./packages/custom-types.d.ts"] + "include": [ + "./tests/setup/index.ts", + "./packages/custom-types.d.ts" + ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cecbbd36d..1449084df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,9 +2,9 @@ # yarn lockfile v1 -"@adobe/css-tools@^4.0.1": +"@adobe/css-tools@^4.4.0": version "4.4.0" - resolved "https://registry.npmmirror.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63" + resolved "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63" integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ== "@ampproject/remapping@^2.2.0": @@ -40,7 +40,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.24.7", "@babel/core@^7.7.2", "@babel/core@^7.8.0": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.24.7": version "7.25.2" resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== @@ -244,7 +244,7 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3", "@babel/parser@^7.25.6": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3", "@babel/parser@^7.25.6": version "7.25.6" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== @@ -330,14 +330,14 @@ "@babel/plugin-syntax-class-properties@^7.12.13": version "7.12.13" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/download/@babel/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha1-tcmHJ0xKOoK4lxR5aTGmtTVErhA= + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-class-static-block@^7.14.5": version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -364,15 +364,15 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.24.7" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" - integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== + version "7.25.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz#6d4c78f042db0e82fd6436cd65fec5dc78ad2bde" + integrity sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" @@ -384,7 +384,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.24.7": +"@babel/plugin-syntax-jsx@^7.24.7", "@babel/plugin-syntax-jsx@^7.7.2": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== @@ -393,8 +393,8 @@ "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/download/@babel/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha1-ypHvRjA1MESLkGZSusLp/plB9pk= + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" @@ -407,8 +407,8 @@ "@babel/plugin-syntax-numeric-separator@^7.10.4": version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/download/@babel/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha1-ubBws+M1cM2f0Hun+pHA3Te5r5c= + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" @@ -435,15 +435,15 @@ "@babel/plugin-syntax-private-property-in-object@^7.14.5": version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-top-level-await@^7.14.5": version "7.14.5" - resolved "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/download/@babel/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha1-wc/a3DWmRiQAAfBhOCR7dBw02Uw= + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -1007,7 +1007,7 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== @@ -1030,7 +1030,7 @@ "@babel/parser" "^7.25.0" "@babel/types" "^7.25.0" -"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": version "7.25.6" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== @@ -1526,66 +1526,66 @@ js-yaml "^3.13.1" resolve-from "^5.0.0" -"@istanbuljs/schema@^0.1.2": +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" - integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^27.5.1" - jest-util "^27.5.1" + jest-message-util "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" -"@jest/core@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" - integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== - dependencies: - "@jest/console" "^27.5.1" - "@jest/reporters" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - emittery "^0.8.1" + ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^27.5.1" - jest-config "^27.5.1" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-resolve-dependencies "^27.5.1" - jest-runner "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - jest-watcher "^27.5.1" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" micromatch "^4.0.4" - rimraf "^3.0.0" + pretty-format "^29.7.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" - integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== dependencies: - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^27.5.1" + jest-mock "^29.7.0" "@jest/expect-utils@^29.7.0": version "29.7.0" @@ -1594,57 +1594,65 @@ dependencies: jest-get-type "^29.6.3" -"@jest/fake-timers@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" - integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== dependencies: - "@jest/types" "^27.5.1" - "@sinonjs/fake-timers" "^8.0.1" + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-util "^27.5.1" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" -"@jest/globals@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" - integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== dependencies: - "@jest/environment" "^27.5.1" - "@jest/types" "^27.5.1" - expect "^27.5.1" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" -"@jest/reporters@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" - integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" - glob "^7.1.2" + glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" + istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-haste-map "^27.5.1" - jest-resolve "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" slash "^3.0.0" - source-map "^0.6.0" string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^8.1.0" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" "@jest/schemas@^29.6.3": version "29.6.3" @@ -1653,76 +1661,55 @@ dependencies: "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" - integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== dependencies: + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" - source-map "^0.6.0" -"@jest/test-result@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" - integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== dependencies: - "@jest/console" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" - integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== dependencies: - "@jest/test-result" "^27.5.1" + "@jest/test-result" "^29.7.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-runtime "^27.5.1" + jest-haste-map "^29.7.0" + slash "^3.0.0" -"@jest/transform@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" - integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^27.5.1" + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-regex-util "^27.5.1" - jest-util "^27.5.1" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/types@^25.5.0": - version "25.5.0" - resolved "https://registry.npmmirror.com/@jest/types/download/@jest/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" - integrity sha1-TWpHk/e5WZ/DaAh3uFapfbzPKp0= - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - -"@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" + write-file-atomic "^4.0.2" "@jest/types@^29.6.3": version "29.6.3" @@ -1768,7 +1755,7 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -1908,19 +1895,19 @@ resolved "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sinonjs/commons@^1.7.0": - version "1.8.6" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" - integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^8.0.1": - version "8.1.0" - resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" - integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: - "@sinonjs/commons" "^1.7.0" + "@sinonjs/commons" "^3.0.0" "@testing-library/dom@^10.4.0": version "10.4.0" @@ -1936,25 +1923,23 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.14.1": - version "5.17.0" - resolved "https://registry.npmmirror.com/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz#5e97c8f9a15ccf4656da00fecab505728de81e0c" - integrity sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg== +"@testing-library/jest-dom@6.5.0": + version "6.5.0" + resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz#50484da3f80fb222a853479f618a9ce5c47bfe54" + integrity sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA== dependencies: - "@adobe/css-tools" "^4.0.1" - "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" + "@adobe/css-tools" "^4.4.0" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" - lodash "^4.17.15" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" redent "^3.0.0" -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@transloadit/prettier-bytes@0.0.7": version "0.0.7" @@ -1971,7 +1956,7 @@ resolved "https://registry.npmmirror.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": +"@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== @@ -1997,7 +1982,7 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": version "7.20.6" resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== @@ -2041,7 +2026,7 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/graceful-fs@^4.1.2": +"@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== @@ -2070,14 +2055,6 @@ dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^1.1.1": - version "1.1.2" - resolved "https://registry.npmmirror.com/@types/istanbul-reports/download/@types/istanbul-reports-1.1.2.tgz?cache=0&sync_timestamp=1637266271844&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fistanbul-reports%2Fdownload%2F%40types%2Fistanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" - integrity sha1-6HXMaJ5HvOVJ7IHz315vbxHPrrI= - dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" - "@types/istanbul-reports@^3.0.0": version "3.0.4" resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" @@ -2085,21 +2062,22 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*": - version "29.5.12" - resolved "https://registry.npmmirror.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" - integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== +"@types/jest@^29.5.13": + version "29.5.13" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc" + integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg== dependencies: expect "^29.0.0" pretty-format "^29.0.0" -"@types/jest@^25.2.1": - version "25.2.3" - resolved "https://registry.npmmirror.com/@types/jest/download/@types/jest-25.2.3.tgz?cache=0&sync_timestamp=1637263851129&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fjest%2Fdownload%2F%40types%2Fjest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" - integrity sha1-M9J+TEcWyq5OztNVCXpHrTY/3K8= +"@types/jsdom@^20.0.0": + version "20.0.1" + resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" + integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" "@types/json-schema@^7.0.9": version "7.0.15" @@ -2117,9 +2095,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*": - version "22.6.1" - resolved "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz#e531a45f4d78f14a8468cb9cdc29dc9602afc7ac" - integrity sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw== + version "22.7.0" + resolved "https://registry.npmjs.org/@types/node/-/node-22.7.0.tgz#670aa1874bc836863e5c116f9f2c32416ff27e1f" + integrity sha512-MOdOibwBs6KW1vfqz2uKMlxq5xAfAZ98SZjO8e3XnAbFnTJtAspqhWk7hrdSAs9/Y14ZWMiy7/MxMUzAOadYEw== dependencies: undici-types "~6.19.2" @@ -2133,11 +2111,6 @@ resolved "https://registry.npmmirror.com/@types/node/download/@types/node-14.18.0.tgz#98df2397f6936bfbff4f089e40e06fa5dd88d32a" integrity sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ== -"@types/prettier@^2.1.5": - version "2.7.3" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" - integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== - "@types/prismjs@^1.16.5": version "1.26.4" resolved "https://registry.npmmirror.com/@types/prismjs/-/prismjs-1.26.4.tgz#1a9e1074619ce1d7322669e5b46fbe823925103a" @@ -2189,12 +2162,10 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== -"@types/testing-library__jest-dom@^5.9.1": - version "5.14.9" - resolved "https://registry.npmmirror.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz#0fb1e6a0278d87b6737db55af5967570b67cb466" - integrity sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw== - dependencies: - "@types/jest" "*" +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== "@types/use-sync-external-store@^0.0.3": version "0.0.3" @@ -2206,20 +2177,6 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== -"@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.npmmirror.com/@types/yargs/download/@types/yargs-15.0.14.tgz?cache=0&sync_timestamp=1637271390567&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fyargs%2Fdownload%2F%40types%2Fyargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha1-Jtgh3biecEkhYLZtEKDrbfj2+wY= - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^16.0.0": - version "16.0.9" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz#ba506215e45f7707e6cbcaf386981155b7ab956e" - integrity sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA== - dependencies: - "@types/yargs-parser" "*" - "@types/yargs@^17.0.8": version "17.0.33" resolved "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" @@ -2379,35 +2336,32 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" -abab@^2.0.3, abab@^2.0.5: +abab@^2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" + acorn "^8.1.0" + acorn-walk "^8.0.2" acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn-walk@^8.0.2: + version "8.3.4" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" -acorn@^8.2.4, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: version "8.12.1" resolved "https://registry.npmmirror.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -2486,7 +2440,7 @@ ansi-regex@^4.1.0: resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== -ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -2657,6 +2611,11 @@ async@^3.2.0: resolved "https://registry.npmmirror.com/async/download/async-3.2.2.tgz#2eb7671034bb2194d45d30e31e24ec7e7f9670cd" integrity sha1-LrdnEDS7IZTUXTDjHiTsfn+WcM0= +async@^3.2.3: + version "3.2.6" + resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2701,16 +2660,15 @@ babel-core@^7.0.0-bridge.0: resolved "https://registry.npmmirror.com/babel-core/download/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha1-laSS3dkPm06aSh2hTrM1uHtjTs4= -babel-jest@^27.0.6, babel-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" - integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== dependencies: - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/transform" "^29.7.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^27.5.1" + babel-preset-jest "^29.6.3" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -2726,14 +2684,14 @@ babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" - integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" + "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" babel-plugin-polyfill-corejs2@^0.4.10: @@ -2781,12 +2739,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" -babel-preset-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" - integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== dependencies: - babel-plugin-jest-hoist "^27.5.1" + babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -2850,6 +2808,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.1, braces@^3.0.3: version "3.0.3" resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -2857,12 +2822,7 @@ braces@^3.0.1, braces@^3.0.3: dependencies: fill-range "^7.1.1" -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.23.1, browserslist@^4.23.3: +browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.23.3: version "4.23.3" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== @@ -2872,9 +2832,19 @@ browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.23.1, browserslist@^4 node-releases "^2.0.18" update-browserslist-db "^1.1.0" -bs-logger@0.x: +browserslist@^4.23.1: + version "4.24.0" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== + dependencies: + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +bs-logger@^0.2.6: version "0.2.6" - resolved "https://registry.npmmirror.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" @@ -2955,7 +2925,7 @@ caniuse-lite@^1.0.0: resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== -caniuse-lite@^1.0.30001646: +caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663: version "1.0.30001663" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz#1529a723505e429fdfd49532e9fc42273ba7fed7" integrity sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA== @@ -2982,7 +2952,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3299,11 +3269,6 @@ conventional-commits-parser@^5.0.0: meow "^12.0.1" split2 "^4.0.0" -convert-source-map@^1.4.0, convert-source-map@^1.6.0: - version "1.9.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -3360,6 +3325,19 @@ cosmiconfig@^9.0.0: js-yaml "^4.1.0" parse-json "^5.2.0" +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + cross-env@^7.0.2: version "7.0.3" resolved "https://registry.npmmirror.com/cross-env/download/cross-env-7.0.3.tgz?cache=0&sync_timestamp=1624607983846&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcross-env%2Fdownload%2Fcross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" @@ -3480,10 +3458,10 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== cssom@~0.3.6: version "0.3.8" @@ -3596,14 +3574,14 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" data-view-buffer@^1.0.1: version "1.0.1" @@ -3665,16 +3643,21 @@ debug@^4.3.2: dependencies: ms "2.1.2" -decimal.js@^10.2.1: +decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -dedent@0.7.0, dedent@^0.7.0: +dedent@0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmmirror.com/deep-is/download/deep-is-0.1.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fdeep-is%2Fdownload%2Fdeep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -3754,16 +3737,6 @@ detect-newline@^3.0.0: resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^25.2.6: - version "25.2.6" - resolved "https://registry.npmmirror.com/diff-sequences/download/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" - integrity sha1-X0Z8AO3TU1K3vKRteSfWDmh6dt0= - -diff-sequences@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" - integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== - diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -3790,11 +3763,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.9: version "0.5.16" resolved "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -3816,12 +3794,12 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== dependencies: - webidl-conversions "^5.0.0" + webidl-conversions "^7.0.0" domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1: version "4.3.1" @@ -3859,15 +3837,22 @@ editor@1.0.0: resolved "https://registry.npmmirror.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742" integrity sha512-SoRmbGStwNYHgKfjOrX2L0mUvp9bUVv0uPppZSOMAntEbcFtoC3MKF5b3T6HQPXKIV+QGY3xPO3JK5it5lVkuw== -electron-to-chromium@^1.5.4: +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.28, electron-to-chromium@^1.5.4: version "1.5.28" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz#aee074e202c6ee8a0030a9c2ef0b3fe9f967d576" integrity sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw== -emittery@^0.8.1: - version "0.8.1" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" - integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^10.3.0: version "10.4.0" @@ -3911,6 +3896,11 @@ entities@^3.0.1: resolved "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -3930,7 +3920,7 @@ errno@^0.1.1: error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" @@ -4406,17 +4396,7 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" - integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== - dependencies: - "@jest/types" "^27.5.1" - jest-get-type "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - -expect@^29.0.0: +expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.npmmirror.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== @@ -4495,7 +4475,7 @@ fast-glob@^3.0.3, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -4552,6 +4532,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -4660,10 +4647,10 @@ forever-agent@~0.6.1: resolved "https://registry.npmmirror.com/forever-agent/download/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -4851,7 +4838,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@7.2.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: +glob@7.2.3, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -5049,22 +5036,22 @@ homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== dependencies: - whatwg-encoding "^1.0.5" + whatwg-encoding "^2.0.0" html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/html-void-elements/download/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f" - integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A== +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== htmlparser2@^7.1.2: version "7.2.0" @@ -5076,12 +5063,12 @@ htmlparser2@^7.1.2: domutils "^2.8.0" entities "^3.0.1" -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== dependencies: - "@tootallnate/once" "1" + "@tootallnate/once" "2" agent-base "6" debug "4" @@ -5121,7 +5108,7 @@ http-signature@~1.3.6: jsprim "^2.0.2" sshpk "^1.14.1" -https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -5161,7 +5148,14 @@ i18next@^20.4.0: dependencies: "@babel/runtime" "^7.12.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -5335,7 +5329,7 @@ is-array-buffer@^3.0.4: is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-bigint@^1.0.1: @@ -5365,14 +5359,7 @@ is-ci@^3.0.0: dependencies: ci-info "^3.2.0" -is-core-module@^2.13.0: - version "2.15.0" - resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" - integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== - dependencies: - hasown "^2.0.2" - -is-core-module@^2.15.1, is-core-module@^2.2.0: +is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.2.0: version "2.15.1" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== @@ -5574,9 +5561,9 @@ is-typed-array@^1.1.13: dependencies: which-typed-array "^1.1.14" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@~1.0.0: version "1.0.0" - resolved "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== is-unicode-supported@^0.1.0: @@ -5643,7 +5630,7 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: +istanbul-lib-instrument@^5.0.4: version "5.2.1" resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== @@ -5654,6 +5641,17 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + istanbul-lib-report@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" @@ -5680,108 +5678,96 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" - integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== dependencies: - "@jest/types" "^27.5.1" execa "^5.0.0" - throat "^6.0.1" + jest-util "^29.7.0" + p-limit "^3.1.0" -jest-circus@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" - integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== dependencies: - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" - expect "^27.5.1" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" - throat "^6.0.1" -jest-cli@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" - integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== dependencies: - "@jest/core" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" chalk "^4.0.0" + create-jest "^29.7.0" exit "^0.1.2" - graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - prompts "^2.0.1" - yargs "^16.2.0" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" -jest-config@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" - integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== dependencies: - "@babel/core" "^7.8.0" - "@jest/test-sequencer" "^27.5.1" - "@jest/types" "^27.5.1" - babel-jest "^27.5.1" + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" - glob "^7.1.1" + glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-get-type "^27.5.1" - jest-jasmine2 "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runner "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^27.5.1" + pretty-format "^29.7.0" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^25.2.1: - version "25.5.0" - resolved "https://registry.npmmirror.com/jest-diff/download/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" - integrity sha1-HdJu1k+WZnwGjO8Ca2d9+gGvz6k= - dependencies: - chalk "^3.0.0" - diff-sequences "^25.2.6" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" - -jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== - dependencies: - chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - jest-diff@^29.7.0: version "29.7.0" resolved "https://registry.npmmirror.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" @@ -5792,124 +5778,81 @@ jest-diff@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-docblock@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" - integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== dependencies: detect-newline "^3.0.0" -jest-each@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" - integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" chalk "^4.0.0" - jest-get-type "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" -jest-environment-jsdom@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" - integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== +jest-environment-jsdom@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - jsdom "^16.6.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jsdom "^20.0.0" -jest-environment-node@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" - integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - -jest-get-type@^25.2.6: - version "25.2.6" - resolved "https://registry.npmmirror.com/jest-get-type/download/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" - integrity sha1-Cwoy+riQi0TVCL6BaBSH26u42Hc= - -jest-get-type@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" - integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-mock "^29.7.0" + jest-util "^29.7.0" jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.npmmirror.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== -jest-haste-map@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" - integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== dependencies: - "@jest/types" "^27.5.1" - "@types/graceful-fs" "^4.1.2" + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^27.5.1" - jest-serializer "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" micromatch "^4.0.4" - walker "^1.0.7" + walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-jasmine2@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" - integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^27.5.1" - is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - throat "^6.0.1" - -jest-leak-detector@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" - integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== - dependencies: - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-matcher-utils@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== dependencies: - chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" jest-matcher-utils@^29.7.0: version "29.7.0" @@ -5921,21 +5864,6 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-message-util@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" - integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.5.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^27.5.1" - slash "^3.0.0" - stack-utils "^2.0.3" - jest-message-util@^29.7.0: version "29.7.0" resolved "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" @@ -5951,153 +5879,130 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" - integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" "@types/node" "*" + jest-util "^29.7.0" jest-pnp-resolver@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" - integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== -jest-resolve-dependencies@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" - integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== dependencies: - "@jest/types" "^27.5.1" - jest-regex-util "^27.5.1" - jest-snapshot "^27.5.1" + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" -jest-resolve@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" - integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== dependencies: - "@jest/types" "^27.5.1" chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" + jest-haste-map "^29.7.0" jest-pnp-resolver "^1.2.2" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-util "^29.7.0" + jest-validate "^29.7.0" resolve "^1.20.0" - resolve.exports "^1.1.0" + resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" - integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== - dependencies: - "@jest/console" "^27.5.1" - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - emittery "^0.8.1" + emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-haste-map "^27.5.1" - jest-leak-detector "^27.5.1" - jest-message-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runtime "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - source-map-support "^0.5.6" - throat "^6.0.1" - -jest-runtime@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" - integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/globals" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - execa "^5.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-serializer@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" - integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.9" - -jest-snapshot@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" - integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== dependencies: - "@babel/core" "^7.7.2" + "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.0.0" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.1.5" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^27.5.1" + expect "^29.7.0" graceful-fs "^4.2.9" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - jest-haste-map "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-util "^27.5.1" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" natural-compare "^1.4.0" - pretty-format "^27.5.1" - semver "^7.3.2" - -jest-util@^27.0.0, jest-util@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" - integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== - dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" + pretty-format "^29.7.0" + semver "^7.5.3" -jest-util@^29.7.0: +jest-util@^29.0.0, jest-util@^29.7.0: version "29.7.0" resolved "https://registry.npmmirror.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -6109,48 +6014,51 @@ jest-util@^29.7.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" - integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^27.5.1" + jest-get-type "^29.6.3" leven "^3.1.0" - pretty-format "^27.5.1" + pretty-format "^29.7.0" -jest-watcher@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" - integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== dependencies: - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^27.5.1" + emittery "^0.13.1" + jest-util "^29.7.0" string-length "^4.0.1" -jest-worker@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== dependencies: "@types/node" "*" + jest-util "^29.7.0" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^27.0.6: - version "27.5.1" - resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" - integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== dependencies: - "@jest/core" "^27.5.1" + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" import-local "^3.0.2" - jest-cli "^27.5.1" + jest-cli "^29.7.0" jiti@^1.19.1: version "1.21.6" @@ -6191,38 +6099,37 @@ jsbn@~0.1.0: resolved "https://registry.npmmirror.com/jsbn/download/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.6.0: - version "16.7.0" - resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.1" + saxes "^6.0.0" symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" jsesc@^2.5.1: version "2.5.2" @@ -6236,7 +6143,7 @@ jsesc@~0.5.0: json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: @@ -6274,11 +6181,6 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@2.x, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - json5@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -6286,6 +6188,11 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -6387,7 +6294,7 @@ lilconfig@~3.1.2: lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== lint-staged@^15.2.10: @@ -6498,7 +6405,7 @@ lodash.map@^4.5.1: resolved "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q== -lodash.memoize@4.x, lodash.memoize@^4.1.2: +lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -6548,9 +6455,9 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@4.17.21, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: +lodash@4.17.21, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" - resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@^4.0.0, log-symbols@^4.1.0: @@ -6636,9 +6543,9 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@1.x: +make-error@^1.3.6: version "1.3.6" - resolved "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.12: @@ -6762,6 +6669,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimist@1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" @@ -6798,7 +6712,7 @@ ms@2.1.2: ms@^2.1.1, ms@^2.1.3: version "2.1.3" - resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== mute-stream@0.0.7: @@ -6906,7 +6820,7 @@ nth-check@^2.0.1, nth-check@^2.1.1: dependencies: boolbase "^1.0.0" -nwsapi@^2.2.0: +nwsapi@^2.2.2: version "2.2.12" resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz#fb6af5c0ec35b27b4581eb3bbad34ec9e5c696f8" integrity sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w== @@ -7096,7 +7010,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.npmmirror.com/p-limit/download/p-limit-3.1.0.tgz?cache=0&sync_timestamp=1628813055527&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fp-limit%2Fdownload%2Fp-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha1-4drMvnjQ0TiMoYxk/qOOPlfjcGs= @@ -7197,10 +7111,12 @@ parse-passwd@^1.0.0: resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.0.0, parse5@^7.1.1: + version "7.1.2" + resolved "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" patch-package@^8.0.0: version "8.0.0" @@ -7250,7 +7166,7 @@ path-key@^4.0.0: path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: @@ -7638,17 +7554,7 @@ pretty-bytes@^5.6.0: resolved "https://registry.npmmirror.com/pretty-bytes/download/pretty-bytes-5.6.0.tgz?cache=0&sync_timestamp=1613916423464&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fpretty-bytes%2Fdownload%2Fpretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha1-NWJW9kOAR3PIL2RyP+eMksYr6us= -pretty-format@^25.2.1, pretty-format@^25.5.0: - version "25.5.0" - resolved "https://registry.npmmirror.com/pretty-format/download/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" - integrity sha1-eHPB13T2gsNLjUi2dDor8qxVeRo= - dependencies: - "@jest/types" "^25.5.0" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - -pretty-format@^27.0.2, pretty-format@^27.5.1: +pretty-format@^27.0.2: version "27.5.1" resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -7737,6 +7643,11 @@ punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + qs@^6.4.0: version "6.10.2" resolved "https://registry.npmmirror.com/qs/download/qs-6.10.2.tgz#c1431bea37fc5b24c5bdbafa20f16bdf2a4b9ffe" @@ -7776,11 +7687,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -react-is@^16.12.0: - version "16.13.1" - resolved "https://registry.npmmirror.com/react-is/download/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha1-eJcppNw23imZ3BVt1sHZwYzqVqQ= - react-is@^17.0.1: version "17.0.2" resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" @@ -7890,8 +7796,8 @@ require-from-string@^2.0.2: requires-port@^1.0.0: version "1.0.0" - resolved "https://registry.npmmirror.com/requires-port/download/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== resolve-cwd@^3.0.0: version "3.0.0" @@ -7918,10 +7824,10 @@ resolve-from@^5.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve.exports@^1.1.0: - version "1.1.1" - resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" - integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.4: version "1.22.8" @@ -8162,15 +8068,15 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== dependencies: xmlchars "^2.2.0" @@ -8186,11 +8092,6 @@ secure-compare@3.0.1: resolved "https://registry.npmmirror.com/secure-compare/download/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" integrity sha1-8aAymzCLIh+uN7mXTz1XjQypmeM= -semver@7.x, semver@^7.3.2, semver@^7.3.6, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: - version "7.6.3" - resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - semver@^5.6.0: version "5.7.1" resolved "https://registry.npmmirror.com/semver/download/semver-5.7.1.tgz?cache=0&sync_timestamp=1616463550093&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -8201,6 +8102,11 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.3.6, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + serialize-javascript@^6.0.1: version "6.0.2" resolved "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" @@ -8264,7 +8170,7 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" -signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -8359,7 +8265,15 @@ source-map-js@^1.2.0: resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== -source-map-support@^0.5.6, source-map-support@~0.5.20: +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.npmmirror.com/source-map-support/download/source-map-support-0.5.21.tgz?cache=0&sync_timestamp=1637320256759&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fsource-map-support%2Fdownload%2Fsource-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -8372,9 +8286,9 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3, source-map@^0.7.4: +source-map@^0.7.4: version "0.7.4" - resolved "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== sourcemap-codec@^1.4.4: @@ -8596,7 +8510,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -8610,17 +8524,9 @@ supports-color@^8.0.0, supports-color@^8.1.0, supports-color@^8.1.1: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== svgo@^2.7.0: @@ -8662,14 +8568,6 @@ term-size@^2.1.0: resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - terser@^5.17.4: version "5.31.5" resolved "https://registry.npmmirror.com/terser/-/terser-5.31.5.tgz#e48b7c65f32d2808e7dad803e4586a0bc3829b87" @@ -8699,11 +8597,6 @@ text-table@^0.2.0: resolved "https://registry.npmmirror.com/text-table/download/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -throat@^6.0.1: - version "6.0.2" - resolved "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz#51a3fbb5e11ae72e2cf74861ed5c8020f89f29fe" - integrity sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ== - throttleit@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/throttleit/download/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -8755,7 +8648,7 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tough-cookie@^4.0.0: +tough-cookie@^4.1.2: version "4.1.4" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== @@ -8773,10 +8666,10 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== dependencies: punycode "^2.1.1" @@ -8785,19 +8678,20 @@ tree-kill@^1.2.2: resolved "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -ts-jest@^27.0.4: - version "27.1.5" - resolved "https://registry.npmmirror.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" - integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^27.0.0" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - semver "7.x" - yargs-parser "20.x" + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" tsconfig-paths@^3.15.0: version "3.15.0" @@ -8966,17 +8860,10 @@ typed-array-length@^1.0.6: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@4.3.2: - version "4.3.2" - resolved "https://registry.npmmirror.com/typescript/download/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" - integrity sha1-OZqxiqxFgC1vJJjeUFT8u+cWqAU= +typescript@5.6.2: + version "5.6.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== typescript@^4.6.3: version "4.9.5" @@ -9111,14 +8998,14 @@ uuid@^8.3.2: resolved "https://registry.npmmirror.com/uuid/download/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha1-gNW1ztJxu5r2xEXyGhoExgbO++I= -v8-to-istanbul@^8.1.0: - version "8.1.1" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" - integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: + "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" + convert-source-map "^2.0.0" verror@1.10.0: version "1.10.0" @@ -9142,21 +9029,14 @@ vue-eslint-parser@^9.4.3: lodash "^4.17.21" semver "^7.3.6" -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== dependencies: - xml-name-validator "^3.0.0" + xml-name-validator "^4.0.0" -walker@^1.0.7: +walker@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -9170,36 +9050,30 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== dependencies: - iconv-lite "0.4.24" + iconv-lite "0.6.3" -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" + tr46 "^3.0.0" + webidl-conversions "^7.0.0" which-boxed-primitive@^1.0.2: version "1.0.2" @@ -9279,25 +9153,18 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" + signal-exit "^3.0.7" -ws@^7.4.6: - version "7.5.10" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" - integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +ws@^8.11.0: + version "8.18.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== xml-name-validator@^4.0.0: version "4.0.0" @@ -9341,7 +9208,7 @@ yaml@^2.2.2, yaml@~2.5.0: resolved "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== -yargs-parser@20.x, yargs-parser@^20.2.2: +yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -9364,7 +9231,7 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0, yargs@^17.5.1: +yargs@^17.0.0, yargs@^17.3.1, yargs@^17.5.1: version "17.7.2" resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==