From 91b72e671f99a395a9ee0401c88c58c6b5f04ca3 Mon Sep 17 00:00:00 2001 From: shixiaohua Date: Thu, 29 Feb 2024 17:00:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Note/Hlexical/index.jsx | 6 +- src/pages/Note/Hlexical/index.less | 1541 +++++++++-------- .../plugins/ContextMenuPlugin/index.jsx | 221 +++ .../plugins/ContextMenuPlugin/index.less | 0 .../Note/Hlexical/plugins/SaveFilePlugin.js | 1 + .../plugins/TableOfContentsPlugin/index.jsx | 206 +++ .../{index.css => index.less} | 16 +- .../plugins/TableOfContentsPlugin/index.tsx | 197 --- .../Note/Hlexical/plugins/ToobarPlugin.less | 16 + .../Note/Hlexical/plugins/ToolbarPlugin.js | 10 +- src/pages/Note/index.jsx | 14 +- src/redux/tableBarItem_reducer.js | 10 +- 12 files changed, 1273 insertions(+), 965 deletions(-) create mode 100644 src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx create mode 100644 src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.less create mode 100644 src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.jsx rename src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/{index.css => index.less} (87%) delete mode 100644 src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx create mode 100644 src/pages/Note/Hlexical/plugins/ToobarPlugin.less diff --git a/src/pages/Note/Hlexical/index.jsx b/src/pages/Note/Hlexical/index.jsx index 56da82e..101021a 100644 --- a/src/pages/Note/Hlexical/index.jsx +++ b/src/pages/Note/Hlexical/index.jsx @@ -31,6 +31,8 @@ import InlineImagePlugin from "./plugins/InlineImagePlugin"; import {TablePlugin} from "@lexical/react/LexicalTablePlugin"; import TableCellActionMenuPlugin from './plugins/TableActionMenuPlugin'; import ExcalidrawPlugin from "./plugins/ExcalidrawPlugin"; +import TableOfContentsPlugin from "./plugins/TableOfContentsPlugin"; +import ContextMenuPlugin from "./plugins/ContextMenuPlugin" function Placeholder() { return
Enter some rich text...
; } @@ -93,7 +95,9 @@ export default function Hlexical(props) { {/*页分割线*/} {/*目录加载*/} - + + {/*右键菜单*/} + {/* 画图 */} diff --git a/src/pages/Note/Hlexical/index.less b/src/pages/Note/Hlexical/index.less index a4bd306..06c9dc1 100644 --- a/src/pages/Note/Hlexical/index.less +++ b/src/pages/Note/Hlexical/index.less @@ -1,754 +1,777 @@ body { - margin: 0; - background: #eee; - font-family: system-ui, -apple-system, BlinkMacSystemFont, ".SFNSText-Regular", - sans-serif; - font-weight: 500; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - - .other h2 { - font-size: 18px; - color: #444; - margin-bottom: 7px; - } - - .other a { - color: #777; - text-decoration: underline; - font-size: 14px; - } - - .other ul { - padding: 0; - margin: 0; - list-style-type: none; - } + margin: 0; + background: #eee; + font-family: system-ui, -apple-system, BlinkMacSystemFont, ".SFNSText-Regular", + sans-serif; + font-weight: 500; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} - h1 { - font-size: 24px; - color: #333; - } - - .ltr { - text-align: left; - } - - .rtl { - text-align: right; - } - - .editor-container { - // margin: 20px auto 20px auto; - border-radius: 2px; - // max-width: 600px; - color: #000; - position: relative; - line-height: 20px; - font-weight: 400; - text-align: left; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - height: 100%; - } - .toolbar{ - height:4.9%; - margin-bottom:0.1% - } - .editor-inner { - background: #fff; - position: relative; - height:95%; - overflow: hidden auto; - } - - .editor-input { - min-height: 150px; - resize: none; - font-size: 15px; - caret-color: rgb(5, 5, 5); - position: relative; - tab-size: 1; - outline: 0; - padding: 15px 10px; - caret-color: #444; - } - - .editor-placeholder { - color: #999; - overflow: hidden; - position: absolute; - text-overflow: ellipsis; - top: 15px; - left: 10px; - font-size: 15px; - user-select: none; - display: inline-block; - pointer-events: none; - } - - .editor-text-bold { - font-weight: bold; - } - - .editor-text-italic { - font-style: italic; - } - - .editor-text-underline { - text-decoration: underline; - } - - .editor-text-strikethrough { - text-decoration: line-through; - } - - .editor-text-underlineStrikethrough { - text-decoration: underline line-through; - } - - .editor-text-code { - background-color: rgb(240, 242, 245); - padding: 1px 0.25rem; - font-family: Menlo, Consolas, Monaco, monospace; - font-size: 94%; - } - - .editor-link { - color: rgb(33, 111, 219); - text-decoration: none; - } - - .tree-view-output { - display: block; - background: #222; - color: #fff; - padding: 5px; - font-size: 12px; - white-space: pre-wrap; - margin: 1px auto 10px auto; - max-height: 250px; - position: relative; - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - overflow: auto; - line-height: 14px; - } - - .editor-code { - background-color: rgb(240, 242, 245); - font-family: Menlo, Consolas, Monaco, monospace; - display: block; - padding: 8px 8px 8px 52px; - line-height: 1.53; - font-size: 13px; - margin: 0; - margin-top: 8px; - margin-bottom: 8px; - tab-size: 2; - white-space: pre; - overflow: auto; - position: relative; - } - - .editor-code:before { - content: attr(data-gutter); - position: absolute; - background-color: #eee; - left: 0; - top: 0; - border-right: 1px solid #ccc; - padding: 8px; - color: #777; - white-space: pre-wrap; - text-align: right; - min-width: 25px; - } - .editor-code:after { - content: attr(data-highlight-language); - top: 0; - right: 3px; - padding: 3px; - font-size: 10px; - text-transform: uppercase; - position: absolute; - color: rgba(0, 0, 0, 0.5); - } - - .editor-tokenComment { - color: slategray; - } - - .editor-tokenPunctuation { - color: #999; - } - - .editor-tokenProperty { - color: #905; - } - - .editor-tokenSelector { - color: #690; - } - - .editor-tokenOperator { - color: #9a6e3a; - } - - .editor-tokenAttr { - color: #07a; - } - - .editor-tokenVariable { - color: #e90; - } - - .editor-tokenFunction { - color: #dd4a68; - } - - .editor-paragraph { - margin: 0; - margin-bottom: 8px; - position: relative; - } - - .editor-paragraph:last-child { - margin-bottom: 0; - } - - .editor-heading-h1 { - font-size: 24px; - color: rgb(5, 5, 5); - font-weight: 400; - margin: 0; - margin-bottom: 12px; - padding: 0; - } - - .editor-heading-h2 { - font-size: 15px; - color: rgb(101, 103, 107); - font-weight: 700; - margin: 0; - margin-top: 10px; - padding: 0; - text-transform: uppercase; - } - - .editor-quote { - margin: 0; - margin-left: 20px; - font-size: 15px; - color: rgb(101, 103, 107); - border-left-color: rgb(206, 208, 212); - border-left-width: 4px; - border-left-style: solid; - padding-left: 16px; - } - - .editor-list-ol { - padding: 0; - margin: 0; - margin-left: 16px; - } - - .editor-list-ul { - padding: 0; - margin: 0; - margin-left: 16px; - } - - .editor-listitem { - margin: 8px 32px 8px 32px; - } - - .editor-nested-listitem { - list-style-type: none; - } - - pre::-webkit-scrollbar { - background: transparent; - width: 10px; - } - - pre::-webkit-scrollbar-thumb { - background: #999; - } - - .debug-timetravel-panel { - overflow: hidden; - padding: 0 0 10px 0; - margin: auto; - display: flex; - } - - .debug-timetravel-panel-slider { - padding: 0; - flex: 8; - } - - .debug-timetravel-panel-button { - padding: 0; - border: 0; - background: none; - flex: 1; - color: #fff; - font-size: 12px; - } - - .debug-timetravel-panel-button:hover { - text-decoration: underline; - } - - .debug-timetravel-button { - border: 0; - padding: 0; - font-size: 12px; - top: 10px; - right: 15px; - position: absolute; - background: none; - color: #fff; - } - - .debug-timetravel-button:hover { - text-decoration: underline; - } - - .emoji { - color: transparent; - background-size: 16px 16px; - background-position: center; - background-repeat: no-repeat; - vertical-align: middle; - margin: 0 -1px; - } - - .emoji-inner { - padding: 0 0.15em; - } - - .emoji-inner::selection { - color: transparent; - background-color: rgba(150, 150, 150, 0.4); - } - - .emoji-inner::moz-selection { - color: transparent; - background-color: rgba(150, 150, 150, 0.4); - } - - .emoji.happysmile { - background-image: url(./images/emoji/1F642.png); - } - - .toolbar { - display: flex; - margin-bottom: 1px; - background: #fff; - padding: 4px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - vertical-align: middle; - } - - .toolbar button.toolbar-item { - border: 0; - display: flex; - background: none; - border-radius: 10px; - padding: 8px; - cursor: pointer; - vertical-align: middle; - } - - .toolbar button.toolbar-item:disabled { - cursor: not-allowed; - } - - .toolbar button.toolbar-item.spaced { - margin-right: 2px; - } - - .toolbar button.toolbar-item i.format { - background-size: contain; - display: inline-block; - height: 18px; - width: 18px; - margin-top: 2px; - vertical-align: -0.25em; - display: flex; - opacity: 0.6; - } - - .toolbar button.toolbar-item:disabled i.format { - opacity: 0.2; - } - - .toolbar button.toolbar-item.active { - background-color: rgba(223, 232, 250, 0.3); - } - - .toolbar button.toolbar-item.active i { - opacity: 1; - } - - .toolbar .toolbar-item:hover:not([disabled]) { - background-color: #eee; - } - - .toolbar .divider { - width: 1px; - background-color: #eee; - margin: 0 4px; - } - - .toolbar select.toolbar-item { - border: 0; - display: flex; - background: none; - border-radius: 10px; - padding: 8px; - vertical-align: middle; - -webkit-appearance: none; - -moz-appearance: none; - width: 70px; - font-size: 14px; - color: #777; - text-overflow: ellipsis; - } - - .toolbar select.code-language { - text-transform: capitalize; - width: 130px; - } - - .toolbar .toolbar-item .text { - display: flex; - line-height: 20px; - vertical-align: middle; - font-size: 14px; - color: #777; - text-overflow: ellipsis; - width: 70px; - overflow: hidden; - height: 20px; - text-align: left; - } - - .toolbar .toolbar-item .icon { - display: flex; - width: 20px; - height: 20px; - user-select: none; - margin-right: 8px; - line-height: 16px; - background-size: contain; - } - - .toolbar i.chevron-down { - margin-top: 3px; - width: 16px; - height: 16px; - display: flex; - user-select: none; - } - - .toolbar i.chevron-down.inside { - width: 16px; - height: 16px; - display: flex; - margin-left: -25px; - margin-top: 11px; - margin-right: 10px; - pointer-events: none; - } - - i.chevron-down { - background-color: transparent; - background-size: contain; - display: inline-block; - height: 8px; - width: 8px; - background-image: url(images/icons/chevron-down.svg); - } - - #block-controls button:hover { - background-color: #efefef; - } - - #block-controls button:focus-visible { - border-color: blue; - } - - #block-controls span.block-type { - background-size: contain; - display: block; - width: 18px; - height: 18px; - margin: 2px; - } - - #block-controls span.block-type.paragraph { - background-image: url(images/icons/text-paragraph.svg); - } - - #block-controls span.block-type.h1 { - background-image: url(images/icons/type-h1.svg); - } - - #block-controls span.block-type.h2 { - background-image: url(images/icons/type-h2.svg); - } - - #block-controls span.block-type.quote { - background-image: url(images/icons/chat-square-quote.svg); - } - - #block-controls span.block-type.ul { - background-image: url(images/icons/list-ul.svg); - } - - #block-controls span.block-type.ol { - background-image: url(images/icons/list-ol.svg); - } - - #block-controls span.block-type.code { - background-image: url(images/icons/code.svg); - } - - .dropdown { - z-index: 5; - display: block; - position: absolute; - box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1), - inset 0 0 0 1px rgba(255, 255, 255, 0.5); - border-radius: 8px; - min-width: 100px; - min-height: 40px; - background-color: #fff; - } - - .dropdown .item { - margin: 0 8px 0 8px; - padding: 8px; - color: #050505; - cursor: pointer; - line-height: 16px; - font-size: 15px; - display: flex; - align-content: center; - flex-direction: row; - flex-shrink: 0; - justify-content: space-between; - background-color: #fff; - border-radius: 8px; - border: 0; - min-width: 268px; - } - - .dropdown .item .active { - display: flex; - width: 20px; - height: 20px; - background-size: contain; - } - - .dropdown .item:first-child { - margin-top: 8px; - } - - .dropdown .item:last-child { - margin-bottom: 8px; - } - - .dropdown .item:hover { - background-color: #eee; - } - - .dropdown .item .text { - display: flex; - line-height: 20px; - flex-grow: 1; - width: 200px; - } - - .dropdown .item .icon { - display: flex; - width: 20px; - height: 20px; - user-select: none; - margin-right: 12px; - line-height: 16px; - background-size: contain; - } - - .link-editor { - position: absolute; - z-index: 100; - top: -10000px; - left: -10000px; - margin-top: -6px; - max-width: 300px; - width: 100%; - opacity: 0; - background-color: #fff; - box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3); - border-radius: 8px; - transition: opacity 0.5s; - } - - .link-editor .link-input { - display: block; - width: calc(100% - 24px); - box-sizing: border-box; - margin: 8px 12px; - padding: 8px 12px; - border-radius: 15px; - background-color: #eee; - font-size: 15px; - color: rgb(5, 5, 5); - border: 0; - outline: 0; - position: relative; - font-family: inherit; - } - - .link-editor div.link-edit { - background-image: url(images/icons/pencil-fill.svg); - background-size: 16px; - background-position: center; - background-repeat: no-repeat; - width: 35px; - vertical-align: -0.25em; - position: absolute; - right: 0; - top: 0; - bottom: 0; - cursor: pointer; - } - - .link-editor .link-input a { - color: rgb(33, 111, 219); - text-decoration: none; - display: block; - white-space: nowrap; - overflow: hidden; - margin-right: 30px; - text-overflow: ellipsis; - } - - .link-editor .link-input a:hover { - text-decoration: underline; - } - - .link-editor .button { - width: 20px; - height: 20px; - display: inline-block; - padding: 6px; - border-radius: 8px; - cursor: pointer; - margin: 0 2px; - } - - .link-editor .button.hovered { - width: 20px; - height: 20px; - display: inline-block; - background-color: #eee; - } - - .link-editor .button i, - .actions i { - background-size: contain; - display: inline-block; - height: 20px; - width: 20px; - vertical-align: -0.25em; - } - - i.undo { - background-image: url(images/icons/arrow-counterclockwise.svg); - } - - i.redo { - background-image: url(images/icons/arrow-clockwise.svg); - } - - .icon.paragraph { - background-image: url(images/icons/text-paragraph.svg); - } - - .icon.large-heading, - .icon.h1 { - background-image: url(images/icons/type-h1.svg); - } - - .icon.small-heading, - .icon.h2 { - background-image: url(images/icons/type-h2.svg); - } - - .icon.bullet-list, - .icon.ul { - background-image: url(images/icons/list-ul.svg); - } - - .icon.numbered-list, - .icon.ol { - background-image: url(images/icons/list-ol.svg); - } - - .icon.quote { - background-image: url(images/icons/chat-square-quote.svg); - } - - .icon.code { - background-image: url(images/icons/code.svg); - } - - i.bold { - background-image: url(images/icons/type-bold.svg); - } - - i.italic { - background-image: url(images/icons/type-italic.svg); - } - - i.underline { - background-image: url(images/icons/type-underline.svg); - } - - i.strikethrough { - background-image: url(images/icons/type-strikethrough.svg); - } - - i.code { - background-image: url(images/icons/code.svg); - } - - i.link { - background-image: url(images/icons/link.svg); - } - - i.left-align { - background-image: url(images/icons/text-left.svg); - } - - i.center-align { - background-image: url(images/icons/text-center.svg); - } - - i.right-align { - background-image: url(images/icons/text-right.svg); - } - - i.justify-align { - background-image: url(images/icons/justify.svg); - } +.other h2 { + font-size: 18px; + color: #444; + margin-bottom: 7px; +} + +.other a { + color: #777; + text-decoration: underline; + font-size: 14px; +} + +.other ul { + padding: 0; + margin: 0; + list-style-type: none; +} + +h1 { + font-size: 24px; + color: #333; +} + +.ltr { + text-align: left; +} + +.rtl { + text-align: right; +} + +.editor-container { + // margin: 20px auto 20px auto; + border-radius: 2px; + // max-width: 600px; + color: #000; + position: relative; + line-height: 20px; + font-weight: 400; + text-align: left; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + height: 100%; +} + +.toolbar { + height: 4.9%; + margin-bottom: 0.1% +} + +.editor-inner { + background: #fff; + position: relative; + height: 95%; + overflow: hidden auto; +} + +.editor-input { + min-height: 150px; + resize: none; + font-size: 15px; + caret-color: rgb(5, 5, 5); + position: relative; + tab-size: 1; + outline: 0; + padding: 15px 10px; + caret-color: #444; +} + +.editor-placeholder { + color: #999; + overflow: hidden; + position: absolute; + text-overflow: ellipsis; + top: 15px; + left: 10px; + font-size: 15px; + user-select: none; + display: inline-block; + pointer-events: none; +} + +.editor-text-bold { + font-weight: bold; +} + +.editor-text-italic { + font-style: italic; +} + +.editor-text-underline { + text-decoration: underline; +} + +.editor-text-strikethrough { + text-decoration: line-through; +} + +.editor-text-underlineStrikethrough { + text-decoration: underline line-through; +} + +.editor-text-code { + background-color: rgb(240, 242, 245); + padding: 1px 0.25rem; + font-family: Menlo, Consolas, Monaco, monospace; + font-size: 94%; +} + +.editor-link { + color: rgb(33, 111, 219); + text-decoration: none; +} + +.tree-view-output { + display: block; + background: #222; + color: #fff; + padding: 5px; + font-size: 12px; + white-space: pre-wrap; + margin: 1px auto 10px auto; + max-height: 250px; + position: relative; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + overflow: auto; + line-height: 14px; +} + +.editor-code { + background-color: rgb(240, 242, 245); + font-family: Menlo, Consolas, Monaco, monospace; + display: block; + padding: 8px 8px 8px 52px; + line-height: 1.53; + font-size: 13px; + margin: 0; + margin-top: 8px; + margin-bottom: 8px; + tab-size: 2; + white-space: pre; + overflow: auto; + position: relative; +} + +.editor-code:before { + content: attr(data-gutter); + position: absolute; + background-color: #eee; + left: 0; + top: 0; + border-right: 1px solid #ccc; + padding: 8px; + color: #777; + white-space: pre-wrap; + text-align: right; + min-width: 25px; +} + +.editor-code:after { + content: attr(data-highlight-language); + top: 0; + right: 3px; + padding: 3px; + font-size: 10px; + text-transform: uppercase; + position: absolute; + color: rgba(0, 0, 0, 0.5); +} + +.editor-tokenComment { + color: slategray; +} + +.editor-tokenPunctuation { + color: #999; +} + +.editor-tokenProperty { + color: #905; +} + +.editor-tokenSelector { + color: #690; +} + +.editor-tokenOperator { + color: #9a6e3a; +} + +.editor-tokenAttr { + color: #07a; +} + +.editor-tokenVariable { + color: #e90; +} + +.editor-tokenFunction { + color: #dd4a68; +} + +.editor-paragraph { + margin: 0; + margin-bottom: 8px; + position: relative; +} + +.editor-paragraph:last-child { + margin-bottom: 0; +} + +.editor-heading-h1 { + font-size: 24px; + color: rgb(5, 5, 5); + font-weight: 400; + margin: 0; + margin-bottom: 12px; + padding: 0; +} + +.editor-heading-h2 { + font-size: 15px; + color: rgb(101, 103, 107); + font-weight: 700; + margin: 0; + margin-top: 10px; + padding: 0; + text-transform: uppercase; +} + +.editor-quote { + margin: 0; + margin-left: 20px; + font-size: 15px; + color: rgb(101, 103, 107); + border-left-color: rgb(206, 208, 212); + border-left-width: 4px; + border-left-style: solid; + padding-left: 16px; +} + +.editor-list-ol { + padding: 0; + margin: 0; + margin-left: 16px; +} + +.editor-list-ul { + padding: 0; + margin: 0; + margin-left: 16px; +} + +.editor-listitem { + margin: 8px 32px 8px 32px; +} + +.editor-nested-listitem { + list-style-type: none; +} + +pre::-webkit-scrollbar { + background: transparent; + width: 10px; +} + +pre::-webkit-scrollbar-thumb { + background: #999; +} + +.debug-timetravel-panel { + overflow: hidden; + padding: 0 0 10px 0; + margin: auto; + display: flex; +} + +.debug-timetravel-panel-slider { + padding: 0; + flex: 8; +} + +.debug-timetravel-panel-button { + padding: 0; + border: 0; + background: none; + flex: 1; + color: #fff; + font-size: 12px; +} + +.debug-timetravel-panel-button:hover { + text-decoration: underline; +} + +.debug-timetravel-button { + border: 0; + padding: 0; + font-size: 12px; + top: 10px; + right: 15px; + position: absolute; + background: none; + color: #fff; +} + +.debug-timetravel-button:hover { + text-decoration: underline; +} + +.emoji { + color: transparent; + background-size: 16px 16px; + background-position: center; + background-repeat: no-repeat; + vertical-align: middle; + margin: 0 -1px; +} + +.emoji-inner { + padding: 0 0.15em; +} + +.emoji-inner::selection { + color: transparent; + background-color: rgba(150, 150, 150, 0.4); +} + +.emoji-inner::moz-selection { + color: transparent; + background-color: rgba(150, 150, 150, 0.4); +} + +.emoji.happysmile { + background-image: url(./images/emoji/1F642.png); +} + +.toolbar { + display: flex; + margin-bottom: 1px; + background: #fff; + padding: 4px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + vertical-align: middle; +} + +.toolbar button.toolbar-item { + border: 0; + display: flex; + background: none; + border-radius: 10px; + padding: 8px; + cursor: pointer; + vertical-align: middle; +} + +.toolbar button.toolbar-item:disabled { + cursor: not-allowed; +} + +.toolbar button.toolbar-item.spaced { + margin-right: 2px; +} + +.toolbar button.toolbar-item i.format { + background-size: contain; + display: inline-block; + height: 18px; + width: 18px; + margin-top: 2px; + vertical-align: -0.25em; + display: flex; + opacity: 0.6; +} + +.toolbar button.toolbar-item:disabled i.format { + opacity: 0.2; +} + +.toolbar button.toolbar-item.active { + background-color: rgba(223, 232, 250, 0.3); +} + +.toolbar button.toolbar-item.active i { + opacity: 1; +} + +.toolbar .toolbar-item:hover:not([disabled]) { + background-color: #eee; +} + +.toolbar .divider { + width: 1px; + background-color: #eee; + margin: 0 4px; +} + +.toolbar select.toolbar-item { + border: 0; + display: flex; + background: none; + border-radius: 10px; + padding: 8px; + vertical-align: middle; + -webkit-appearance: none; + -moz-appearance: none; + width: 70px; + font-size: 14px; + color: #777; + text-overflow: ellipsis; +} + +.toolbar select.code-language { + text-transform: capitalize; + width: 130px; +} + +.toolbar .toolbar-item .text { + display: flex; + line-height: 20px; + vertical-align: middle; + font-size: 14px; + color: #777; + text-overflow: ellipsis; + width: 70px; + overflow: hidden; + height: 20px; + text-align: left; +} + +.toolbar .toolbar-item .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 8px; + line-height: 16px; + background-size: contain; +} + +.toolbar i.chevron-down { + margin-top: 3px; + width: 16px; + height: 16px; + display: flex; + user-select: none; +} + +.toolbar i.chevron-down.inside { + width: 16px; + height: 16px; + display: flex; + margin-left: -25px; + margin-top: 11px; + margin-right: 10px; + pointer-events: none; +} + +i.chevron-down { + background-color: transparent; + background-size: contain; + display: inline-block; + height: 8px; + width: 8px; + background-image: url(images/icons/chevron-down.svg); +} + +#block-controls button:hover { + background-color: #efefef; +} + +#block-controls button:focus-visible { + border-color: blue; +} + +#block-controls span.block-type { + background-size: contain; + display: block; + width: 18px; + height: 18px; + margin: 2px; +} + +#block-controls span.block-type.paragraph { + background-image: url(images/icons/text-paragraph.svg); +} + +#block-controls span.block-type.h1 { + background-image: url(images/icons/type-h1.svg); +} + +#block-controls span.block-type.h2 { + background-image: url(images/icons/type-h2.svg); +} + +#block-controls span.block-type.quote { + background-image: url(images/icons/chat-square-quote.svg); +} + +#block-controls span.block-type.ul { + background-image: url(images/icons/list-ul.svg); +} + +#block-controls span.block-type.ol { + background-image: url(images/icons/list-ol.svg); +} + +#block-controls span.block-type.code { + background-image: url(images/icons/code.svg); +} + +.dropdown { + z-index: 5; + display: block; + position: absolute; + box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1), + inset 0 0 0 1px rgba(255, 255, 255, 0.5); + border-radius: 8px; + min-width: 100px; + min-height: 40px; + background-color: #fff; +} + +.dropdown .item { + margin: 0 8px 0 8px; + padding: 8px; + color: #050505; + cursor: pointer; + line-height: 16px; + font-size: 15px; + display: flex; + align-content: center; + flex-direction: row; + flex-shrink: 0; + justify-content: space-between; + background-color: #fff; + border-radius: 8px; + border: 0; + min-width: 268px; +} + +.dropdown .item .active { + display: flex; + width: 20px; + height: 20px; + background-size: contain; +} + +.dropdown .item:first-child { + margin-top: 8px; +} + +.dropdown .item:last-child { + margin-bottom: 8px; +} + +.dropdown .item:hover { + background-color: #eee; +} + +.dropdown .item .text { + display: flex; + line-height: 20px; + flex-grow: 1; + width: 200px; +} + +.dropdown .item .icon { + display: flex; + width: 20px; + height: 20px; + user-select: none; + margin-right: 12px; + line-height: 16px; + background-size: contain; +} + +.link-editor { + position: absolute; + z-index: 100; + top: -10000px; + left: -10000px; + margin-top: -6px; + max-width: 300px; + width: 100%; + opacity: 0; + background-color: #fff; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3); + border-radius: 8px; + transition: opacity 0.5s; +} + +.link-editor .link-input { + display: block; + width: calc(100% - 24px); + box-sizing: border-box; + margin: 8px 12px; + padding: 8px 12px; + border-radius: 15px; + background-color: #eee; + font-size: 15px; + color: rgb(5, 5, 5); + border: 0; + outline: 0; + position: relative; + font-family: inherit; +} + +.link-editor div.link-edit { + background-image: url(images/icons/pencil-fill.svg); + background-size: 16px; + background-position: center; + background-repeat: no-repeat; + width: 35px; + vertical-align: -0.25em; + position: absolute; + right: 0; + top: 0; + bottom: 0; + cursor: pointer; +} + +.link-editor .link-input a { + color: rgb(33, 111, 219); + text-decoration: none; + display: block; + white-space: nowrap; + overflow: hidden; + margin-right: 30px; + text-overflow: ellipsis; +} + +.link-editor .link-input a:hover { + text-decoration: underline; +} + +.link-editor .button { + width: 20px; + height: 20px; + display: inline-block; + padding: 6px; + border-radius: 8px; + cursor: pointer; + margin: 0 2px; +} + +.link-editor .button.hovered { + width: 20px; + height: 20px; + display: inline-block; + background-color: #eee; +} + +.link-editor .button i, +.actions i { + background-size: contain; + display: inline-block; + height: 20px; + width: 20px; + vertical-align: -0.25em; +} + +i.undo { + background-image: url(images/icons/arrow-counterclockwise.svg); +} + +i.redo { + background-image: url(images/icons/arrow-clockwise.svg); +} + +.icon.paragraph { + background-image: url(images/icons/text-paragraph.svg); +} + +.icon.large-heading, +.icon.h1 { + background-image: url(images/icons/type-h1.svg); +} + +.icon.small-heading, +.icon.h2 { + background-image: url(images/icons/type-h2.svg); +} + +.icon.small-heading, +.icon.h3 { + background-image: url(images/icons/type-h3.svg); +} + +.icon.small-heading, +.icon.h4 { + background-image: url(images/icons/type-h4.svg); +} + +.icon.small-heading, +.icon.h5 { + background-image: url(images/icons/type-h5.svg); +} + +.icon.small-heading, +.icon.h6 { + background-image: url(images/icons/type-h6.svg); +} + +.icon.bullet-list, +.icon.ul { + background-image: url(images/icons/list-ul.svg); +} + +.icon.numbered-list, +.icon.ol { + background-image: url(images/icons/list-ol.svg); +} + +.icon.quote { + background-image: url(images/icons/chat-square-quote.svg); +} + +.icon.code { + background-image: url(images/icons/code.svg); +} + +i.bold { + background-image: url(images/icons/type-bold.svg); +} + +i.italic { + background-image: url(images/icons/type-italic.svg); +} + +i.underline { + background-image: url(images/icons/type-underline.svg); +} + +i.strikethrough { + background-image: url(images/icons/type-strikethrough.svg); +} + +i.code { + background-image: url(images/icons/code.svg); +} + +i.link { + background-image: url(images/icons/link.svg); +} + +i.left-align { + background-image: url(images/icons/text-left.svg); +} + +i.center-align { + background-image: url(images/icons/text-center.svg); +} + +i.right-align { + background-image: url(images/icons/text-right.svg); +} + +i.justify-align { + background-image: url(images/icons/justify.svg); +} .editor-table { border-collapse: collapse; @@ -759,12 +782,15 @@ body { width: max-content; margin: 30px 0; } + .editor-tableSelection *::selection { background-color: transparent; } + .editor-tableSelected { outline: 2px solid rgb(60, 132, 244); } + .editor-tableCell { border: 1px solid #bbb; width: 75px; @@ -775,6 +801,7 @@ body { position: relative; outline: none; } + .editor-tableCellSortedIndicator { display: block; opacity: 0.5; @@ -785,6 +812,7 @@ body { height: 4px; background-color: #999; } + .editor-tableCellResizer { position: absolute; right: -4px; @@ -794,13 +822,16 @@ body { z-index: 10; top: 0; } + .editor-tableCellHeader { background-color: #f2f3f5; text-align: start; } + .editor-tableCellSelected { background-color: #c9dbf0; } + .editor-tableCellPrimarySelected { border: 2px solid rgb(60, 132, 244); display: block; @@ -811,10 +842,12 @@ body { top: -1px; z-index: 2; } + .editor-tableCellEditing { box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); border-radius: 3px; } + .editor-tableAddColumns { position: absolute; top: 0; @@ -826,6 +859,7 @@ body { border: 0; cursor: pointer; } + .editor-tableAddColumns:after { background-image: url(images/icons/plus.svg); background-size: contain; @@ -840,9 +874,11 @@ body { height: 100%; opacity: 0.4; } + .editor-tableAddColumns:hover { background-color: #c9dbf0; } + .editor-tableAddRows { position: absolute; bottom: -25px; @@ -854,6 +890,7 @@ body { border: 0; cursor: pointer; } + .editor-tableAddRows:after { background-image: url(images/icons/plus.svg); background-size: contain; @@ -868,9 +905,11 @@ body { height: 100%; opacity: 0.4; } + .editor-tableAddRows:hover { background-color: #c9dbf0; } + @keyframes table-controls { 0% { opacity: 0; @@ -879,6 +918,7 @@ body { opacity: 1; } } + .editor-tableCellResizeRuler { display: block; position: absolute; @@ -887,6 +927,7 @@ body { height: 100%; top: 0; } + .editor-tableCellActionButtonContainer { display: block; right: 5px; @@ -896,6 +937,7 @@ body { width: 20px; height: 20px; } + .editor-tableCellActionButton { background-color: #eee; display: block; @@ -906,6 +948,7 @@ body { color: #222; cursor: pointer; } + .editor-tableCellActionButton:hover { background-color: #ddd; } diff --git a/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx b/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx new file mode 100644 index 0000000..4e8ac02 --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.jsx @@ -0,0 +1,221 @@ +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import { + LexicalContextMenuPlugin, + MenuOption, +} from '@lexical/react/LexicalContextMenuPlugin'; +import {useCallback, useMemo} from 'react'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { + $getSelection, + $isRangeSelection, + COPY_COMMAND, + CUT_COMMAND, + PASTE_COMMAND, +} from 'lexical'; +function ContextMenuItem({ + index, + isSelected, + onClick, + onMouseEnter, + option, + }) { + let className = 'item'; + if (isSelected) { + className += ' selected'; + } + return ( +
  • + {option.title} +
  • + ); +} + +function ContextMenu({ + options, + selectedItemIndex, + onOptionClick, + onOptionMouseEnter, + }) { + return ( +
    +
      + {options.map((option, i) => ( + onOptionClick(option, i)} + onMouseEnter={() => onOptionMouseEnter(i)} + key={option.key} + option={option} + /> + ))} +
    +
    + ); +} + +export class ContextMenuOption extends MenuOption { + title; + onSelect; + constructor( + title, + options, + ) { + super(title); + this.title = title; + this.onSelect = options.onSelect.bind(this); + } +} + +export default function ContextMenuPlugin(){ + const [editor] = useLexicalComposerContext(); + + const options = useMemo(() => { + return [ + new ContextMenuOption(`复制`, { + onSelect: (_node) => { + editor.dispatchCommand(COPY_COMMAND, null); + }, + }), + new ContextMenuOption(`剪切`, { + onSelect: (_node) => { + editor.dispatchCommand(CUT_COMMAND, null); + }, + }), + new ContextMenuOption(`粘贴`, { + onSelect: (_node) => { + navigator.clipboard.read().then(async (...args) => { + const data = new DataTransfer(); + + const items = await navigator.clipboard.read(); + const item = items[0]; + + const permission = await navigator.permissions.query({ + // @ts-expect-error These types are incorrect. + name: 'clipboard-read', + }); + if (permission.state === 'denied') { + alert('Not allowed to paste from clipboard.'); + return; + } + + for (const type of item.types) { + const dataString = await (await item.getType(type)).text(); + data.setData(type, dataString); + } + + const event = new ClipboardEvent('paste', { + clipboardData: data, + }); + + editor.dispatchCommand(PASTE_COMMAND, event); + }); + }, + }), + new ContextMenuOption(`作为文本复制`, { + onSelect: (_node) => { + navigator.clipboard.read().then(async (...args) => { + const permission = await navigator.permissions.query({ + // @ts-expect-error These types are incorrect. + name: 'clipboard-read', + }); + + if (permission.state === 'denied') { + alert('Not allowed to paste from clipboard.'); + return; + } + + const data = new DataTransfer(); + const items = await navigator.clipboard.readText(); + data.setData('text/plain', items); + + const event = new ClipboardEvent('paste', { + clipboardData: data, + }); + editor.dispatchCommand(PASTE_COMMAND, event); + }); + }, + }), + new ContextMenuOption(`删除文本块`, { + onSelect: (_node) => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + const currentNode = selection.anchor.getNode(); + const ancestorNodeWithRootAsParent = currentNode + .getParents() + .at(-2); + + ancestorNodeWithRootAsParent?.remove(); + } + }, + }), + ]; + }, [editor]); + + const onSelectOption = useCallback( + ( + selectedOption, + targetNode, + closeMenu, + ) => { + editor.update(() => { + selectedOption.onSelect(targetNode); + closeMenu(); + }); + }, + [editor], + ); + + return ( + + anchorElementRef.current + ? ReactDOM.createPortal( +
    + { + setHighlightedIndex(index); + selectOptionAndCleanUp(option); + }} + onOptionMouseEnter={(index) => { + setHighlightedIndex(index); + }} + /> +
    , + anchorElementRef.current, + ) + : null + } + /> + ); +} diff --git a/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.less b/src/pages/Note/Hlexical/plugins/ContextMenuPlugin/index.less new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Note/Hlexical/plugins/SaveFilePlugin.js b/src/pages/Note/Hlexical/plugins/SaveFilePlugin.js index 06209df..9049d2e 100644 --- a/src/pages/Note/Hlexical/plugins/SaveFilePlugin.js +++ b/src/pages/Note/Hlexical/plugins/SaveFilePlugin.js @@ -10,6 +10,7 @@ import {useDispatch, useSelector} from "react-redux"; import md5 from "md5" import {message} from "antd"; const {ipcRenderer} = window.require('electron') +import "./ToobarPlugin.less" const SaveFilePlugin=(props)=> { let activeKey = useSelector(state => state.tableBarItem.activeKey); const dispatch = useDispatch(); diff --git a/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.jsx b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.jsx new file mode 100644 index 0000000..e2d4de1 --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.jsx @@ -0,0 +1,206 @@ +import './index.less'; +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents'; +import {useEffect, useRef, useState} from 'react'; +import * as React from 'react'; +import {createPortal} from "react-dom"; +import {useSelector} from "react-redux"; + +const MARGIN_ABOVE_EDITOR = 624; +const HEADING_WIDTH = 9; + +function indent(tagName) { + if (tagName === 'h1') { + return 'heading1'; + } else if (tagName === 'h2') { + return 'heading2'; + } else if (tagName === 'h3') { + return 'heading3'; + } else if (tagName === 'h4') { + return 'heading4'; + } else if (tagName === 'h5') { + return 'heading5'; + } else if (tagName === 'h6') { + return 'heading6'; + } +} + +function isHeadingAtTheTopOfThePage(element) { + const elementYPosition = element?.getClientRects()[0].y; + return ( + elementYPosition >= MARGIN_ABOVE_EDITOR && + elementYPosition <= MARGIN_ABOVE_EDITOR + HEADING_WIDTH + ); +} + +function isHeadingAboveViewport(element) { + const elementYPosition = element?.getClientRects()[0].y; + return elementYPosition < MARGIN_ABOVE_EDITOR; +} + +function isHeadingBelowTheTopOfThePage(element) { + const elementYPosition = element?.getClientRects()[0].y; + return elementYPosition >= MARGIN_ABOVE_EDITOR + HEADING_WIDTH; +} + +function TableOfContentsList({ + tableOfContents, + filePath + }) { + const [selectedKey, setSelectedKey] = useState(''); + const selectedIndex = useRef(0); + const [editor] = useLexicalComposerContext(); + const [show,setShow]=useState(false) + const activeFilePath=useSelector(state => state.tableBarItem.activeKey) + function scrollToNode(key, currIndex) { + editor.getEditorState().read(() => { + const domElement = editor.getElementByKey(key); + if (domElement !== null) { + domElement.scrollIntoView(); + setSelectedKey(key); + selectedIndex.current = currIndex; + } + }); + } + + useEffect(() => { + function scrollCallback() { + if ( + tableOfContents.length !== 0 && + selectedIndex.current < tableOfContents.length - 1 + ) { + let currentHeading = editor.getElementByKey( + tableOfContents[selectedIndex.current][0], + ); + if (currentHeading !== null) { + if (isHeadingBelowTheTopOfThePage(currentHeading)) { + //On natural scroll, user is scrolling up + while ( + currentHeading !== null && + isHeadingBelowTheTopOfThePage(currentHeading) && + selectedIndex.current > 0 + ) { + const prevHeading = editor.getElementByKey( + tableOfContents[selectedIndex.current - 1][0], + ); + if ( + prevHeading !== null && + (isHeadingAboveViewport(prevHeading) || + isHeadingBelowTheTopOfThePage(prevHeading)) + ) { + selectedIndex.current--; + } + currentHeading = prevHeading; + } + const prevHeadingKey = tableOfContents[selectedIndex.current][0]; + setSelectedKey(prevHeadingKey); + } else if (isHeadingAboveViewport(currentHeading)) { + //On natural scroll, user is scrolling down + while ( + currentHeading !== null && + isHeadingAboveViewport(currentHeading) && + selectedIndex.current < tableOfContents.length - 1 + ) { + const nextHeading = editor.getElementByKey( + tableOfContents[selectedIndex.current + 1][0], + ); + if ( + nextHeading !== null && + (isHeadingAtTheTopOfThePage(nextHeading) || + isHeadingAboveViewport(nextHeading)) + ) { + selectedIndex.current++; + } + currentHeading = nextHeading; + } + const nextHeadingKey = tableOfContents[selectedIndex.current][0]; + setSelectedKey(nextHeadingKey); + } + } + } else { + selectedIndex.current = 0; + } + } + + let timerId; + + function debounceFunction(func, delay) { + clearTimeout(timerId); + timerId = setTimeout(func, delay); + } + + function onScroll() { + debounceFunction(scrollCallback, 10); + } + + document.addEventListener('scroll', onScroll); + if (document.getElementById("leftTableOfContents")&&filePath===activeFilePath){ + setShow(true) + }else { + setShow(false) + } + return () => document.removeEventListener('scroll', onScroll); + }, [tableOfContents, editor, + useSelector(state => state.tableBarItem.leftTableOfContents), + useSelector(state => state.tableBarItem.activeKey) + ]); + return <> + {show && createPortal(
    +
      + {tableOfContents.map(([key, text, tag], index) => { + if (index === 0) { + return ( +
      +
      scrollToNode(key, index)} + role="button" + tabIndex={0}> + {('' + text).length > 20 + ? text.substring(0, 20) + '...' + : text} +
      +
      + ); + } else { + return ( +
      +
      scrollToNode(key, index)} + role="button" + className={indent(tag)} + tabIndex={0}> +
    • + {('' + text).length > 27 + ? text.substring(0, 27) + '...' + : text} +
    • +
      +
      + ); + } + })} +
    +
    , document.getElementById("leftTableOfContents"))} + + + ; +} + +export default function TableOfContentsPlugin(prop) { + return ( + + {(tableOfContents) => { + return ; + }} + + ); +} diff --git a/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.css b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.less similarity index 87% rename from src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.css rename to src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.less index ecf6672..bce7fbd 100644 --- a/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.css +++ b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.less @@ -6,6 +6,18 @@ margin-left: 20px; } +.table-of-contents .heading4 { + margin-left: 30px; +} + +.table-of-contents .heading5 { + margin-left: 40px; +} + +.table-of-contents .heading6 { + margin-left: 50px; +} + .selected-heading { color: #3578e5; position: relative; @@ -34,8 +46,8 @@ .table-of-contents { color: #65676b; position: fixed; - top: 200px; - right: -35px; + //top: 200px; + //right: -35px; padding: 10px; width: 250px; display: flex; diff --git a/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx deleted file mode 100644 index 56fbbf7..0000000 --- a/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ -import type {TableOfContentsEntry} from '@lexical/react/LexicalTableOfContents'; -import type {HeadingTagType} from '@lexical/rich-text'; -import type {NodeKey} from 'lexical'; - -import './index.css'; - -import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; -import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents'; -import {useEffect, useRef, useState} from 'react'; -import * as React from 'react'; - -const MARGIN_ABOVE_EDITOR = 624; -const HEADING_WIDTH = 9; - -function indent(tagName: HeadingTagType) { - if (tagName === 'h2') { - return 'heading2'; - } else if (tagName === 'h3') { - return 'heading3'; - } -} - -function isHeadingAtTheTopOfThePage(element: HTMLElement): boolean { - const elementYPosition = element?.getClientRects()[0].y; - return ( - elementYPosition >= MARGIN_ABOVE_EDITOR && - elementYPosition <= MARGIN_ABOVE_EDITOR + HEADING_WIDTH - ); -} -function isHeadingAboveViewport(element: HTMLElement): boolean { - const elementYPosition = element?.getClientRects()[0].y; - return elementYPosition < MARGIN_ABOVE_EDITOR; -} -function isHeadingBelowTheTopOfThePage(element: HTMLElement): boolean { - const elementYPosition = element?.getClientRects()[0].y; - return elementYPosition >= MARGIN_ABOVE_EDITOR + HEADING_WIDTH; -} - -function TableOfContentsList({ - tableOfContents, -}: { - tableOfContents: Array; -}): JSX.Element { - const [selectedKey, setSelectedKey] = useState(''); - const selectedIndex = useRef(0); - const [editor] = useLexicalComposerContext(); - - function scrollToNode(key: NodeKey, currIndex: number) { - editor.getEditorState().read(() => { - const domElement = editor.getElementByKey(key); - if (domElement !== null) { - domElement.scrollIntoView(); - setSelectedKey(key); - selectedIndex.current = currIndex; - } - }); - } - - useEffect(() => { - function scrollCallback() { - if ( - tableOfContents.length !== 0 && - selectedIndex.current < tableOfContents.length - 1 - ) { - let currentHeading = editor.getElementByKey( - tableOfContents[selectedIndex.current][0], - ); - if (currentHeading !== null) { - if (isHeadingBelowTheTopOfThePage(currentHeading)) { - //On natural scroll, user is scrolling up - while ( - currentHeading !== null && - isHeadingBelowTheTopOfThePage(currentHeading) && - selectedIndex.current > 0 - ) { - const prevHeading = editor.getElementByKey( - tableOfContents[selectedIndex.current - 1][0], - ); - if ( - prevHeading !== null && - (isHeadingAboveViewport(prevHeading) || - isHeadingBelowTheTopOfThePage(prevHeading)) - ) { - selectedIndex.current--; - } - currentHeading = prevHeading; - } - const prevHeadingKey = tableOfContents[selectedIndex.current][0]; - setSelectedKey(prevHeadingKey); - } else if (isHeadingAboveViewport(currentHeading)) { - //On natural scroll, user is scrolling down - while ( - currentHeading !== null && - isHeadingAboveViewport(currentHeading) && - selectedIndex.current < tableOfContents.length - 1 - ) { - const nextHeading = editor.getElementByKey( - tableOfContents[selectedIndex.current + 1][0], - ); - if ( - nextHeading !== null && - (isHeadingAtTheTopOfThePage(nextHeading) || - isHeadingAboveViewport(nextHeading)) - ) { - selectedIndex.current++; - } - currentHeading = nextHeading; - } - const nextHeadingKey = tableOfContents[selectedIndex.current][0]; - setSelectedKey(nextHeadingKey); - } - } - } else { - selectedIndex.current = 0; - } - } - let timerId: ReturnType; - - function debounceFunction(func: () => void, delay: number) { - clearTimeout(timerId); - timerId = setTimeout(func, delay); - } - - function onScroll(): void { - debounceFunction(scrollCallback, 10); - } - - document.addEventListener('scroll', onScroll); - return () => document.removeEventListener('scroll', onScroll); - }, [tableOfContents, editor]); - - return ( -
    -
      - {tableOfContents.map(([key, text, tag], index) => { - if (index === 0) { - return ( -
      -
      scrollToNode(key, index)} - role="button" - tabIndex={0}> - {('' + text).length > 20 - ? text.substring(0, 20) + '...' - : text} -
      -
      -
      - ); - } else { - return ( -
      -
      scrollToNode(key, index)} - role="button" - className={indent(tag)} - tabIndex={0}> -
    • - {('' + text).length > 27 - ? text.substring(0, 27) + '...' - : text} -
    • -
      -
      - ); - } - })} -
    -
    - ); -} - -export default function TableOfContentsPlugin() { - return ( - - {(tableOfContents) => { - return ; - }} - - ); -} diff --git a/src/pages/Note/Hlexical/plugins/ToobarPlugin.less b/src/pages/Note/Hlexical/plugins/ToobarPlugin.less new file mode 100644 index 0000000..794de07 --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/ToobarPlugin.less @@ -0,0 +1,16 @@ +i.diagram-2 { + background-image: url(../images/icons/diagram-2.svg); +} +i.horizontal-rule { + background-image: url(../images/icons/horizontal-rule.svg); +} +i.image { + background-image: url(../images/icons/file-image.svg); +} + +i.table { + background-image: url(../images/icons/table.svg); +} +.icon.plus { + background-image: url(../images/icons/plus.svg); +} diff --git a/src/pages/Note/Hlexical/plugins/ToolbarPlugin.js b/src/pages/Note/Hlexical/plugins/ToolbarPlugin.js index f0f812f..f3673bf 100644 --- a/src/pages/Note/Hlexical/plugins/ToolbarPlugin.js +++ b/src/pages/Note/Hlexical/plugins/ToolbarPlugin.js @@ -59,6 +59,7 @@ const supportedBlockTypes = new Set([ "h3", "h4", "h5", + "h6", "ul", "ol" ]); @@ -70,6 +71,7 @@ const blockTypeToBlockName = { h3: "三级标题", h4: "四级标题", h5: "五级标题", + h6: "六级标题", ol: "有序序列", paragraph: "普通文本", quote: "引用", @@ -720,14 +722,6 @@ export default function ToolbarPlugin() { 分割线 - {/* {*/} - {/* activeEditor.dispatchCommand(INSERT_PAGE_BREAK, undefined);*/} - {/* }}*/} - {/* className="item">*/} - {/* */} - {/* 页分割线*/} - {/**/} { showModal('插入图片', (onClose) => ( diff --git a/src/pages/Note/index.jsx b/src/pages/Note/index.jsx index 263ae61..7ba481d 100644 --- a/src/pages/Note/index.jsx +++ b/src/pages/Note/index.jsx @@ -7,7 +7,7 @@ import Hlexical from './Hlexical'; import ItemTree from "../../components/ItemTree"; import './index.less' import {useSelector, useDispatch} from "react-redux"; -import {addTableBarItem, removeTableBarItem, setActiveKey,updatedSavedFile} from "../../redux/tableBarItem_reducer" +import {addTableBarItem, removeTableBarItem, setActiveKey,editLeftTableOfContents} from "../../redux/tableBarItem_reducer" const {Sider} = Layout; const Note = () => { @@ -31,13 +31,17 @@ const Note = () => { }, { key: '2', label: '标题', - children: '开发中,尽情期待。。', + children:
    , + forceRender:true } ] const onChange = (newActiveKey) => { console.log("setActiveKey(newActiveKey)",newActiveKey) dispatch(setActiveKey({"activeKey":newActiveKey})); }; + const onChangeLeftTableOfContents = (activeKey)=>{ + dispatch(editLeftTableOfContents({"leftTableOfContents":activeKey})) + } const add = () => { const newActiveKey = `newTab${newTabIndex.current++}`; dispatch(addTableBarItem( @@ -90,8 +94,8 @@ const Note = () => { - state.tableBarItem.leftTableOfContents)} items={itemTreeTab} + onChange={onChangeLeftTableOfContents} style ={{background:"#fff"}} /> @@ -109,4 +113,4 @@ const Note = () => { ); }; -export default Note; \ No newline at end of file +export default Note; diff --git a/src/redux/tableBarItem_reducer.js b/src/redux/tableBarItem_reducer.js index 35f7121..08c0d1d 100644 --- a/src/redux/tableBarItem_reducer.js +++ b/src/redux/tableBarItem_reducer.js @@ -1,5 +1,4 @@ import { createSlice } from '@reduxjs/toolkit' -import {isEmpty} from "../utils/ObjectUtils"; import {getFileFullNameByPath} from "../utils/PathOperate"; /** @@ -16,7 +15,8 @@ export const tableBarItemSlice = createSlice({ type:"tableBarItem", data: [], activeKey:"", - expandedKeyList:[] + expandedKeyList:[], + leftTableOfContents:"" }, reducers: { addTableBarItem: (state, action) => { @@ -82,6 +82,9 @@ export const tableBarItemSlice = createSlice({ }, removeExpandedKeys:(state, action)=>{ state.expandedKeyList=state.expandedKeyList.filter(key=>key!==action.payload) + }, + editLeftTableOfContents:(state, action)=>{ + state.leftTableOfContents=action.payload.leftTableOfContents } } }) @@ -92,6 +95,7 @@ export const { addTableBarItem, setExpandedKeys, removeExpandedKeys, addExpandedKeys, - updateFileName + updateFileName, + editLeftTableOfContents } = tableBarItemSlice.actions export default tableBarItemSlice.reducer