From 37fad9f1d11bb4be6463ab51ae7d3405a02368d0 Mon Sep 17 00:00:00 2001 From: tdd28 Date: Sun, 17 Nov 2024 20:23:33 +0700 Subject: [PATCH 1/3] chores: basic editor --- package.json | 2 + pnpm-lock.yaml | 281 ++++++++++++++++++++++++++++++++++++++ src/components/Editor.tsx | 49 +++++++ 3 files changed, 332 insertions(+) create mode 100644 src/components/Editor.tsx diff --git a/package.json b/package.json index 9c0b6c2..c90f406 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "preview": "rsbuild preview" }, "dependencies": { + "@lexical/react": "^0.20.0", + "lexical": "^0.20.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.28.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d299ff9..b0432db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@lexical/react': + specifier: ^0.20.0 + version: 0.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.20) + lexical: + specifier: ^0.20.0 + version: 0.20.0 react: specifier: ^18.3.1 version: 18.3.1 @@ -73,6 +79,10 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -158,6 +168,77 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@lexical/clipboard@0.20.0': + resolution: {integrity: sha512-oHmb9kSVHjeFCd2q8VrEXW22doUHMJ6cGXqo7Ican7Ljl4/9OgRWr+cq55yntoSaJfCrRYkTiZCLDejF2ciSiA==} + + '@lexical/code@0.20.0': + resolution: {integrity: sha512-zFsVGuzIn4CQxEnlW4AG/Hq6cyATVZ4fZTxozE/f5oK4vDPvnY/goRxrzSuAMX73A/HRX3kTEzMDcm4taRM3Mg==} + + '@lexical/devtools-core@0.20.0': + resolution: {integrity: sha512-/CnL+Dfpzw4koy2BTdUICkvrCkMIYG8Y73KB/S1Bt5UzJpD+PV300puWJ0NvUvAj24H78r73jxvK2QUG67Tdaw==} + peerDependencies: + react: '>=17.x' + react-dom: '>=17.x' + + '@lexical/dragon@0.20.0': + resolution: {integrity: sha512-3DAHF8mSKiPZtXCqu2P8ynSwS3fGXzg4G/V0lXNjBxhmozjzUzWZRWIWtmTlWdEu9GXsoyeM3agcaxyDPJJwkA==} + + '@lexical/hashtag@0.20.0': + resolution: {integrity: sha512-ldOP/d9tA6V9qvLyr3mRYkcYY5ySOHJ2BFOW/jZPxQcj6lWafS8Lk7XdMUpHHDjRpY2Hizsi5MHJkIqFglYXbw==} + + '@lexical/history@0.20.0': + resolution: {integrity: sha512-dXtIS31BU6RmLX2KwLAi1EgGl+USeyi+rshh19azACXHPFqONZgPd2t21LOLSFn7C1/W+cSp/kqVDlQVbZUZRA==} + + '@lexical/html@0.20.0': + resolution: {integrity: sha512-ob7QHkEv+mhaZjlurDj90UmEyN9G4rzBPR5QV42PLnu1qMSviMEdI5V3a5/A5aFf/FDDQ+0GAgWBFnA/MEDczQ==} + + '@lexical/link@0.20.0': + resolution: {integrity: sha512-zicDcfgRZPRFZ8WOZv5er0Aqkde+i7QoFVkLQD4dNLLORjoMSJOISJH6VEdjBl3k7QJTxbfrt+xT5d/ZsAN5GA==} + + '@lexical/list@0.20.0': + resolution: {integrity: sha512-ufSse8ui3ooUe0HA/yF/9STrG8wYhIDLMRhELOw80GFCkPJaxs6yRvjfmJooH5IC88rpUJ5XXFFiZKfGxEZLEw==} + + '@lexical/mark@0.20.0': + resolution: {integrity: sha512-1P2izmkgZ4VDp+49rWO1KfWivL5aA30y5kkYbFZ/CS05fgbO7ogMjLSajpz+RN/zzW79v3q4YfikrMgaD23InA==} + + '@lexical/markdown@0.20.0': + resolution: {integrity: sha512-ZoGsECejp9z6MEvc8l81b1h1aWbB3sTq6xOFeUTbDL5vKpA67z5CmQQLi0uZWrygrbO9dSE3Q/JGcodUrczxbw==} + + '@lexical/offset@0.20.0': + resolution: {integrity: sha512-VMhxsxxDGnpVw0jgC8UlDf0Q2RHIHbS49uZgs3l9nP+O+G8s3b76Ta4Tb+iJOK2FY6874/TcQMbSuXGhfpQk8A==} + + '@lexical/overflow@0.20.0': + resolution: {integrity: sha512-z4lElzLm1FVifc7bzBZN4VNKeTuwygpyHQvCJVWXzF2Kbvex43PEYMi8u4A83idVqbmzbyBLASwUJS0voLoPLw==} + + '@lexical/plain-text@0.20.0': + resolution: {integrity: sha512-LvoC+9mm2Im1iO8GgtgaqSfW0T3mIE5GQl1xGxbVNdANmtHmBgRAJn2KfQm1XHZP6zydLRMhZkzC+jfInh2yfQ==} + + '@lexical/react@0.20.0': + resolution: {integrity: sha512-5QbN5AFtZ9efXxU/M01ADhUZgthR0e8WKi5K/w5EPpWtYFDPQnUte3rKUjYJ7uwG1iwcvaCpuMbxJjHQ+i6pDQ==} + peerDependencies: + react: '>=17.x' + react-dom: '>=17.x' + + '@lexical/rich-text@0.20.0': + resolution: {integrity: sha512-BR1pACdMA+Ymef0f5EN1y+9yP8w7S+9MgmBP1yjr3w4KdqRnfSaGWyxwcHU8eA+zu16QfivpB6501VJ90YeuXw==} + + '@lexical/selection@0.20.0': + resolution: {integrity: sha512-YnkH5UCMNN/em95or/6uwAV31vcENh1Roj+JOg5KD+gJuA7VGdDCy0vZl/o0+1badXozeZ2VRxXNC6JSK7T4+A==} + + '@lexical/table@0.20.0': + resolution: {integrity: sha512-qHuK2rvQUoQDx62YpvJE3Ev4yK9kjRFo79IDBapxrhoXg/wCGQOjMBzVD3G5PWkhyl/GDnww80GwYjLloQLQzg==} + + '@lexical/text@0.20.0': + resolution: {integrity: sha512-Fu64i5CIlEOlgucSdp9XFqB2XqoRsw4at76n93+6RF4+LgGDnu4nLXQVCVxNmLcGyh2WgczuTpnk5P2mHNAIUA==} + + '@lexical/utils@0.20.0': + resolution: {integrity: sha512-sXIa2nowrNxY8VcjjuxZbJ/HovIql8bmInNaxBR03JAYfqMiL5I5/dYgjOQJV49NJnuR1uTY2GwVxVTXCTFUCw==} + + '@lexical/yjs@0.20.0': + resolution: {integrity: sha512-TiHNhu2VkhXN69V+fXVS3xjOQ6aLnheQUGwOAhuFkDPL3VLCb0yl2Mgydpayn+3Grwii4ZBHcF7oCC84GiU5bw==} + peerDependencies: + yjs: '>=13.5.22' + '@module-federation/runtime-tools@0.5.1': resolution: {integrity: sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==} @@ -903,6 +984,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + iterator.prototype@1.1.3: resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} engines: {node: '>= 0.4'} @@ -941,6 +1025,14 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lexical@0.20.0: + resolution: {integrity: sha512-lJEHLFACXqRf3u/VlIOu9T7MJ51O4la92uOBwiS9Sx+juDK3Nrru5Vgl1aUirV1qK8XEM3h6Org2HcrsrzZ3ZA==} + + lib0@0.2.98: + resolution: {integrity: sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==} + engines: {node: '>=16'} + hasBin: true + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -1145,6 +1237,10 @@ packages: engines: {node: '>=14'} hasBin: true + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -1160,6 +1256,12 @@ packages: peerDependencies: react: ^18.3.1 + react-error-boundary@3.1.4: + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -1195,6 +1297,9 @@ packages: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regexp.prototype.flags@1.5.3: resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} @@ -1434,6 +1539,10 @@ packages: engines: {node: '>= 14'} hasBin: true + yjs@13.6.20: + resolution: {integrity: sha512-Z2YZI+SYqK7XdWlloI3lhMiKnCdFCVC4PchpdO+mCYwtiTwncjUbnRK9R1JmkNfdmHyDXuWN3ibJAt0wsqTbLQ==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1442,6 +1551,10 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.1 + '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0(jiti@1.21.6))': dependencies: eslint: 9.15.0(jiti@1.21.6) @@ -1524,6 +1637,153 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@lexical/clipboard@0.20.0': + dependencies: + '@lexical/html': 0.20.0 + '@lexical/list': 0.20.0 + '@lexical/selection': 0.20.0 + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/code@0.20.0': + dependencies: + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + prismjs: 1.29.0 + + '@lexical/devtools-core@0.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@lexical/html': 0.20.0 + '@lexical/link': 0.20.0 + '@lexical/mark': 0.20.0 + '@lexical/table': 0.20.0 + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@lexical/dragon@0.20.0': + dependencies: + lexical: 0.20.0 + + '@lexical/hashtag@0.20.0': + dependencies: + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/history@0.20.0': + dependencies: + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/html@0.20.0': + dependencies: + '@lexical/selection': 0.20.0 + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/link@0.20.0': + dependencies: + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/list@0.20.0': + dependencies: + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/mark@0.20.0': + dependencies: + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/markdown@0.20.0': + dependencies: + '@lexical/code': 0.20.0 + '@lexical/link': 0.20.0 + '@lexical/list': 0.20.0 + '@lexical/rich-text': 0.20.0 + '@lexical/text': 0.20.0 + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/offset@0.20.0': + dependencies: + lexical: 0.20.0 + + '@lexical/overflow@0.20.0': + dependencies: + lexical: 0.20.0 + + '@lexical/plain-text@0.20.0': + dependencies: + '@lexical/clipboard': 0.20.0 + '@lexical/selection': 0.20.0 + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/react@0.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.20)': + dependencies: + '@lexical/clipboard': 0.20.0 + '@lexical/code': 0.20.0 + '@lexical/devtools-core': 0.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@lexical/dragon': 0.20.0 + '@lexical/hashtag': 0.20.0 + '@lexical/history': 0.20.0 + '@lexical/link': 0.20.0 + '@lexical/list': 0.20.0 + '@lexical/mark': 0.20.0 + '@lexical/markdown': 0.20.0 + '@lexical/overflow': 0.20.0 + '@lexical/plain-text': 0.20.0 + '@lexical/rich-text': 0.20.0 + '@lexical/selection': 0.20.0 + '@lexical/table': 0.20.0 + '@lexical/text': 0.20.0 + '@lexical/utils': 0.20.0 + '@lexical/yjs': 0.20.0(yjs@13.6.20) + lexical: 0.20.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-error-boundary: 3.1.4(react@18.3.1) + transitivePeerDependencies: + - yjs + + '@lexical/rich-text@0.20.0': + dependencies: + '@lexical/clipboard': 0.20.0 + '@lexical/selection': 0.20.0 + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/selection@0.20.0': + dependencies: + lexical: 0.20.0 + + '@lexical/table@0.20.0': + dependencies: + '@lexical/clipboard': 0.20.0 + '@lexical/utils': 0.20.0 + lexical: 0.20.0 + + '@lexical/text@0.20.0': + dependencies: + lexical: 0.20.0 + + '@lexical/utils@0.20.0': + dependencies: + '@lexical/list': 0.20.0 + '@lexical/selection': 0.20.0 + '@lexical/table': 0.20.0 + lexical: 0.20.0 + + '@lexical/yjs@0.20.0(yjs@13.6.20)': + dependencies: + '@lexical/offset': 0.20.0 + '@lexical/selection': 0.20.0 + lexical: 0.20.0 + yjs: 13.6.20 + '@module-federation/runtime-tools@0.5.1': dependencies: '@module-federation/runtime': 0.5.1 @@ -2401,6 +2661,8 @@ snapshots: isexe@2.0.0: {} + isomorphic.js@0.2.5: {} + iterator.prototype@1.1.3: dependencies: define-properties: 1.2.1 @@ -2445,6 +2707,12 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lexical@0.20.0: {} + + lib0@0.2.98: + dependencies: + isomorphic.js: 0.2.5 + lilconfig@2.1.0: {} lilconfig@3.1.2: {} @@ -2617,6 +2885,8 @@ snapshots: prettier@3.3.3: {} + prismjs@1.29.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -2633,6 +2903,11 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-error-boundary@3.1.4(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + react: 18.3.1 + react-is@16.13.1: {} react-refresh@0.14.2: {} @@ -2671,6 +2946,8 @@ snapshots: globalthis: 1.0.4 which-builtin-type: 1.1.4 + regenerator-runtime@0.14.1: {} + regexp.prototype.flags@1.5.3: dependencies: call-bind: 1.0.7 @@ -3004,4 +3281,8 @@ snapshots: yaml@2.6.0: {} + yjs@13.6.20: + dependencies: + lib0: 0.2.98 + yocto-queue@0.1.0: {} diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx new file mode 100644 index 0000000..f1851f8 --- /dev/null +++ b/src/components/Editor.tsx @@ -0,0 +1,49 @@ +import { useMemo } from 'react'; +import { + InitialConfigType, + LexicalComposer, +} from '@lexical/react/LexicalComposer'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; + +interface EditorProps { + namespace: string; + onError: (error: Error) => void; + placeholder?: string; +} + +export default function Editor({ + namespace, + onError, + placeholder = 'Enter something', +}: EditorProps) { + const config = useMemo( + () => ({ namespace, onError }), + [namespace, onError], + ); + + return ( + +
+ + {placeholder} + + } + /> + } + ErrorBoundary={LexicalErrorBoundary} + /> +
+ + +
+ ); +} From 187d31c3903f4b7860c1881abbd64143b706e737 Mon Sep 17 00:00:00 2001 From: tdd28 Date: Sun, 17 Nov 2024 20:24:30 +0700 Subject: [PATCH 2/3] chores: add new route + layouting --- src/App.tsx | 19 +++++++++++++++++-- src/components/PageLoading.tsx | 3 +++ src/routes/Layout.tsx | 9 +++++++++ src/routes/New.tsx | 5 +++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/components/PageLoading.tsx create mode 100644 src/routes/Layout.tsx create mode 100644 src/routes/New.tsx diff --git a/src/App.tsx b/src/App.tsx index 820fe83..f8c1d22 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,25 @@ +import { lazy, Suspense } from 'react'; import { Route, Routes } from 'react-router-dom'; -import Home from '@/routes/Home'; +import PageLoading from './components/PageLoading'; +import Layout from './routes/Layout'; +import Home from './routes/Home'; + +const New = lazy(() => import('@/routes/New')); export default function App() { return ( - + + + }> + + + } + /> + ); } diff --git a/src/components/PageLoading.tsx b/src/components/PageLoading.tsx new file mode 100644 index 0000000..ffffcfb --- /dev/null +++ b/src/components/PageLoading.tsx @@ -0,0 +1,3 @@ +export default function PageLoading() { + return

Loading ...

; +} diff --git a/src/routes/Layout.tsx b/src/routes/Layout.tsx new file mode 100644 index 0000000..7f06536 --- /dev/null +++ b/src/routes/Layout.tsx @@ -0,0 +1,9 @@ +import { Outlet } from 'react-router-dom'; + +export default function Layout() { + return ( +
+ +
+ ); +} diff --git a/src/routes/New.tsx b/src/routes/New.tsx new file mode 100644 index 0000000..2d8e441 --- /dev/null +++ b/src/routes/New.tsx @@ -0,0 +1,5 @@ +import Editor from '@/components/Editor'; + +export default function New() { + return ; +} From c11164324816b1277e013b3b3848f03369e4c03a Mon Sep 17 00:00:00 2001 From: tdd28 Date: Mon, 18 Nov 2024 23:54:58 +0700 Subject: [PATCH 3/3] feat: undo/redo --- package.json | 2 + pnpm-lock.yaml | 15 +++++++ src/components/Editor.tsx | 49 -------------------- src/lexical/Editor.tsx | 62 +++++++++++++++++++++++++ src/lexical/plugins/ToolbarPlugin.tsx | 65 +++++++++++++++++++++++++++ src/routes/New.tsx | 2 +- src/utils/html.ts | 4 ++ 7 files changed, 149 insertions(+), 50 deletions(-) delete mode 100644 src/components/Editor.tsx create mode 100644 src/lexical/Editor.tsx create mode 100644 src/lexical/plugins/ToolbarPlugin.tsx create mode 100644 src/utils/html.ts diff --git a/package.json b/package.json index c90f406..45157a1 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "preview": "rsbuild preview" }, "dependencies": { + "@heroicons/react": "^2.2.0", "@lexical/react": "^0.20.0", + "@lexical/utils": "^0.20.0", "lexical": "^0.20.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0432db..bd559f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,15 @@ importers: .: dependencies: + '@heroicons/react': + specifier: ^2.2.0 + version: 2.2.0(react@18.3.1) '@lexical/react': specifier: ^0.20.0 version: 0.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.20) + '@lexical/utils': + specifier: ^0.20.0 + version: 0.20.0 lexical: specifier: ^0.20.0 version: 0.20.0 @@ -126,6 +132,11 @@ packages: resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@heroicons/react@2.2.0': + resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} + peerDependencies: + react: '>= 16 || ^19.0.0-rc' + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1598,6 +1609,10 @@ snapshots: dependencies: levn: 0.4.1 + '@heroicons/react@2.2.0(react@18.3.1)': + dependencies: + react: 18.3.1 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx deleted file mode 100644 index f1851f8..0000000 --- a/src/components/Editor.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useMemo } from 'react'; -import { - InitialConfigType, - LexicalComposer, -} from '@lexical/react/LexicalComposer'; -import { ContentEditable } from '@lexical/react/LexicalContentEditable'; -import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; -import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; -import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; -import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; - -interface EditorProps { - namespace: string; - onError: (error: Error) => void; - placeholder?: string; -} - -export default function Editor({ - namespace, - onError, - placeholder = 'Enter something', -}: EditorProps) { - const config = useMemo( - () => ({ namespace, onError }), - [namespace, onError], - ); - - return ( - -
- - {placeholder} - - } - /> - } - ErrorBoundary={LexicalErrorBoundary} - /> -
- - -
- ); -} diff --git a/src/lexical/Editor.tsx b/src/lexical/Editor.tsx new file mode 100644 index 0000000..8eabc01 --- /dev/null +++ b/src/lexical/Editor.tsx @@ -0,0 +1,62 @@ +import { useMemo } from 'react'; +import { + InitialConfigType, + LexicalComposer, +} from '@lexical/react/LexicalComposer'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; +import type { EditorState, LexicalEditor } from 'lexical'; +import ToolbarPlugin from './plugins/ToolbarPlugin'; + +interface EditorProps { + namespace: string; + onError: (error: Error, editor: LexicalEditor) => void; + onChange?: ( + state: EditorState, + editor: LexicalEditor, + tags: Set, + ) => void; + placeholder?: string; +} + +export default function Editor({ + namespace, + onError, + onChange, + placeholder = 'Enter something', +}: EditorProps) { + const config = useMemo( + () => ({ namespace, onError }), + [namespace, onError], + ); + + return ( + +
+ +
+ + {placeholder} + + } + /> + } + ErrorBoundary={LexicalErrorBoundary} + /> +
+
+ {onChange && } + + +
+ ); +} diff --git a/src/lexical/plugins/ToolbarPlugin.tsx b/src/lexical/plugins/ToolbarPlugin.tsx new file mode 100644 index 0000000..a3c6fa7 --- /dev/null +++ b/src/lexical/plugins/ToolbarPlugin.tsx @@ -0,0 +1,65 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { + ArrowUturnLeftIcon, + ArrowUturnRightIcon, +} from '@heroicons/react/24/solid'; +import { useEffect, useState } from 'react'; +import { mergeRegister } from '@lexical/utils'; +import { + CAN_REDO_COMMAND, + CAN_UNDO_COMMAND, + COMMAND_PRIORITY_LOW, + REDO_COMMAND, + UNDO_COMMAND, +} from 'lexical'; +import { clsx } from '@/utils/html'; + +export default function ToolbarPlugin() { + const [editor] = useLexicalComposerContext(); + const [canUndo, setCanUndo] = useState(false); + const [canRedo, setCanRedo] = useState(false); + + useEffect( + () => + mergeRegister( + editor.registerCommand( + CAN_UNDO_COMMAND, + (payload) => { + setCanUndo(payload); + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + CAN_REDO_COMMAND, + (payload) => { + setCanRedo(payload); + return false; + }, + COMMAND_PRIORITY_LOW, + ), + ), + [], + ); + + return ( +
+ + +
+ ); +} + +function Button(props: React.ComponentProps<'button'>) { + return