From 0faa8a89ffa64e2cc5bf4d1c779782fb9fe69e9e Mon Sep 17 00:00:00 2001 From: Takagi <1103069291@qq.com> Date: Thu, 25 Jan 2024 11:05:50 +0800 Subject: [PATCH] pref: resolve horizontal scrollbar display issue in table (#5191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement #### What this PR does / why we need it: 为溢出的表格增加鼠标滚动功能,当光标在溢出的表格上时,滚动鼠标则可以使表格左右滚动。 当表格可以左右滚动时,增加侧边阴影用于提示用户。 #### How to test it? 测试具有滚动条的表格是否有侧边阴影用于提示用户,并且用鼠标滚动是否可以使表格左右滚动。 #### Which issue(s) this PR fixes: Fixes #5182 #### Does this PR introduce a user-facing change? ```release-note 优化富文本编辑器中表格组件可滚动时的显示效果 ``` --- .../editor/src/extensions/table/index.ts | 87 ++++++++++++++++++- console/packages/editor/src/styles/table.scss | 41 +++++++-- 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/console/packages/editor/src/extensions/table/index.ts b/console/packages/editor/src/extensions/table/index.ts index 25412ac21..f9862e5a2 100644 --- a/console/packages/editor/src/extensions/table/index.ts +++ b/console/packages/editor/src/extensions/table/index.ts @@ -8,12 +8,16 @@ import { type Range, mergeAttributes, isNodeActive, -} from "@/tiptap/vue-3"; + CoreEditor, +} from "@/tiptap"; import { type Node as ProseMirrorNode, type NodeView, type EditorState, type DOMOutputSpec, + Plugin, + DecorationSet, + Decoration, } from "@/tiptap/pm"; import TableCell from "./table-cell"; import TableRow from "./table-row"; @@ -94,6 +98,8 @@ function updateColumns( } } +let editor: CoreEditor | undefined = undefined; + class TableView implements NodeView { node: ProseMirrorNode; @@ -109,15 +115,33 @@ class TableView implements NodeView { contentDOM: HTMLElement; + containerDOM: HTMLElement; + constructor(node: ProseMirrorNode, cellMinWidth: number) { this.node = node; this.cellMinWidth = cellMinWidth; this.dom = document.createElement("div"); - this.dom.className = "tableWrapper"; + this.dom.className = "table-container"; + + this.containerDOM = this.dom.appendChild(document.createElement("div")); + + this.containerDOM.className = "tableWrapper"; + this.containerDOM.addEventListener("wheel", (e) => { + return this.handleHorizontalWheel(this.containerDOM, e); + }); + this.containerDOM.addEventListener("scroll", () => { + if (!editor) { + return false; + } + const { state, view } = editor; + const { tr } = state; + view.dispatch(tr); + return false; + }); this.scrollDom = document.createElement("div"); this.scrollDom.className = "scrollWrapper"; - this.dom.appendChild(this.scrollDom); + this.containerDOM.appendChild(this.scrollDom); this.table = this.scrollDom.appendChild(document.createElement("table")); this.colgroup = this.table.appendChild(document.createElement("colgroup")); @@ -132,7 +156,6 @@ class TableView implements NodeView { this.node = node; updateColumns(node, this.colgroup, this.table, this.cellMinWidth); - return true; } @@ -145,6 +168,16 @@ class TableView implements NodeView { this.colgroup.contains(mutation.target)) ); } + + handleHorizontalWheel(dom: HTMLElement, event: WheelEvent) { + const { scrollWidth, clientWidth } = dom; + const hasScrollWidth = scrollWidth > clientWidth; + if (hasScrollWidth) { + event.stopPropagation(); + event.preventDefault(); + dom.scrollBy({ left: event.deltaY }); + } + } } const Table = TiptapTable.extend({ @@ -450,6 +483,52 @@ const Table = TiptapTable.extend({ return table; }, + + onTransaction() { + editor = this.editor; + }, + + addProseMirrorPlugins() { + const plugins = this.parent?.() ?? []; + return [ + ...plugins, + new Plugin({ + props: { + decorations: (state) => { + const { doc, tr } = state; + const decorations: Decoration[] = []; + doc.descendants((node, pos) => { + if (node.type.name === Table.name) { + const { view } = this.editor; + const nodeDom = view.nodeDOM(pos) || view.domAtPos(pos)?.node; + if (!nodeDom) { + return true; + } + const { scrollWidth, clientWidth, scrollLeft } = + nodeDom.firstChild as HTMLElement; + let classNames = ""; + if ( + scrollWidth > clientWidth && + scrollLeft < scrollWidth - clientWidth + ) { + classNames += "table-right-shadow "; + } + if (scrollLeft > 0) { + classNames += "table-left-shadow "; + } + decorations.push( + Decoration.node(pos, pos + node.nodeSize, { + class: classNames, + }) + ); + } + }); + return DecorationSet.create(tr.doc, decorations); + }, + }, + }), + ]; + }, }).configure({ resizable: true }); export default Table; diff --git a/console/packages/editor/src/styles/table.scss b/console/packages/editor/src/styles/table.scss index a6891000f..f2004dad7 100644 --- a/console/packages/editor/src/styles/table.scss +++ b/console/packages/editor/src/styles/table.scss @@ -6,11 +6,46 @@ $tableResizeHandleBgColor: #adf; .ProseMirror { + .table-container { + position: relative; + + &.table-right-shadow { + &::after { + bottom: 15px; + background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.08)); + right: 0; + content: " "; + position: absolute; + pointer-events: none; + top: 0; + width: 8px; + z-index: 2; + margin-top: 27px; + } + } + + &.table-left-shadow { + &::before { + bottom: 15px; + background: linear-gradient(-90deg, transparent, rgba(0, 0, 0, 0.08)); + left: 0; + content: " "; + position: absolute; + pointer-events: none; + top: 0; + width: 8px; + z-index: 2; + margin-top: 27px; + } + } + } + .tableWrapper { position: relative; margin: 0.5em 0px; overflow-x: auto; overflow-y: hidden; + cursor: default; &.has-focus { .scrollWrapper { @@ -44,6 +79,7 @@ tr { position: relative; border-bottom: 1px solid $tableBorderColor; + cursor: text; } th { @@ -281,13 +317,8 @@ right: -2px; bottom: -2px; width: 4px; - pointer-events: none; background-color: $tableResizeHandleBgColor; z-index: 1; - } - - &.resize-cursor { - cursor: ew-resize; cursor: col-resize; } }