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