From 8dd57ec5b467f80299db4319229e516b50d32819 Mon Sep 17 00:00:00 2001 From: shixiaohua Date: Thu, 29 Feb 2024 09:05:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E8=A1=A8=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 315 +++++++- package.json | 4 +- .../Hlexical/context/shared/invariant.jsx | 15 + .../Note/Hlexical/createWebsocketProvider.js | 33 - src/pages/Note/Hlexical/index.jsx | 14 +- src/pages/Note/Hlexical/index.less | 162 +++- .../nodes/TableNode/TableComponent/index.jsx | 6 +- src/pages/Note/Hlexical/nodes/UsefulNodes.jsx | 2 +- .../plugins/Input/ColorPicker/index.jsx | 307 ++++++++ .../plugins/Input/ColorPicker/index.less | 88 +++ .../plugins/TableActionMenuPlugin/index.jsx | 710 ++++++++++++++++++ .../plugins/TableActionMenuPlugin/index.less | 6 + .../plugins/TableCellResizer/index.css | 3 + .../plugins/TableCellResizer/index.jsx | 407 ++++++++++ .../plugins/TableOfContentsPlugin/index.css | 87 +++ .../plugins/TableOfContentsPlugin/index.tsx | 197 +++++ .../Hlexical/plugins/TablePlugin/index.jsx | 2 +- src/pages/Note/Hlexical/themes/FirstTheme.js | 17 +- 18 files changed, 2323 insertions(+), 52 deletions(-) create mode 100644 src/pages/Note/Hlexical/context/shared/invariant.jsx delete mode 100644 src/pages/Note/Hlexical/createWebsocketProvider.js create mode 100644 src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.jsx create mode 100644 src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.less create mode 100644 src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.jsx create mode 100644 src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.less create mode 100644 src/pages/Note/Hlexical/plugins/TableCellResizer/index.css create mode 100644 src/pages/Note/Hlexical/plugins/TableCellResizer/index.jsx create mode 100644 src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.css create mode 100644 src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx diff --git a/package-lock.json b/package-lock.json index 4bd4400..46c3439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@electron-forge/maker-rpm": "^6.0.4", "@electron-forge/maker-squirrel": "^6.0.4", "@electron-forge/maker-zip": "^6.0.4", - "@lexical/react": "^0.12.2", + "@lexical/react": "^0.12.6", "@reduxjs/toolkit": "^1.9.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", @@ -39,7 +39,7 @@ "echarts-for-react": "^3.0.2", "electron": "^22.1.0", "formik": "^2.2.9", - "lexical": "^0.12.2", + "lexical": "^0.12.6", "localStorage": "^1.0.4", "nanoid": "^4.0.2", "prop-types": "^15.8.1", @@ -52,7 +52,9 @@ "redux-thunk": "^2.4.2", "umi-request": "^1.4.0", "wait-on": "^3.3.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "y-websocket": ">=1.3.x", + "yjs": ">=13.5.42" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -6953,6 +6955,23 @@ "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "dev": true, + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", @@ -7599,6 +7618,13 @@ "node": "*" } }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true, + "optional": true + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", @@ -10369,6 +10395,20 @@ "node": ">=10" } }, + "node_modules/deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "dev": true, + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/define-data-property": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.1.tgz", @@ -11419,6 +11459,22 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "dev": true, + "optional": true, + "dependencies": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -14167,6 +14223,13 @@ "node": ">=0.10.0" } }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", + "dev": true, + "optional": true + }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", @@ -14759,8 +14822,7 @@ "version": "0.2.5", "resolved": "https://registry.npmmirror.com/isomorphic.js/-/isomorphic.js-0.2.5.tgz", "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/isstream": { "version": "0.1.2", @@ -17474,6 +17536,157 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/level": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/level/-/level-6.0.1.tgz", + "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", + "dev": true, + "optional": true, + "dependencies": { + "level-js": "^5.0.0", + "level-packager": "^5.1.0", + "leveldown": "^5.4.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmmirror.com/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "dev": true, + "optional": true, + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "dev": true, + "optional": true, + "dependencies": { + "errno": "~0.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "dev": true, + "optional": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-js": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", + "dev": true, + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.3", + "buffer": "^5.5.0", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + } + }, + "node_modules/level-packager": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", + "dev": true, + "optional": true, + "dependencies": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "dev": true, + "optional": true, + "dependencies": { + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/leveldown/node_modules/node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/levelup": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", + "dev": true, + "optional": true, + "dependencies": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz", @@ -17507,7 +17720,6 @@ "resolved": "https://registry.npmmirror.com/lib0/-/lib0-0.2.88.tgz", "integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==", "dev": true, - "peer": true, "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -17797,6 +18009,13 @@ "node": ">=10" } }, + "node_modules/ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", + "dev": true, + "optional": true + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmmirror.com/lz-string/-/lz-string-1.5.0.tgz", @@ -18563,6 +18782,13 @@ "node": "^14 || ^16 || >=18" } }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "dev": true, + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", @@ -26698,6 +26924,82 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y-leveldb": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/y-leveldb/-/y-leveldb-0.1.2.tgz", + "integrity": "sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg==", + "dev": true, + "optional": true, + "dependencies": { + "level": "^6.0.1", + "lib0": "^0.2.31" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-protocols": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/y-protocols/-/y-protocols-1.0.6.tgz", + "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", + "dev": true, + "dependencies": { + "lib0": "^0.2.85" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, + "node_modules/y-websocket": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/y-websocket/-/y-websocket-1.5.4.tgz", + "integrity": "sha512-Y3021uy0anOIHqAPyAZbNDoR05JuMEGjRNI8c+K9MHzVS8dWoImdJUjccljAznc8H2L7WkIXhRHZ1igWNRSgPw==", + "dev": true, + "dependencies": { + "lib0": "^0.2.52", + "lodash.debounce": "^4.0.8", + "y-protocols": "^1.0.5" + }, + "bin": { + "y-websocket": "bin/server.js", + "y-websocket-server": "bin/server.js" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "ws": "^6.2.1", + "y-leveldb": "^0.1.0" + }, + "peerDependencies": { + "yjs": "^13.5.6" + } + }, + "node_modules/y-websocket/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "optional": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", @@ -26844,7 +27146,6 @@ "resolved": "https://registry.npmmirror.com/yjs/-/yjs-13.6.10.tgz", "integrity": "sha512-1JcyQek1vaMyrDm7Fqfa+pvHg/DURSbVo4VmeN7wjnTKB/lZrfIPhdCj7d8sboK6zLfRBJXegTjc9JlaDd8/Zw==", "dev": true, - "peer": true, "dependencies": { "lib0": "^0.2.86" }, diff --git a/package.json b/package.json index 35c60ea..8bdd4f5 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@electron-forge/maker-rpm": "^6.0.4", "@electron-forge/maker-squirrel": "^6.0.4", "@electron-forge/maker-zip": "^6.0.4", - "@lexical/react": "^0.12.2", + "@lexical/react": "^0.12.6", "@reduxjs/toolkit": "^1.9.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", @@ -27,7 +27,7 @@ "echarts-for-react": "^3.0.2", "electron": "^22.1.0", "formik": "^2.2.9", - "lexical": "^0.12.2", + "lexical": "^0.12.6", "localStorage": "^1.0.4", "nanoid": "^4.0.2", "prop-types": "^15.8.1", diff --git a/src/pages/Note/Hlexical/context/shared/invariant.jsx b/src/pages/Note/Hlexical/context/shared/invariant.jsx new file mode 100644 index 0000000..e1bcc5b --- /dev/null +++ b/src/pages/Note/Hlexical/context/shared/invariant.jsx @@ -0,0 +1,15 @@ +export default function invariant( + cond, + message, +...args +){ + if (cond) { + return; + } + + throw new Error( + 'Internal Lexical error: invariant() is meant to be replaced at compile ' + + 'time. There is no runtime version. Error: ' + + message, + ); +} diff --git a/src/pages/Note/Hlexical/createWebsocketProvider.js b/src/pages/Note/Hlexical/createWebsocketProvider.js deleted file mode 100644 index 823495f..0000000 --- a/src/pages/Note/Hlexical/createWebsocketProvider.js +++ /dev/null @@ -1,33 +0,0 @@ -// import {WebsocketProvider} from 'y-websocket'; -// import {Doc} from 'yjs'; -// -// const url = new URL(window.location.href); -// const params = new URLSearchParams(url.search); -// const WEBSOCKET_ENDPOINT = -// params.get('collabEndpoint') || 'ws://localhost:1234'; -// const WEBSOCKET_SLUG = 'playground'; -// const WEBSOCKET_ID = params.get('collabId') || '0'; -// -// export function createWebsocketProvider( -// id, -// yjsDocMap, -// ) { -// let doc = yjsDocMap.get(id); -// -// if (doc === undefined) { -// doc = new Doc(); -// yjsDocMap.set(id, doc); -// } else { -// doc.load(); -// } -// -// // @ts-ignore -// return new WebsocketProvider( -// WEBSOCKET_ENDPOINT, -// WEBSOCKET_SLUG + '/' + WEBSOCKET_ID + '/' + id, -// doc, -// { -// connect: false, -// }, -// ); -// } diff --git a/src/pages/Note/Hlexical/index.jsx b/src/pages/Note/Hlexical/index.jsx index a9f84a7..048f450 100644 --- a/src/pages/Note/Hlexical/index.jsx +++ b/src/pages/Note/Hlexical/index.jsx @@ -25,9 +25,11 @@ import SaveFilePlugin from "./plugins/SaveFilePlugin"; import {TabIndentationPlugin} from "@lexical/react/LexicalTabIndentationPlugin"; import UsefulNodes from "./nodes/UsefulNodes"; import ImagesPlugin from "./plugins/ImagesPlugin"; -import {TablePlugin} from "./plugins/TablePlugin"; + import {HorizontalRulePlugin} from "@lexical/react/LexicalHorizontalRulePlugin" import InlineImagePlugin from "./plugins/InlineImagePlugin"; +import {TablePlugin} from "@lexical/react/LexicalTablePlugin"; +import TableCellActionMenuPlugin from './plugins/TableActionMenuPlugin'; function Placeholder() { return
Enter some rich text...
; } @@ -71,7 +73,15 @@ export default function Hlexical(props) { - + {/* 表格操作 */} + + {/* 表格单元格操作 */} + + + {/*markdown 快捷键*/} diff --git a/src/pages/Note/Hlexical/index.less b/src/pages/Note/Hlexical/index.less index d5adae1..a4bd306 100644 --- a/src/pages/Note/Hlexical/index.less +++ b/src/pages/Note/Hlexical/index.less @@ -749,4 +749,164 @@ body { i.justify-align { background-image: url(images/icons/justify.svg); } - \ No newline at end of file + +.editor-table { + border-collapse: collapse; + border-spacing: 0; + overflow-y: scroll; + overflow-x: scroll; + table-layout: fixed; + 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; + min-width: 75px; + vertical-align: top; + text-align: start; + padding: 6px 8px; + position: relative; + outline: none; +} +.editor-tableCellSortedIndicator { + display: block; + opacity: 0.5; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 4px; + background-color: #999; +} +.editor-tableCellResizer { + position: absolute; + right: -4px; + height: 100%; + width: 8px; + cursor: ew-resize; + 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; + height: calc(100% - 2px); + position: absolute; + width: calc(100% - 2px); + left: -1px; + 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; + width: 20px; + background-color: #eee; + height: 100%; + right: -25px; + animation: table-controls 0.2s ease; + border: 0; + cursor: pointer; +} +.editor-tableAddColumns:after { + background-image: url(images/icons/plus.svg); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + display: block; + content: ' '; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.4; +} +.editor-tableAddColumns:hover { + background-color: #c9dbf0; +} +.editor-tableAddRows { + position: absolute; + bottom: -25px; + width: calc(100% - 25px); + background-color: #eee; + height: 20px; + left: 0; + animation: table-controls 0.2s ease; + border: 0; + cursor: pointer; +} +.editor-tableAddRows:after { + background-image: url(images/icons/plus.svg); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + display: block; + content: ' '; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.4; +} +.editor-tableAddRows:hover { + background-color: #c9dbf0; +} +@keyframes table-controls { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +.editor-tableCellResizeRuler { + display: block; + position: absolute; + width: 1px; + background-color: rgb(60, 132, 244); + height: 100%; + top: 0; +} +.editor-tableCellActionButtonContainer { + display: block; + right: 5px; + top: 6px; + position: absolute; + z-index: 4; + width: 20px; + height: 20px; +} +.editor-tableCellActionButton { + background-color: #eee; + display: block; + border: 0; + border-radius: 20px; + width: 20px; + height: 20px; + color: #222; + cursor: pointer; +} +.editor-tableCellActionButton:hover { + background-color: #ddd; +} + diff --git a/src/pages/Note/Hlexical/nodes/TableNode/TableComponent/index.jsx b/src/pages/Note/Hlexical/nodes/TableNode/TableComponent/index.jsx index 6e7375d..126e92e 100644 --- a/src/pages/Note/Hlexical/nodes/TableNode/TableComponent/index.jsx +++ b/src/pages/Note/Hlexical/nodes/TableNode/TableComponent/index.jsx @@ -686,10 +686,8 @@ export default function TableComponent({ _rows.unshift(rawRows[0]); return _rows; }, [rawRows, sortingOptions]); - const [primarySelectedCellID, setPrimarySelectedCellID] = useState< - null | string - >(null); - const cellEditor = useMemo(() => { + const [primarySelectedCellID, setPrimarySelectedCellID] = useState(null); + const cellEditor = useMemo(() => { if (cellEditorConfig === null) { return null; } diff --git a/src/pages/Note/Hlexical/nodes/UsefulNodes.jsx b/src/pages/Note/Hlexical/nodes/UsefulNodes.jsx index 834ede9..dc66910 100644 --- a/src/pages/Note/Hlexical/nodes/UsefulNodes.jsx +++ b/src/pages/Note/Hlexical/nodes/UsefulNodes.jsx @@ -1,11 +1,11 @@ import {HeadingNode, QuoteNode} from "@lexical/rich-text"; -import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; import {ListItemNode, ListNode} from "@lexical/list"; import {CodeHighlightNode, CodeNode, $createCodeNode, $isCodeNode} from "@lexical/code"; import {AutoLinkNode, LinkNode} from "@lexical/link"; import {ImageNode} from "./ImageNode"; import {HorizontalRuleNode} from "@lexical/react/LexicalHorizontalRuleNode"; import {InlineImageNode} from "./InlineImageNode"; +import {TableNode,TableCellNode, TableRowNode} from "@lexical/table"; const UsefulNodes=[ HeadingNode, diff --git a/src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.jsx b/src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.jsx new file mode 100644 index 0000000..70d2d1f --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.jsx @@ -0,0 +1,307 @@ +import './index.less'; + +import {useEffect, useMemo, useRef, useState} from 'react'; +import * as React from 'react'; + +import TextInput from '../TextInput'; +const basicColors = [ + '#d0021b', + '#f5a623', + '#f8e71c', + '#8b572a', + '#7ed321', + '#417505', + '#bd10e0', + '#9013fe', + '#4a90e2', + '#50e3c2', + '#b8e986', + '#000000', + '#4a4a4a', + '#9b9b9b', + '#ffffff', +]; + +const WIDTH = 214; +const HEIGHT = 150; + +export default function ColorPicker({ + color, + onChange, +}) { + const [selfColor, setSelfColor] = useState(transformColor('hex', color)); + const [inputColor, setInputColor] = useState(color); + const innerDivRef = useRef(null); + + const saturationPosition = useMemo( + () => ({ + x: (selfColor.hsv.s / 100) * WIDTH, + y: ((100 - selfColor.hsv.v) / 100) * HEIGHT, + }), + [selfColor.hsv.s, selfColor.hsv.v], + ); + + const huePosition = useMemo( + () => ({ + x: (selfColor.hsv.h / 360) * WIDTH, + }), + [selfColor.hsv], + ); + + const onSetHex = (hex) => { + setInputColor(hex); + if (/^#[0-9A-Fa-f]{6}$/i.test(hex)) { + const newColor = transformColor('hex', hex); + setSelfColor(newColor); + } + }; + + const onMoveSaturation = ({x, y}) => { + const newHsv = { + ...selfColor.hsv, + s: (x / WIDTH) * 100, + v: 100 - (y / HEIGHT) * 100, + }; + const newColor = transformColor('hsv', newHsv); + setSelfColor(newColor); + setInputColor(newColor.hex); + }; + + const onMoveHue = ({x}) => { + const newHsv = {...selfColor.hsv, h: (x / WIDTH) * 360}; + const newColor = transformColor('hsv', newHsv); + + setSelfColor(newColor); + setInputColor(newColor.hex); + }; + + useEffect(() => { + // Check if the dropdown is actually active + if (innerDivRef.current !== null && onChange) { + onChange(selfColor.hex); + setInputColor(selfColor.hex); + } + }, [selfColor, onChange]); + + useEffect(() => { + if (color === undefined) return; + const newColor = transformColor('hex', color); + setSelfColor(newColor); + setInputColor(newColor.hex); + }, [color]); + + return ( +
+ +
+ {basicColors.map((basicColor) => ( +
+ +
+ + +
+ +
+
+ ); +} + +function MoveWrapper({className, style, onChange, children}) { + const divRef = useRef(null); + + const move = (e) => { + if (divRef.current) { + const {current: div} = divRef; + const {width, height, left, top} = div.getBoundingClientRect(); + + const x = clamp(e.clientX - left, width, 0); + const y = clamp(e.clientY - top, height, 0); + + onChange({x, y}); + } + }; + + const onMouseDown = (e) => { + if (e.button !== 0) return; + + move(e); + + const onMouseMove = (_e) => { + move(_e); + }; + + const onMouseUp = (_e) => { + document.removeEventListener('mousemove', onMouseMove, false); + document.removeEventListener('mouseup', onMouseUp, false); + + move(_e); + }; + + document.addEventListener('mousemove', onMouseMove, false); + document.addEventListener('mouseup', onMouseUp, false); + }; + + return ( +
+ {children} +
+ ); +} + +function clamp(value, max, min) { + return value > max ? max : value < min ? min : value; +} + +export function toHex(value) { + if (!value.startsWith('#')) { + const ctx = document.createElement('canvas').getContext('2d'); + + if (!ctx) { + throw new Error('2d context not supported or canvas already initialized'); + } + + ctx.fillStyle = value; + + return ctx.fillStyle; + } else if (value.length === 4 || value.length === 5) { + value = value + .split('') + .map((v, i) => (i ? v + v : '#')) + .join(''); + + return value; + } else if (value.length === 7 || value.length === 9) { + return value; + } + + return '#000000'; +} + +function hex2rgb(hex) { + const rbgArr = ( + hex + .replace( + /^#?([a-f\d])([a-f\d])([a-f\d])$/i, + (m, r, g, b) => '#' + r + r + g + g + b + b, + ) + .substring(1) + .match(/.{2}/g) || [] + ).map((x) => parseInt(x, 16)); + + return { + b: rbgArr[2], + g: rbgArr[1], + r: rbgArr[0], + }; +} + +function rgb2hsv({r, g, b}) { + r /= 255; + g /= 255; + b /= 255; + + const max = Math.max(r, g, b); + const d = max - Math.min(r, g, b); + + const h = d + ? (max === r + ? (g - b) / d + (g < b ? 6 : 0) + : max === g + ? 2 + (b - r) / d + : 4 + (r - g) / d) * 60 + : 0; + const s = max ? (d / max) * 100 : 0; + const v = max * 100; + + return {h, s, v}; +} + +function hsv2rgb({h, s, v}) { + s /= 100; + v /= 100; + + const i = ~~(h / 60); + const f = h / 60 - i; + const p = v * (1 - s); + const q = v * (1 - s * f); + const t = v * (1 - s * (1 - f)); + const index = i % 6; + + const r = Math.round([v, q, p, p, t, v][index] * 255); + const g = Math.round([t, v, v, q, p, p][index] * 255); + const b = Math.round([p, p, t, v, v, q][index] * 255); + + return {b, g, r}; +} + +function rgb2hex({b, g, r}) { + return '#' + [r, g, b].map((x) => x.toString(16).padStart(2, '0')).join(''); +} + +function transformColor( + format, + color, +) { + let hex = toHex('#121212'); + let rgb = hex2rgb(hex); + let hsv = rgb2hsv(rgb); + + if (format === 'hex') { + const value = color; + + hex = toHex(value); + rgb = hex2rgb(hex); + hsv = rgb2hsv(rgb); + } else if (format === 'rgb') { + const value = color; + + rgb = value; + hex = rgb2hex(rgb); + hsv = rgb2hsv(rgb); + } else if (format === 'hsv') { + const value = color; + + hsv = value; + rgb = hsv2rgb(hsv); + hex = rgb2hex(rgb); + } + + return {hex, hsv, rgb}; +} diff --git a/src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.less b/src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.less new file mode 100644 index 0000000..d6e9b0e --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/Input/ColorPicker/index.less @@ -0,0 +1,88 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +.color-picker-wrapper { + padding: 20px; +} + +.color-picker-basic-color { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 0; + padding: 0; +} + +.color-picker-basic-color button { + border: 1px solid #ccc; + border-radius: 4px; + height: 16px; + width: 16px; + cursor: pointer; + list-style-type: none; +} + +.color-picker-basic-color button.active { + box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.3); +} + +.color-picker-saturation { + width: 100%; + position: relative; + margin-top: 15px; + height: 150px; + background-image: linear-gradient(transparent, black), + linear-gradient(to right, white, transparent); + user-select: none; +} +.color-picker-saturation_cursor { + position: absolute; + width: 20px; + height: 20px; + border: 2px solid #ffffff; + border-radius: 50%; + box-shadow: 0 0 15px #00000026; + box-sizing: border-box; + transform: translate(-10px, -10px); +} +.color-picker-hue { + width: 100%; + position: relative; + margin-top: 15px; + height: 12px; + background-image: linear-gradient( + to right, + rgb(255, 0, 0), + rgb(255, 255, 0), + rgb(0, 255, 0), + rgb(0, 255, 255), + rgb(0, 0, 255), + rgb(255, 0, 255), + rgb(255, 0, 0) + ); + user-select: none; + border-radius: 12px; +} + +.color-picker-hue_cursor { + position: absolute; + width: 20px; + height: 20px; + border: 2px solid #ffffff; + border-radius: 50%; + box-shadow: #0003 0 0 0 0.5px; + box-sizing: border-box; + transform: translate(-10px, -4px); +} + +.color-picker-color { + border: 1px solid #ccc; + margin-top: 15px; + width: 100%; + height: 20px; +} diff --git a/src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.jsx b/src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.jsx new file mode 100644 index 0000000..7700c94 --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.jsx @@ -0,0 +1,710 @@ +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import useLexicalEditable from '@lexical/react/useLexicalEditable'; +import "./index.less" +import { + $deleteTableColumn__EXPERIMENTAL, + $deleteTableRow__EXPERIMENTAL, + $getTableCellNodeFromLexicalNode, + $getTableColumnIndexFromTableCellNode, + $getTableNodeFromLexicalNodeOrThrow, + $getTableRowIndexFromTableCellNode, + $insertTableColumn__EXPERIMENTAL, + $insertTableRow__EXPERIMENTAL, + $isTableCellNode, + $isTableRowNode, + $unmergeCell, + getTableSelectionFromTableElement, + $isGridSelection, + TableCellHeaderStates, + TableCellNode, +} from '@lexical/table'; +import { + $createParagraphNode, + $getRoot, + $getSelection, + $isElementNode, + $isParagraphNode, + $isRangeSelection, + $isTextNode, + DEPRECATED_$getNodeTriplet, + DEPRECATED_$isGridCellNode, + +} from 'lexical'; +import * as React from 'react'; +import {ReactPortal, useCallback, useEffect, useRef, useState} from 'react'; +import {createPortal} from 'react-dom'; +import invariant from '../../context/shared/invariant'; + +import useModal from '../../hook/userModal'; +import ColorPicker from '../../plugins/Input/ColorPicker'; + +function computeSelectionCount(selection) { + const selectionShape = selection.getShape(); + return { + columns: selectionShape.toX - selectionShape.fromX + 1, + rows: selectionShape.toY - selectionShape.fromY + 1, + }; +} + +function isGridSelectionRectangular(selection) { + const nodes = selection.getNodes(); + const currentRows = []; + let currentRow = null; + let expectedColumns = null; + let currentColumns = 0; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if ($isTableCellNode(node)) { + const row = node.getParentOrThrow(); + invariant( + $isTableRowNode(row), + 'Expected CellNode to have a RowNode parent', + ); + if (currentRow !== row) { + if (expectedColumns !== null && currentColumns !== expectedColumns) { + return false; + } + if (currentRow !== null) { + expectedColumns = currentColumns; + } + currentRow = row; + currentColumns = 0; + } + const colSpan = node.__colSpan; + for (let j = 0; j < colSpan; j++) { + if (currentRows[currentColumns + j] === undefined) { + currentRows[currentColumns + j] = 0; + } + currentRows[currentColumns + j] += node.__rowSpan; + } + currentColumns += colSpan; + } + } + return ( + (expectedColumns === null || currentColumns === expectedColumns) && + currentRows.every((v) => v === currentRows[0]) + ); +} + +function $canUnmerge() { + const selection = $getSelection(); + if ( + ($isRangeSelection(selection) && !selection.isCollapsed()) || + ($isGridSelection(selection) && + !selection.anchor.is(selection.focus)) || + (!$isRangeSelection(selection) && !$isGridSelection(selection)) + ) { + return false; + } + const [cell] = DEPRECATED_$getNodeTriplet(selection.anchor); + return cell.__colSpan > 1 || cell.__rowSpan > 1; +} + +function $cellContainsEmptyParagraph(cell) { + if (cell.getChildrenSize() !== 1) { + return false; + } + const firstChild = cell.getFirstChildOrThrow(); + if (!$isParagraphNode(firstChild) || !firstChild.isEmpty()) { + return false; + } + return true; +} + +function $selectLastDescendant(node) { + const lastDescendant = node.getLastDescendant(); + if ($isTextNode(lastDescendant)) { + lastDescendant.select(); + } else if ($isElementNode(lastDescendant)) { + lastDescendant.selectEnd(); + } else if (lastDescendant !== null) { + lastDescendant.selectNext(); + } +} + +function currentCellBackgroundColor(editor) { + return editor.getEditorState().read(() => { + const selection = $getSelection(); + if ( + $isRangeSelection(selection) || $isGridSelection(selection) + ) { + const [cell] = DEPRECATED_$getNodeTriplet(selection.anchor); + if ($isTableCellNode(cell)) { + return cell.getBackgroundColor(); + } + } + return null; + }); +} + +function TableActionMenu({ + onClose, + tableCellNodeParam, + setIsMenuOpen, + contextRef, + cellMerge, + showColorPickerModal, +}) { + const [editor] = useLexicalComposerContext(); + const dropDownRef = useRef(null); + const [tableCellNode, updateTableCellNode] = useState(tableCellNodeParam); + const [selectionCounts, updateSelectionCounts] = useState({ + columns: 1, + rows: 1, + }); + const [canMergeCells, setCanMergeCells] = useState(false); + const [canUnmergeCell, setCanUnmergeCell] = useState(false); + const [backgroundColor, setBackgroundColor] = useState( + () => currentCellBackgroundColor(editor) || '', + ); + + useEffect(() => { + return editor.registerMutationListener(TableCellNode, (nodeMutations) => { + const nodeUpdated = + nodeMutations.get(tableCellNode.getKey()) === 'updated'; + + if (nodeUpdated) { + editor.getEditorState().read(() => { + updateTableCellNode(tableCellNode.getLatest()); + }); + setBackgroundColor(currentCellBackgroundColor(editor) || ''); + } + }); + }, [editor, tableCellNode]); + + useEffect(() => { + editor.getEditorState().read(() => { + const selection = $getSelection(); + if ($isGridSelection(selection)) { + const currentSelectionCounts = computeSelectionCount(selection); + updateSelectionCounts(computeSelectionCount(selection)); + setCanMergeCells( + isGridSelectionRectangular(selection) && + (currentSelectionCounts.columns > 1 || + currentSelectionCounts.rows > 1), + ); + } + setCanUnmergeCell($canUnmerge()); + }); + }, [editor]); + + useEffect(() => { + const menuButtonElement = contextRef.current; + const dropDownElement = dropDownRef.current; + const rootElement = editor.getRootElement(); + + if ( + menuButtonElement != null && + dropDownElement != null && + rootElement != null + ) { + const rootEleRect = rootElement.getBoundingClientRect(); + const menuButtonRect = menuButtonElement.getBoundingClientRect(); + dropDownElement.style.opacity = '1'; + const dropDownElementRect = dropDownElement.getBoundingClientRect(); + const margin = 5; + let leftPosition = menuButtonRect.right + margin; + if ( + leftPosition + dropDownElementRect.width > window.innerWidth || + leftPosition + dropDownElementRect.width > rootEleRect.right + ) { + const position = + menuButtonRect.left - dropDownElementRect.width - margin; + leftPosition = (position < 0 ? margin : position) + window.pageXOffset; + } + dropDownElement.style.left = `${leftPosition + window.pageXOffset}px`; + + let topPosition = menuButtonRect.top; + if (topPosition + dropDownElementRect.height > window.innerHeight) { + const position = menuButtonRect.bottom - dropDownElementRect.height; + topPosition = (position < 0 ? margin : position) + window.pageYOffset; + } + dropDownElement.style.top = `${topPosition + +window.pageYOffset}px`; + } + }, [contextRef, dropDownRef, editor]); + + useEffect(() => { + function handleClickOutside(event) { + if ( + dropDownRef.current != null && + contextRef.current != null && + !dropDownRef.current.contains(event.target) && + !contextRef.current.contains(event.target) + ) { + setIsMenuOpen(false); + } + } + + window.addEventListener('click', handleClickOutside); + + return () => window.removeEventListener('click', handleClickOutside); + }, [setIsMenuOpen, contextRef]); + + const clearTableSelection = useCallback(() => { + editor.update(() => { + if (tableCellNode.isAttached()) { + const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); + const tableElement = editor.getElementByKey( + tableNode.getKey(), + ); + + if (!tableElement) { + throw new Error('Expected to find tableElement in DOM'); + } + + const tableSelection = getTableSelectionFromTableElement(tableElement); + if (tableSelection !== null) { + tableSelection.clearHighlight(); + } + + tableNode.markDirty(); + updateTableCellNode(tableCellNode.getLatest()); + } + + const rootNode = $getRoot(); + rootNode.selectStart(); + }); + }, [editor, tableCellNode]); + + const mergeTableCellsAtSelection = () => { + editor.update(() => { + const selection = $getSelection(); + if ($isGridSelection(selection)) { + const {columns, rows} = computeSelectionCount(selection); + const nodes = selection.getNodes(); + let firstCell = null; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (DEPRECATED_$isGridCellNode(node)) { + if (firstCell === null) { + node.setColSpan(columns).setRowSpan(rows); + firstCell = node; + const isEmpty = $cellContainsEmptyParagraph(node); + let firstChild; + if ( + isEmpty && + $isParagraphNode((firstChild = node.getFirstChild())) + ) { + firstChild.remove(); + } + } else if (DEPRECATED_$isGridCellNode(firstCell)) { + const isEmpty = $cellContainsEmptyParagraph(node); + if (!isEmpty) { + firstCell.append(...node.getChildren()); + } + node.remove(); + } + } + } + if (firstCell !== null) { + if (firstCell.getChildrenSize() === 0) { + firstCell.append($createParagraphNode()); + } + $selectLastDescendant(firstCell); + } + onClose(); + } + }); + }; + + const unmergeTableCellsAtSelection = () => { + editor.update(() => { + $unmergeCell(); + }); + }; + + const insertTableRowAtSelection = useCallback( + (shouldInsertAfter) => { + editor.update(() => { + $insertTableRow__EXPERIMENTAL(shouldInsertAfter); + onClose(); + }); + }, + [editor, onClose], + ); + + const insertTableColumnAtSelection = useCallback( + (shouldInsertAfter) => { + editor.update(() => { + for (let i = 0; i < selectionCounts.columns; i++) { + $insertTableColumn__EXPERIMENTAL(shouldInsertAfter); + } + onClose(); + }); + }, + [editor, onClose, selectionCounts.columns], + ); + + const deleteTableRowAtSelection = useCallback(() => { + editor.update(() => { + $deleteTableRow__EXPERIMENTAL(); + onClose(); + }); + }, [editor, onClose]); + + const deleteTableAtSelection = useCallback(() => { + editor.update(() => { + const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); + tableNode.remove(); + + clearTableSelection(); + onClose(); + }); + }, [editor, tableCellNode, clearTableSelection, onClose]); + + const deleteTableColumnAtSelection = useCallback(() => { + editor.update(() => { + $deleteTableColumn__EXPERIMENTAL(); + onClose(); + }); + }, [editor, onClose]); + + const toggleTableRowIsHeader = useCallback(() => { + editor.update(() => { + const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); + + const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode); + + const tableRows = tableNode.getChildren(); + + if (tableRowIndex >= tableRows.length || tableRowIndex < 0) { + throw new Error('Expected table cell to be inside of table row.'); + } + + const tableRow = tableRows[tableRowIndex]; + + if (!$isTableRowNode(tableRow)) { + throw new Error('Expected table row'); + } + + tableRow.getChildren().forEach((tableCell) => { + if (!$isTableCellNode(tableCell)) { + throw new Error('Expected table cell'); + } + + tableCell.toggleHeaderStyle(TableCellHeaderStates.ROW); + }); + + clearTableSelection(); + onClose(); + }); + }, [editor, tableCellNode, clearTableSelection, onClose]); + + const toggleTableColumnIsHeader = useCallback(() => { + editor.update(() => { + const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); + + const tableColumnIndex = + $getTableColumnIndexFromTableCellNode(tableCellNode); + + const tableRows = tableNode.getChildren(); + + for (let r = 0; r < tableRows.length; r++) { + const tableRow = tableRows[r]; + + if (!$isTableRowNode(tableRow)) { + throw new Error('Expected table row'); + } + + const tableCells = tableRow.getChildren(); + + if (tableColumnIndex >= tableCells.length || tableColumnIndex < 0) { + throw new Error('Expected table cell to be inside of table row.'); + } + + const tableCell = tableCells[tableColumnIndex]; + + if (!$isTableCellNode(tableCell)) { + throw new Error('Expected table cell'); + } + + tableCell.toggleHeaderStyle(TableCellHeaderStates.COLUMN); + } + + clearTableSelection(); + onClose(); + }); + }, [editor, tableCellNode, clearTableSelection, onClose]); + + const handleCellBackgroundColor = useCallback( + (value) => { + editor.update(() => { + const selection = $getSelection(); + if ( + $isRangeSelection(selection) || $isGridSelection(selection) + ) { + const [cell] = DEPRECATED_$getNodeTriplet(selection.anchor); + if ($isTableCellNode(cell)) { + cell.setBackgroundColor(value); + } + } + }); + }, + [editor], + ); + + let mergeCellButton = null; + if (cellMerge) { + if (canMergeCells) { + mergeCellButton = ( + + ); + } else if (canUnmergeCell) { + mergeCellButton = ( + + ); + } + } + + return createPortal( +
{ + e.stopPropagation(); + }}> + {mergeCellButton} + +
+ + +
+ + +
+ + + +
+ + +
, + document.body, + ); +} + +function TableCellActionMenuContainer({ + anchorElem, + cellMerge, +}) { + const [editor] = useLexicalComposerContext(); + + const menuButtonRef = useRef(null); + const menuRootRef = useRef(null); + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const [tableCellNode, setTableMenuCellNode] = useState(null,); + + const [colorPickerModal, showColorPickerModal] = useModal(); + + const moveMenu = useCallback(() => { + const menu = menuButtonRef.current; + const selection = $getSelection(); + const nativeSelection = window.getSelection(); + const activeElement = document.activeElement; + + if (selection == null || menu == null) { + setTableMenuCellNode(null); + return; + } + + const rootElement = editor.getRootElement(); + + if ( + $isRangeSelection(selection) && + rootElement !== null && + nativeSelection !== null && + rootElement.contains(nativeSelection.anchorNode) + ) { + const tableCellNodeFromSelection = $getTableCellNodeFromLexicalNode( + selection.anchor.getNode(), + ); + + if (tableCellNodeFromSelection == null) { + setTableMenuCellNode(null); + return; + } + + const tableCellParentNodeDOM = editor.getElementByKey( + tableCellNodeFromSelection.getKey(), + ); + + if (tableCellParentNodeDOM == null) { + setTableMenuCellNode(null); + return; + } + + setTableMenuCellNode(tableCellNodeFromSelection); + } else if (!activeElement) { + setTableMenuCellNode(null); + } + }, [editor]); + + useEffect(() => { + return editor.registerUpdateListener(() => { + editor.getEditorState().read(() => { + moveMenu(); + }); + }); + }); + + useEffect(() => { + const menuButtonDOM = menuButtonRef.current; + + if (menuButtonDOM != null && tableCellNode != null) { + const tableCellNodeDOM = editor.getElementByKey(tableCellNode.getKey()); + + if (tableCellNodeDOM != null) { + const tableCellRect = tableCellNodeDOM.getBoundingClientRect(); + const menuRect = menuButtonDOM.getBoundingClientRect(); + const anchorRect = anchorElem.getBoundingClientRect(); + + const top = tableCellRect.top - anchorRect.top + 4; + const left = + tableCellRect.right - menuRect.width - 10 - anchorRect.left; + + menuButtonDOM.style.opacity = '1'; + menuButtonDOM.style.transform = `translate(${left}px, ${top}px)`; + } else { + menuButtonDOM.style.opacity = '0'; + menuButtonDOM.style.transform = 'translate(-10000px, -10000px)'; + } + } + }, [menuButtonRef, tableCellNode, editor, anchorElem]); + + const prevTableCellDOM = useRef(tableCellNode); + + useEffect(() => { + if (prevTableCellDOM.current !== tableCellNode) { + setIsMenuOpen(false); + } + + prevTableCellDOM.current = tableCellNode; + }, [prevTableCellDOM, tableCellNode]); + + return ( +
+ {tableCellNode != null && ( + <> + + {colorPickerModal} + {isMenuOpen && ( + setIsMenuOpen(false)} + tableCellNodeParam={tableCellNode} + cellMerge={cellMerge} + showColorPickerModal={showColorPickerModal} + /> + )} + + )} +
+ ); +} + +export default function TableActionMenuPlugin({ + anchorElem = document.body, + cellMerge = false, +}){ + const isEditable = useLexicalEditable(); + return createPortal( + isEditable ? ( + + ) : null, + anchorElem, + ); +} diff --git a/src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.less b/src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.less new file mode 100644 index 0000000..7782316 --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/TableActionMenuPlugin/index.less @@ -0,0 +1,6 @@ +.table-cell-action-button-container { + position: absolute; + top: 0; + left: 0; + will-change: transform; +} diff --git a/src/pages/Note/Hlexical/plugins/TableCellResizer/index.css b/src/pages/Note/Hlexical/plugins/TableCellResizer/index.css new file mode 100644 index 0000000..a238b9a --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/TableCellResizer/index.css @@ -0,0 +1,3 @@ +.TableCellResizer__resizer { + position: absolute; +} diff --git a/src/pages/Note/Hlexical/plugins/TableCellResizer/index.jsx b/src/pages/Note/Hlexical/plugins/TableCellResizer/index.jsx new file mode 100644 index 0000000..518b2fa --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/TableCellResizer/index.jsx @@ -0,0 +1,407 @@ +import './index.css'; + +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import useLexicalEditable from '@lexical/react/useLexicalEditable'; +import { + $getTableColumnIndexFromTableCellNode, + $getTableNodeFromLexicalNodeOrThrow, + $getTableRowIndexFromTableCellNode, + $isTableCellNode, + $isTableRowNode, + getCellFromTarget, +} from '@lexical/table'; +import { + $getNearestNodeFromDOMNode, + $getSelection, + COMMAND_PRIORITY_HIGH, + DEPRECATED_$isGridSelection, + SELECTION_CHANGE_COMMAND, +} from 'lexical'; +import * as React from 'react'; +import { + MouseEventHandler, + ReactPortal, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import {createPortal} from 'react-dom'; + + +const MIN_ROW_HEIGHT = 33; +const MIN_COLUMN_WIDTH = 50; + +function TableCellResizer({editor}){ + const targetRef = useRef(null); + const resizerRef = useRef(null); + const tableRectRef = useRef(null); + + const mouseStartPosRef = useRef(null); + const [mouseCurrentPos, updateMouseCurrentPos] = + useState(null); + + const [activeCell, updateActiveCell] = useState(null); + const [isSelectingGrid, updateIsSelectingGrid] = useState(false); + const [draggingDirection, updateDraggingDirection] = + useState(null); + + useEffect(() => { + return editor.registerCommand( + SELECTION_CHANGE_COMMAND, + (payload) => { + const selection = $getSelection(); + const isGridSelection = DEPRECATED_$isGridSelection(selection); + + if (isSelectingGrid !== isGridSelection) { + updateIsSelectingGrid(isGridSelection); + } + + return false; + }, + COMMAND_PRIORITY_HIGH, + ); + }); + + const resetState = useCallback(() => { + updateActiveCell(null); + targetRef.current = null; + updateDraggingDirection(null); + mouseStartPosRef.current = null; + tableRectRef.current = null; + }, []); + + useEffect(() => { + const onMouseMove = (event) => { + setTimeout(() => { + const target = event.target; + + if (draggingDirection) { + updateMouseCurrentPos({ + x: event.clientX, + y: event.clientY, + }); + return; + } + + if (resizerRef.current && resizerRef.current.contains(target)) { + return; + } + + if (targetRef.current !== target) { + targetRef.current = target; + const cell = getCellFromTarget(target); + + if (cell && activeCell !== cell) { + editor.update(() => { + const tableCellNode = $getNearestNodeFromDOMNode(cell.elem); + if (!tableCellNode) { + throw new Error('TableCellResizer: Table cell node not found.'); + } + + const tableNode = + $getTableNodeFromLexicalNodeOrThrow(tableCellNode); + const tableElement = editor.getElementByKey(tableNode.getKey()); + + if (!tableElement) { + throw new Error('TableCellResizer: Table element not found.'); + } + + targetRef.current = target; + tableRectRef.current = tableElement.getBoundingClientRect(); + updateActiveCell(cell); + }); + } else if (cell == null) { + resetState(); + } + } + }, 0); + }; + + document.addEventListener('mousemove', onMouseMove); + + return () => { + document.removeEventListener('mousemove', onMouseMove); + }; + }, [activeCell, draggingDirection, editor, resetState]); + + const isHeightChanging = (direction) => { + if (direction === 'bottom') return true; + return false; + }; + + const updateRowHeight = useCallback( + (newHeight) => { + if (!activeCell) { + throw new Error('TableCellResizer: Expected active cell.'); + } + + editor.update(() => { + const tableCellNode = $getNearestNodeFromDOMNode(activeCell.elem); + if (!$isTableCellNode(tableCellNode)) { + throw new Error('TableCellResizer: Table cell node not found.'); + } + + const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); + + const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode); + + const tableRows = tableNode.getChildren(); + + if (tableRowIndex >= tableRows.length || tableRowIndex < 0) { + throw new Error('Expected table cell to be inside of table row.'); + } + + const tableRow = tableRows[tableRowIndex]; + + if (!$isTableRowNode(tableRow)) { + throw new Error('Expected table row'); + } + + tableRow.setHeight(newHeight); + }); + }, + [activeCell, editor], + ); + + const updateColumnWidth = useCallback( + (newWidth) => { + if (!activeCell) { + throw new Error('TableCellResizer: Expected active cell.'); + } + editor.update(() => { + const tableCellNode = $getNearestNodeFromDOMNode(activeCell.elem); + if (!$isTableCellNode(tableCellNode)) { + throw new Error('TableCellResizer: Table cell node not found.'); + } + + const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); + + const tableColumnIndex = + $getTableColumnIndexFromTableCellNode(tableCellNode); + + const tableRows = tableNode.getChildren(); + + for (let r = 0; r < tableRows.length; r++) { + const tableRow = tableRows[r]; + + if (!$isTableRowNode(tableRow)) { + throw new Error('Expected table row'); + } + + const rowCells = tableRow.getChildren(); + const rowCellsSpan = rowCells.map((cell) => cell.getColSpan()); + + const aggregatedRowSpans = rowCellsSpan.reduce( + (rowSpans, cellSpan) => { + const previousCell = rowSpans[rowSpans.length - 1] ?? 0; + rowSpans.push(previousCell + cellSpan); + return rowSpans; + }, + [], + ); + const rowColumnIndexWithSpan = aggregatedRowSpans.findIndex( + (cellSpan) => cellSpan > tableColumnIndex, + ); + + if ( + rowColumnIndexWithSpan >= rowCells.length || + rowColumnIndexWithSpan < 0 + ) { + throw new Error('Expected table cell to be inside of table row.'); + } + + const tableCell = rowCells[rowColumnIndexWithSpan]; + + if (!$isTableCellNode(tableCell)) { + throw new Error('Expected table cell'); + } + + tableCell.setWidth(newWidth); + } + }); + }, + [activeCell, editor], + ); + + const mouseUpHandler = useCallback( + (direction) => { + const handler = (event) => { + event.preventDefault(); + event.stopPropagation(); + + if (!activeCell) { + throw new Error('TableCellResizer: Expected active cell.'); + } + + if (mouseStartPosRef.current) { + const {x, y} = mouseStartPosRef.current; + + if (activeCell === null) { + return; + } + + if (isHeightChanging(direction)) { + const height = activeCell.elem.getBoundingClientRect().height; + const heightChange = Math.abs(event.clientY - y); + + const isShrinking = direction === 'bottom' && y > event.clientY; + + updateRowHeight( + Math.max( + isShrinking ? height - heightChange : heightChange + height, + MIN_ROW_HEIGHT, + ), + ); + } else { + const computedStyle = getComputedStyle(activeCell.elem); + let width = activeCell.elem.clientWidth; // width with padding + width -= + parseFloat(computedStyle.paddingLeft) + + parseFloat(computedStyle.paddingRight); + const widthChange = Math.abs(event.clientX - x); + + const isShrinking = direction === 'right' && x > event.clientX; + + updateColumnWidth( + Math.max( + isShrinking ? width - widthChange : widthChange + width, + MIN_COLUMN_WIDTH, + ), + ); + } + + resetState(); + document.removeEventListener('mouseup', handler); + } + }; + return handler; + }, + [activeCell, resetState, updateColumnWidth, updateRowHeight], + ); + + const toggleResize = useCallback( + (direction) => + (event) => { + event.preventDefault(); + event.stopPropagation(); + + if (!activeCell) { + throw new Error('TableCellResizer: Expected active cell.'); + } + + mouseStartPosRef.current = { + x: event.clientX, + y: event.clientY, + }; + updateMouseCurrentPos(mouseStartPosRef.current); + updateDraggingDirection(direction); + + document.addEventListener('mouseup', mouseUpHandler(direction)); + }, + [ + activeCell, + draggingDirection, + resetState, + updateColumnWidth, + updateRowHeight, + mouseUpHandler, + ], + ); + + const getResizers = useCallback(() => { + if (activeCell) { + const {height, width, top, left} = + activeCell.elem.getBoundingClientRect(); + + const styles = { + bottom: { + backgroundColor: 'none', + cursor: 'row-resize', + height: '10px', + left: `${window.pageXOffset + left}px`, + top: `${window.pageYOffset + top + height}px`, + width: `${width}px`, + }, + right: { + backgroundColor: 'none', + cursor: 'col-resize', + height: `${height}px`, + left: `${window.pageXOffset + left + width}px`, + top: `${window.pageYOffset + top}px`, + width: '10px', + }, + }; + + const tableRect = tableRectRef.current; + + if (draggingDirection && mouseCurrentPos && tableRect) { + if (isHeightChanging(draggingDirection)) { + styles[draggingDirection].left = `${ + window.pageXOffset + tableRect.left + }px`; + styles[draggingDirection].top = `${ + window.pageYOffset + mouseCurrentPos.y + }px`; + styles[draggingDirection].height = '3px'; + styles[draggingDirection].width = `${tableRect.width}px`; + } else { + styles[draggingDirection].top = `${ + window.pageYOffset + tableRect.top + }px`; + styles[draggingDirection].left = `${ + window.pageXOffset + mouseCurrentPos.x + }px`; + styles[draggingDirection].width = '3px'; + styles[draggingDirection].height = `${tableRect.height}px`; + } + + styles[draggingDirection].backgroundColor = '#adf'; + } + + return styles; + } + + return { + bottom: null, + left: null, + right: null, + top: null, + }; + }, [activeCell, draggingDirection, mouseCurrentPos]); + + const resizerStyles = getResizers(); + + return ( +
+ {activeCell != null && !isSelectingGrid && ( + <> +
+
+ + )} +
+ ); +} + +export default function TableCellResizerPlugin(){ + const [editor] = useLexicalComposerContext(); + const isEditable = useLexicalEditable(); + + return useMemo( + () => + isEditable + ? createPortal(, document.body) + : null, + [editor, isEditable], + ); +} diff --git a/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.css b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.css new file mode 100644 index 0000000..ecf6672 --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.css @@ -0,0 +1,87 @@ +.table-of-contents .heading2 { + margin-left: 10px; +} + +.table-of-contents .heading3 { + margin-left: 20px; +} + +.selected-heading { + color: #3578e5; + position: relative; +} + +.selected-heading-wrapper::before { + content: ' '; + position: absolute; + display: inline-block; + left: -30px; + top: 4px; + z-index: 10; + height: 4px; + width: 4px; + background-color: #3578e5; + border: solid 4px white; + border-radius: 50%; +} + +.normal-heading { + cursor: pointer; + line-height: 20px; + font-size: 16px; +} + +.table-of-contents { + color: #65676b; + position: fixed; + top: 200px; + right: -35px; + padding: 10px; + width: 250px; + display: flex; + flex-direction: row; + justify-content: flex-start; + z-index: 1; + height: 300px; +} + +.first-heading { + color: black; + font-weight: bold; + cursor: pointer; +} + +.headings { + list-style: none; + margin-top: 0; + margin-left: 10px; + padding: 0; + overflow: scroll; + width: 200px; + height: 220px; + overflow-x: hidden; + overflow-y: auto; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +/* Hide scrollbar for Chrome, Safari and Opera */ +.headings::-webkit-scrollbar { + display: none; +} + +.headings::before { + content: ' '; + position: absolute; + height: 220px; + width: 4px; + right: 240px; + margin-top: 5px; + background-color: #ccd0d5; + border-radius: 2px; +} + +.normal-heading-wrapper { + margin-left: 32px; + position: relative; +} diff --git a/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx new file mode 100644 index 0000000..56fbbf7 --- /dev/null +++ b/src/pages/Note/Hlexical/plugins/TableOfContentsPlugin/index.tsx @@ -0,0 +1,197 @@ +/** + * 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/TablePlugin/index.jsx b/src/pages/Note/Hlexical/plugins/TablePlugin/index.jsx index 25dbd96..fa20b24 100644 --- a/src/pages/Note/Hlexical/plugins/TablePlugin/index.jsx +++ b/src/pages/Note/Hlexical/plugins/TablePlugin/index.jsx @@ -165,7 +165,7 @@ export function TablePlugin({ cellContext.set(cellEditorConfig, children); return editor.registerCommand( - INSERT_TABLE_COMMAND, + INSERT_NEW_TABLE_COMMAND, ({columns, rows, includeHeaders}) => { const tableNode = $createTableNodeWithDimensions( Number(rows), diff --git a/src/pages/Note/Hlexical/themes/FirstTheme.js b/src/pages/Note/Hlexical/themes/FirstTheme.js index 77628cf..658075d 100644 --- a/src/pages/Note/Hlexical/themes/FirstTheme.js +++ b/src/pages/Note/Hlexical/themes/FirstTheme.js @@ -31,6 +31,22 @@ const firstTheme = { underlineStrikethrough: "editor-text-underlineStrikethrough", code: "editor-text-code" }, + table: 'editor-table', + tableAddColumns: 'editor-tableAddColumns', + tableAddRows: 'editor-tableAddRows', + tableCell: 'editor-tableCell', + tableCellActionButton: 'editor-tableCellActionButton', + tableCellActionButtonContainer: + 'editor-tableCellActionButtonContainer', + tableCellEditing: 'editor-tableCellEditing', + tableCellHeader: 'editor-tableCellHeader', + tableCellPrimarySelected: 'editor-tableCellPrimarySelected', + tableCellResizer: 'editor-tableCellResizer', + tableCellSelected: 'editor-tableCellSelected', + tableCellSortedIndicator: 'editor-tableCellSortedIndicator', + tableResizeRuler: 'editor-tableCellResizeRuler', + tableSelected: 'editor-tableSelected', + tableSelection: 'editor-tableSelection', code: "editor-code", codeHighlight: { atrule: "editor-tokenAttr", @@ -67,4 +83,3 @@ const firstTheme = { }; export default firstTheme; - \ No newline at end of file