From 8c7396545ed1711231b208290e76ecd8954b41e2 Mon Sep 17 00:00:00 2001 From: "zhihengzhanga@gmail.com" Date: Sun, 19 Nov 2023 00:38:46 -0700 Subject: [PATCH] fix --- .gitignore | 24 + .vscode/extensions.json | 3 + README.md | 47 + index.html | 13 + package.json | 44 + pnpm-lock.yaml | 1387 +++++++++++++++++++++++++ public/vite.svg | 1 + shared/package.json | 20 + shared/src/canUseDOM.ts | 12 + shared/src/caretFromPoint.ts | 40 + shared/src/environment.ts | 53 + shared/src/invariant.ts | 26 + shared/src/simpleDiffWithCursor.ts | 49 + shared/src/useLayoutEffect.svelte.ts | 15 + shared/src/warnOnlyOnce.ts | 20 + src/App.svelte | 105 ++ src/Editor.svelte | 41 + src/app.css | 79 ++ src/appSettings.ts | 34 + src/lib/LexicalComposer.svelte | 137 +++ src/lib/LexicalComposerContext.ts | 69 ++ src/lib/LexicalContentEditable.svelte | 72 ++ src/lib/LexicalNodeMenuPlugin.svelte | 130 +++ src/lib/LexicalPlainTextPlugin.svelte | 26 + src/lib/placeholder.svelte | 17 + src/lib/types.ts | 4 + src/lib/useLexicalEditable.ts | 25 + src/lib/useLexicalSubscription.ts | 39 + src/main.ts | 9 + src/playground/PlaygroundNodes.ts | 38 + src/react.svelte.ts | 39 + src/shared/LexicalMenu.svelte | 0 src/shared/LexicalMenu.ts | 569 ++++++++++ src/shared/useCanShowPlaceholder.ts | 49 + src/shared/useCharacterLimit.ts | 293 ++++++ src/shared/useDecorators.svelte.ts | 56 + src/shared/useHistory.ts | 28 + src/shared/useList.ts | 65 ++ src/shared/usePlainTextSetup.ts | 26 + src/shared/useRichTextSetup.ts | 26 + src/shared/useYjsCollaboration.tsx | 421 ++++++++ src/themes/CommentEditorTheme.css | 13 + src/themes/CommentEditorTheme.ts | 20 + src/themes/PlaygroundEditorTheme.css | 441 ++++++++ src/themes/PlaygroundEditorTheme.ts | 118 +++ src/themes/StickyEditorTheme.css | 13 + src/themes/StickyEditorTheme.ts | 20 + src/vite-env.d.ts | 2 + svelte.config.js | 7 + tsconfig.json | 26 + tsconfig.node.json | 9 + vite.config.ts | 17 + 52 files changed, 4837 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 public/vite.svg create mode 100644 shared/package.json create mode 100644 shared/src/canUseDOM.ts create mode 100644 shared/src/caretFromPoint.ts create mode 100644 shared/src/environment.ts create mode 100644 shared/src/invariant.ts create mode 100644 shared/src/simpleDiffWithCursor.ts create mode 100644 shared/src/useLayoutEffect.svelte.ts create mode 100644 shared/src/warnOnlyOnce.ts create mode 100644 src/App.svelte create mode 100644 src/Editor.svelte create mode 100644 src/app.css create mode 100644 src/appSettings.ts create mode 100644 src/lib/LexicalComposer.svelte create mode 100644 src/lib/LexicalComposerContext.ts create mode 100644 src/lib/LexicalContentEditable.svelte create mode 100644 src/lib/LexicalNodeMenuPlugin.svelte create mode 100644 src/lib/LexicalPlainTextPlugin.svelte create mode 100644 src/lib/placeholder.svelte create mode 100644 src/lib/types.ts create mode 100644 src/lib/useLexicalEditable.ts create mode 100644 src/lib/useLexicalSubscription.ts create mode 100644 src/main.ts create mode 100644 src/playground/PlaygroundNodes.ts create mode 100644 src/react.svelte.ts create mode 100644 src/shared/LexicalMenu.svelte create mode 100644 src/shared/LexicalMenu.ts create mode 100644 src/shared/useCanShowPlaceholder.ts create mode 100644 src/shared/useCharacterLimit.ts create mode 100644 src/shared/useDecorators.svelte.ts create mode 100644 src/shared/useHistory.ts create mode 100644 src/shared/useList.ts create mode 100644 src/shared/usePlainTextSetup.ts create mode 100644 src/shared/useRichTextSetup.ts create mode 100644 src/shared/useYjsCollaboration.tsx create mode 100644 src/themes/CommentEditorTheme.css create mode 100644 src/themes/CommentEditorTheme.ts create mode 100644 src/themes/PlaygroundEditorTheme.css create mode 100644 src/themes/PlaygroundEditorTheme.ts create mode 100644 src/themes/StickyEditorTheme.css create mode 100644 src/themes/StickyEditorTheme.ts create mode 100644 src/vite-env.d.ts create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..bdef820 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e6cd94f --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Svelte + TS + Vite + +This template should help get you started developing with Svelte and TypeScript in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). + +## Need an official Svelte framework? + +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. + +## Technical considerations + +**Why use this over SvelteKit?** + +- It brings its own routing solution which might not be preferable for some users. +- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. + +This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. + +Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. + +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** + +Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. + +**Why include `.vscode/extensions.json`?** + +Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. + +**Why enable `allowJs` in the TS template?** + +While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. + +**Why is HMR not preserving my local component state?** + +HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). + +If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. + +```ts +// store.ts +// An extremely simple external store +import { writable } from 'svelte/store' +export default writable(0) +``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..b6c5f0a --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Svelte + TS + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..23fa941 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "lexical-svelte", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json" + }, + "dependencies": { + "@lexical/clipboard": "0.12.4", + "@lexical/code": "0.12.4", + "@lexical/dragon": "0.12.4", + "@lexical/hashtag": "0.12.4", + "@lexical/history": "0.12.4", + "@lexical/link": "0.12.4", + "@lexical/list": "0.12.4", + "@lexical/mark": "0.12.4", + "@lexical/markdown": "0.12.4", + "@lexical/overflow": "0.12.4", + "@lexical/plain-text": "0.12.4", + "@lexical/rich-text": "0.12.4", + "@lexical/selection": "0.12.4", + "@lexical/table": "0.12.4", + "@lexical/text": "0.12.4", + "@lexical/utils": "0.12.4", + "@lexical/yjs": "0.12.4", + "@types/node": "^20.9.2", + "lexical": "^0.12.4", + "react-error-boundary": "^3.1.4", + "vite-tsconfig-paths": "^4.2.1" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@tsconfig/svelte": "^5.0.2", + "svelte": "5.0.0-next.8", + "svelte-check": "^3.6.0", + "tslib": "^2.6.2", + "typescript": "^5.2.2", + "vite": "^5.0.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..d30699e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1387 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@lexical/clipboard': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/code': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/dragon': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/hashtag': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/history': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/link': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/list': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/mark': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/markdown': + specifier: 0.12.4 + version: 0.12.4(@lexical/clipboard@0.12.4)(@lexical/selection@0.12.4)(lexical@0.12.4) + '@lexical/overflow': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/plain-text': + specifier: 0.12.4 + version: 0.12.4(@lexical/clipboard@0.12.4)(@lexical/selection@0.12.4)(@lexical/utils@0.12.4)(lexical@0.12.4) + '@lexical/rich-text': + specifier: 0.12.4 + version: 0.12.4(@lexical/clipboard@0.12.4)(@lexical/selection@0.12.4)(@lexical/utils@0.12.4)(lexical@0.12.4) + '@lexical/selection': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/table': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/text': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/utils': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4) + '@lexical/yjs': + specifier: 0.12.4 + version: 0.12.4(lexical@0.12.4)(yjs@13.6.8) + '@types/node': + specifier: ^20.9.2 + version: 20.9.2 + lexical: + specifier: ^0.12.4 + version: 0.12.4 + react-error-boundary: + specifier: ^3.1.4 + version: 3.1.4(react@18.2.0) + vite-tsconfig-paths: + specifier: ^4.2.1 + version: 4.2.1(typescript@5.2.2)(vite@5.0.0) + +devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^3.0.0 + version: 3.0.0(svelte@5.0.0-next.8)(vite@5.0.0) + '@tsconfig/svelte': + specifier: ^5.0.2 + version: 5.0.2 + svelte: + specifier: 5.0.0-next.8 + version: 5.0.0-next.8 + svelte-check: + specifier: ^3.6.0 + version: 3.6.0(svelte@5.0.0-next.8) + tslib: + specifier: ^2.6.2 + version: 2.6.2 + typescript: + specifier: ^5.2.2 + version: 5.2.2 + vite: + specifier: ^5.0.0 + version: 5.0.0(@types/node@20.9.2) + +packages: + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@babel/runtime@7.23.2: + resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: false + + /@esbuild/android-arm64@0.19.5: + resolution: {integrity: sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-arm@0.19.5: + resolution: {integrity: sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-x64@0.19.5: + resolution: {integrity: sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/darwin-arm64@0.19.5: + resolution: {integrity: sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/darwin-x64@0.19.5: + resolution: {integrity: sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/freebsd-arm64@0.19.5: + resolution: {integrity: sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/freebsd-x64@0.19.5: + resolution: {integrity: sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/linux-arm64@0.19.5: + resolution: {integrity: sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-arm@0.19.5: + resolution: {integrity: sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ia32@0.19.5: + resolution: {integrity: sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-loong64@0.19.5: + resolution: {integrity: sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-mips64el@0.19.5: + resolution: {integrity: sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ppc64@0.19.5: + resolution: {integrity: sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-riscv64@0.19.5: + resolution: {integrity: sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-s390x@0.19.5: + resolution: {integrity: sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-x64@0.19.5: + resolution: {integrity: sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/netbsd-x64@0.19.5: + resolution: {integrity: sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /@esbuild/openbsd-x64@0.19.5: + resolution: {integrity: sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + + /@esbuild/sunos-x64@0.19.5: + resolution: {integrity: sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + + /@esbuild/win32-arm64@0.19.5: + resolution: {integrity: sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-ia32@0.19.5: + resolution: {integrity: sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-x64@0.19.5: + resolution: {integrity: sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.20: + resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@lexical/clipboard@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-kFR+UdhtLCMTQgZCyDmYzp2yjPFMNpUZ4TaRjuRBpCRFYwKMlgie4p1J4VJm6sT23kkAFZtVjOfp+gDEYnPHRQ==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/html': 0.12.4(lexical@0.12.4) + '@lexical/list': 0.12.4(lexical@0.12.4) + '@lexical/selection': 0.12.4(lexical@0.12.4) + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/code@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-pX7rJCjbjCl6VdOPl2hl/UkjP3iPPyCQgH2VQ+WlXapDd+0uZ54nPL1MKCCaFUZocHPmOmSRKKGUp6K2CNiqzg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + prismjs: 1.29.0 + dev: false + + /@lexical/dragon@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-7DaXdQ/5GJ8HRpPYr2+SjaUi912tG9L6ukg9IglG1t51lWGxqLx2chW17tp50XDTtY05w9VnoMaxtgsuCN5Pmg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + lexical: 0.12.4 + dev: false + + /@lexical/hashtag@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-iCxQRBZmgwAV6kypmxtWg7HVhBC7PKclmqLNaLDLoKBm+keEXpKnGB5iEtgK/tCMiwkzrg+wGcrw5qi+YjvM9Q==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/history@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-XLbSSr9FueAxuKHo4LBi+lZNVAEReNNDCt4MM2Ol8UZhWPlpNskSB/sECYEEQ6/ItlzgtnKyKWjfDFBHRWvC2g==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/html@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-RD/n9n1eCuTZtLaTEI3wuUDlJjCn6j+/0c9GvzqLKhNz9f+E5zMVExhzTT4cZQh5WXbzGFNlwC/cuOtaM3wODg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/selection': 0.12.4(lexical@0.12.4) + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/link@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-gmEs0GJGDhgwV1x0IrO7Br2GCALijZLIayGWoLAgYiXZee4WZpvjbngZuC6yghYBhrme6muPRMG2sLMwV2cWiQ==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/list@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-qxwRIz+4Aj2u2fzyGPo86vX+1ebwCnamppr/c5ZWuqpRTWtYDWjq5LQKIwAvZBxCzPdtP5jzwyZ6VYWQXYW4Kg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/mark@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-NFFk/3AFFJARjsth8wd5HdeW8XhcaECoQ8wwnJ4fRZzgN0lu3ZSiq+CuVm0NRN5xA5KoUT6sfIQqGOzIPfvdsw==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/markdown@0.12.4(@lexical/clipboard@0.12.4)(@lexical/selection@0.12.4)(lexical@0.12.4): + resolution: {integrity: sha512-cOk0dkafyvQI4DMwwMfkP329bRVfyhXcVF3dcRiydl6ZIgqOrj/EMi+C0qxQkcqg0MO26Rky6LLJ4vQi6AgJDg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/code': 0.12.4(lexical@0.12.4) + '@lexical/link': 0.12.4(lexical@0.12.4) + '@lexical/list': 0.12.4(lexical@0.12.4) + '@lexical/rich-text': 0.12.4(@lexical/clipboard@0.12.4)(@lexical/selection@0.12.4)(@lexical/utils@0.12.4)(lexical@0.12.4) + '@lexical/text': 0.12.4(lexical@0.12.4) + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + transitivePeerDependencies: + - '@lexical/clipboard' + - '@lexical/selection' + dev: false + + /@lexical/offset@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-6fjXCx+YD1TMl6GFL4wowhBgbIg+UX3j2OOXh3F7WEp3SDvzoJsJ6F7xRctrHQbluCITM3oDwOyHa1J0m5lrFg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + lexical: 0.12.4 + dev: false + + /@lexical/overflow@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-mEWgVukoOgcyDruHvzk1amy9jgGDVXFYiPn20ykxgrVQz6XEpq+lfyic/BUnN4toNR8p6jc/Yxi2lF1ELCU0Kg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + lexical: 0.12.4 + dev: false + + /@lexical/plain-text@0.12.4(@lexical/clipboard@0.12.4)(@lexical/selection@0.12.4)(@lexical/utils@0.12.4)(lexical@0.12.4): + resolution: {integrity: sha512-osbqOyt19oFG0kTbV71jxxCdgnUqNYW6QXIIaS1SwcCN/N1CdFZ0sNpjPkHIFx9AdZ/Tmi4u9SNFUo16DjvThA==} + peerDependencies: + '@lexical/clipboard': 0.12.4 + '@lexical/selection': 0.12.4 + '@lexical/utils': 0.12.4 + lexical: 0.12.4 + dependencies: + '@lexical/clipboard': 0.12.4(lexical@0.12.4) + '@lexical/selection': 0.12.4(lexical@0.12.4) + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/rich-text@0.12.4(@lexical/clipboard@0.12.4)(@lexical/selection@0.12.4)(@lexical/utils@0.12.4)(lexical@0.12.4): + resolution: {integrity: sha512-gWMDmdRRFPk00JfQv52650qcpjTN6oBrrYwBydYvEG8WTC8o1k8qEOZaOFja6GElPt0520dpyvcWHTlIL0jv3Q==} + peerDependencies: + '@lexical/clipboard': 0.12.4 + '@lexical/selection': 0.12.4 + '@lexical/utils': 0.12.4 + lexical: 0.12.4 + dependencies: + '@lexical/clipboard': 0.12.4(lexical@0.12.4) + '@lexical/selection': 0.12.4(lexical@0.12.4) + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/selection@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-9lJt9PBJW7lWYiPDo/PGl2nZ6NrdYaDBidEoMNhyusPjeBEr35z4Hm0qWUhDrPDQPhK2i1oBw6nZa94bxuS9Lw==} + peerDependencies: + lexical: 0.12.4 + dependencies: + lexical: 0.12.4 + dev: false + + /@lexical/table@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-Lyy6y1HOQqzU8O2cH5Zhzek46B0UU7NceM2fJKM7qiBSuxY/nE0BzkFq0xDk3x5W+vhXob6Z32sJSNFImtuqKw==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/utils': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/text@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-r/7402eCf6C/7BqUNR7ZLZQQjsE62wjeuf0rFeW1ulOpwiti/dFn1o+EsCb0hvNeHPzfGgRC+FuDT9KSEKu7Ig==} + peerDependencies: + lexical: 0.12.4 + dependencies: + lexical: 0.12.4 + dev: false + + /@lexical/utils@0.12.4(lexical@0.12.4): + resolution: {integrity: sha512-ColV11ANBY6deT7CdGwP4lzv3pb5caFfFLcVKdGDMMJSUYFQ5l69aZvDP2qWWnNqzGLb+AJSunMd142wWc5LGg==} + peerDependencies: + lexical: 0.12.4 + dependencies: + '@lexical/list': 0.12.4(lexical@0.12.4) + '@lexical/selection': 0.12.4(lexical@0.12.4) + '@lexical/table': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + dev: false + + /@lexical/yjs@0.12.4(lexical@0.12.4)(yjs@13.6.8): + resolution: {integrity: sha512-qtCiABugE1CiZ7K5iFfQnB1KqfWtLyiRK0nxAaSxuZzQTO4+Kh3WDh7ULppPa53Sf3pKpw8Sq2XB4AXP6csbkg==} + peerDependencies: + lexical: 0.12.4 + yjs: '>=13.5.22' + dependencies: + '@lexical/offset': 0.12.4(lexical@0.12.4) + lexical: 0.12.4 + yjs: 13.6.8 + dev: false + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@rollup/rollup-android-arm-eabi@4.5.0: + resolution: {integrity: sha512-OINaBGY+Wc++U0rdr7BLuFClxcoWaVW3vQYqmQq6B3bqQ/2olkaoz+K8+af/Mmka/C2yN5j+L9scBkv4BtKsDA==} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-android-arm64@4.5.0: + resolution: {integrity: sha512-UdMf1pOQc4ZmUA/NTmKhgJTBimbSKnhPS2zJqucqFyBRFPnPDtwA8MzrGNTjDeQbIAWfpJVAlxejw+/lQyBK/w==} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-arm64@4.5.0: + resolution: {integrity: sha512-L0/CA5p/idVKI+c9PcAPGorH6CwXn6+J0Ys7Gg1axCbTPgI8MeMlhA6fLM9fK+ssFhqogMHFC8HDvZuetOii7w==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-x64@4.5.0: + resolution: {integrity: sha512-QZCbVqU26mNlLn8zi/XDDquNmvcr4ON5FYAHQQsyhrHx8q+sQi/6xduoznYXwk/KmKIXG5dLfR0CvY+NAWpFYQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.5.0: + resolution: {integrity: sha512-VpSQ+xm93AeV33QbYslgf44wc5eJGYfYitlQzAi3OObu9iwrGXEnmu5S3ilkqE3Pr/FkgOiJKV/2p0ewf4Hrtg==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.5.0: + resolution: {integrity: sha512-OrEyIfpxSsMal44JpEVx9AEcGpdBQG1ZuWISAanaQTSMeStBW+oHWwOkoqR54bw3x8heP8gBOyoJiGg+fLY8qQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.5.0: + resolution: {integrity: sha512-1H7wBbQuE6igQdxMSTjtFfD+DGAudcYWhp106z/9zBA8OQhsJRnemO4XGavdzHpGhRtRxbgmUGdO3YQgrWf2RA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.5.0: + resolution: {integrity: sha512-FVyFI13tXw5aE65sZdBpNjPVIi4Q5mARnL/39UIkxvSgRAIqCo5sCpCELk0JtXHGee2owZz5aNLbWNfBHzr71Q==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.5.0: + resolution: {integrity: sha512-eBPYl2sLpH/o8qbSz6vPwWlDyThnQjJfcDOGFbNjmjb44XKC1F5dQfakOsADRVrXCNzM6ZsSIPDG5dc6HHLNFg==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.5.0: + resolution: {integrity: sha512-xaOHIfLOZypoQ5U2I6rEaugS4IYtTgP030xzvrBf5js7p9WI9wik07iHmsKaej8Z83ZDxN5GyypfoyKV5O5TJA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.5.0: + resolution: {integrity: sha512-Al6quztQUrHwcOoU2TuFblUQ5L+/AmPBXFR6dUvyo4nRj2yQRK0WIUaGMF/uwKulvRcXkpHe3k9A8Vf93VDktA==} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.5.0: + resolution: {integrity: sha512-8kdW+brNhI/NzJ4fxDufuJUjepzINqJKLGHuxyAtpPG9bMbn8P5mtaCcbOm0EzLJ+atg+kF9dwg8jpclkVqx5w==} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.0)(svelte@5.0.0-next.8)(vite@5.0.0): + resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==} + engines: {node: ^18.0.0 || >=20} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 3.0.0(svelte@5.0.0-next.8)(vite@5.0.0) + debug: 4.3.4 + svelte: 5.0.0-next.8 + vite: 5.0.0(@types/node@20.9.2) + transitivePeerDependencies: + - supports-color + dev: true + + /@sveltejs/vite-plugin-svelte@3.0.0(svelte@5.0.0-next.8)(vite@5.0.0): + resolution: {integrity: sha512-Th0nupxk8hl5Rcg9jm+1xWylwco4bSUAvutWxM4W4bjOAollpXLmrYqSSnYo9pPbZOO6ZGRm6sSqYa/v1d/Saw==} + engines: {node: ^18.0.0 || >=20} + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 2.0.0(@sveltejs/vite-plugin-svelte@3.0.0)(svelte@5.0.0-next.8)(vite@5.0.0) + debug: 4.3.4 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.5 + svelte: 5.0.0-next.8 + svelte-hmr: 0.15.3(svelte@5.0.0-next.8) + vite: 5.0.0(@types/node@20.9.2) + vitefu: 0.2.5(vite@5.0.0) + transitivePeerDependencies: + - supports-color + dev: true + + /@tsconfig/svelte@5.0.2: + resolution: {integrity: sha512-BRbo1fOtyVbhfLyuCWw6wAWp+U8UQle+ZXu84MYYWzYSEB28dyfnRBIE99eoG+qdAC0po6L2ScIEivcT07UaMA==} + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/node@20.9.2: + resolution: {integrity: sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==} + dependencies: + undici-types: 5.26.5 + + /@types/pug@2.0.9: + resolution: {integrity: sha512-Yg4LkgFYvn1faISbDNWmcAC1XoDT8IoMUFspp5mnagKk+UvD2N0IWt5A7GRdMubsNWqgCLmrkf8rXkzNqb4szA==} + dev: true + + /acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + dependencies: + dequal: 2.0.3 + dev: true + + /axobject-query@4.0.0: + resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + dependencies: + dequal: 2.0.3 + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: true + + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: true + + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + dev: true + + /esbuild@0.19.5: + resolution: {integrity: sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.19.5 + '@esbuild/android-arm64': 0.19.5 + '@esbuild/android-x64': 0.19.5 + '@esbuild/darwin-arm64': 0.19.5 + '@esbuild/darwin-x64': 0.19.5 + '@esbuild/freebsd-arm64': 0.19.5 + '@esbuild/freebsd-x64': 0.19.5 + '@esbuild/linux-arm': 0.19.5 + '@esbuild/linux-arm64': 0.19.5 + '@esbuild/linux-ia32': 0.19.5 + '@esbuild/linux-loong64': 0.19.5 + '@esbuild/linux-mips64el': 0.19.5 + '@esbuild/linux-ppc64': 0.19.5 + '@esbuild/linux-riscv64': 0.19.5 + '@esbuild/linux-s390x': 0.19.5 + '@esbuild/linux-x64': 0.19.5 + '@esbuild/netbsd-x64': 0.19.5 + '@esbuild/openbsd-x64': 0.19.5 + '@esbuild/sunos-x64': 0.19.5 + '@esbuild/win32-arm64': 0.19.5 + '@esbuild/win32-ia32': 0.19.5 + '@esbuild/win32-x64': 0.19.5 + + /esm-env@1.0.0: + resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + dev: true + + /esrap@1.1.1: + resolution: {integrity: sha512-PIgHGLP8VAG4Iao4CbOc+/5tgn+TpzGhyAuVCR5qgcFgPOUk9Ds61bH7hD2lbjDuu86lagofx3lVrRFWcIF+Gg==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + '@types/estree': 1.0.5 + dev: true + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: false + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + dev: false + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /lexical@0.12.4: + resolution: {integrity: sha512-giNrnp45H6P4IHFhkKaHEPTF+bKLBWdEIDL/FGjRZf+to7l7TORIBk/23Zdchzt/VGgKGWu950EOvGh53gkVMQ==} + dev: false + + /lib0@0.2.87: + resolution: {integrity: sha512-TbB63XJixvNToW2IHWAFsCJj9tVnajmwjE14p69i51Rx8byOQd2IP4ourE8v4d7vhyO++nVm1sQk3ePslfbucg==} + engines: {node: '>=16'} + hasBin: true + dependencies: + isomorphic.js: 0.2.5 + dev: false + + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: true + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /magic-string@0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + dev: false + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /react-error-boundary@3.1.4(react@18.2.0): + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + dependencies: + '@babel/runtime': 7.23.2 + react: 18.2.0 + dev: false + + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup@4.5.0: + resolution: {integrity: sha512-41xsWhzxqjMDASCxH5ibw1mXk+3c4TNI2UjKbLxe6iEzrSQnqOzmmK8/3mufCPbzHNJ2e04Fc1ddI35hHy+8zg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.5.0 + '@rollup/rollup-android-arm64': 4.5.0 + '@rollup/rollup-darwin-arm64': 4.5.0 + '@rollup/rollup-darwin-x64': 4.5.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.5.0 + '@rollup/rollup-linux-arm64-gnu': 4.5.0 + '@rollup/rollup-linux-arm64-musl': 4.5.0 + '@rollup/rollup-linux-x64-gnu': 4.5.0 + '@rollup/rollup-linux-x64-musl': 4.5.0 + '@rollup/rollup-win32-arm64-msvc': 4.5.0 + '@rollup/rollup-win32-ia32-msvc': 4.5.0 + '@rollup/rollup-win32-x64-msvc': 4.5.0 + fsevents: 2.3.3 + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: true + + /sander@0.5.1: + resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} + dependencies: + es6-promise: 3.3.1 + graceful-fs: 4.2.11 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: true + + /sorcery@0.11.0: + resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} + hasBin: true + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + buffer-crc32: 0.2.13 + minimist: 1.2.8 + sander: 0.5.1 + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /svelte-check@3.6.0(svelte@5.0.0-next.8): + resolution: {integrity: sha512-8VfqhfuRJ1sKW+o8isH2kPi0RhjXH1nNsIbCFGyoUHG+ZxVxHYRKcb+S8eaL/1tyj3VGvWYx3Y5+oCUsJgnzcw==} + hasBin: true + peerDependencies: + svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + chokidar: 3.5.3 + fast-glob: 3.3.2 + import-fresh: 3.3.0 + picocolors: 1.0.0 + sade: 1.8.1 + svelte: 5.0.0-next.8 + svelte-preprocess: 5.1.0(svelte@5.0.0-next.8)(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + dev: true + + /svelte-hmr@0.15.3(svelte@5.0.0-next.8): + resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} + peerDependencies: + svelte: ^3.19.0 || ^4.0.0 + dependencies: + svelte: 5.0.0-next.8 + dev: true + + /svelte-preprocess@5.1.0(svelte@5.0.0-next.8)(typescript@5.2.2): + resolution: {integrity: sha512-EkErPiDzHAc0k2MF5m6vBNmRUh338h2myhinUw/xaqsLs7/ZvsgREiLGj03VrSzbY/TB5ZXgBOsKraFee5yceA==} + engines: {node: '>= 14.10.0'} + requiresBuild: true + peerDependencies: + '@babel/core': ^7.10.2 + coffeescript: ^2.5.1 + less: ^3.11.3 || ^4.0.0 + postcss: ^7 || ^8 + postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 + pug: ^3.0.0 + sass: ^1.26.8 + stylus: ^0.55.0 + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 + typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + coffeescript: + optional: true + less: + optional: true + postcss: + optional: true + postcss-load-config: + optional: true + pug: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + typescript: + optional: true + dependencies: + '@types/pug': 2.0.9 + detect-indent: 6.1.0 + magic-string: 0.27.0 + sorcery: 0.11.0 + strip-indent: 3.0.0 + svelte: 5.0.0-next.8 + typescript: 5.2.2 + dev: true + + /svelte@5.0.0-next.8: + resolution: {integrity: sha512-4c+Q4tUbnOAQsb7S2vsNE9zdEUMaXhMPqpzCgwfFUfC47Ive3BWgY+oxG5HWtXraweizOycZyyDf5qUVn5PZxg==} + engines: {node: '>=18'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + acorn: 8.11.2 + aria-query: 5.3.0 + axobject-query: 4.0.0 + esm-env: 1.0.0 + esrap: 1.1.1 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.5 + zimmerframe: 1.1.0 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /tsconfck@2.1.2(typescript@5.2.2): + resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} + engines: {node: ^14.13.1 || ^16 || >=18} + hasBin: true + peerDependencies: + typescript: ^4.3.5 || ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.2.2 + dev: false + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: true + + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} + hasBin: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + /vite-tsconfig-paths@4.2.1(typescript@5.2.2)(vite@5.0.0): + resolution: {integrity: sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + dependencies: + debug: 4.3.4 + globrex: 0.1.2 + tsconfck: 2.1.2(typescript@5.2.2) + vite: 5.0.0(@types/node@20.9.2) + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /vite@5.0.0(@types/node@20.9.2): + resolution: {integrity: sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.9.2 + esbuild: 0.19.5 + postcss: 8.4.31 + rollup: 4.5.0 + optionalDependencies: + fsevents: 2.3.3 + + /vitefu@0.2.5(vite@5.0.0): + resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 5.0.0(@types/node@20.9.2) + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /yjs@13.6.8: + resolution: {integrity: sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + dependencies: + lib0: 0.2.87 + dev: false + + /zimmerframe@1.1.0: + resolution: {integrity: sha512-+AmV37r9NPUy7KcuG0Fde9AAFSD88kN5pnqvD7Pkp5WLLK0jct7hAtIDXXFDCRk3l5Mc1r2Sth3gfP2ZLE+/Qw==} + dev: true diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/shared/package.json b/shared/package.json new file mode 100644 index 0000000..6257de8 --- /dev/null +++ b/shared/package.json @@ -0,0 +1,20 @@ +{ + "name": "shared", + "private": "true", + "keywords": [ + "react", + "lexical", + "editor", + "rich-text" + ], + "license": "MIT", + "version": "0.12.4", + "dependencies": { + "lexical": "0.12.4" + }, + "repository": { + "type": "git", + "url": "https://github.com/facebook/lexical", + "directory": "packages/shared" + } +} diff --git a/shared/src/canUseDOM.ts b/shared/src/canUseDOM.ts new file mode 100644 index 0000000..78db6aa --- /dev/null +++ b/shared/src/canUseDOM.ts @@ -0,0 +1,12 @@ +/** + * 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. + * + */ + +export const CAN_USE_DOM: boolean = + typeof window !== 'undefined' && + typeof window.document !== 'undefined' && + typeof window.document.createElement !== 'undefined'; diff --git a/shared/src/caretFromPoint.ts b/shared/src/caretFromPoint.ts new file mode 100644 index 0000000..642e070 --- /dev/null +++ b/shared/src/caretFromPoint.ts @@ -0,0 +1,40 @@ +/** + * 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. + * + */ + +export default function caretFromPoint( + x: number, + y: number, +): null | { + offset: number; + node: Node; +} { + if (typeof document.caretRangeFromPoint !== 'undefined') { + const range = document.caretRangeFromPoint(x, y); + if (range === null) { + return null; + } + return { + node: range.startContainer, + offset: range.startOffset, + }; + // @ts-ignore + } else if (document.caretPositionFromPoint !== 'undefined') { + // @ts-ignore FF - no types + const range = document.caretPositionFromPoint(x, y); + if (range === null) { + return null; + } + return { + node: range.offsetNode, + offset: range.offset, + }; + } else { + // Gracefully handle IE + return null; + } +} diff --git a/shared/src/environment.ts b/shared/src/environment.ts new file mode 100644 index 0000000..61f1985 --- /dev/null +++ b/shared/src/environment.ts @@ -0,0 +1,53 @@ +/** + * 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 {CAN_USE_DOM} from 'shared/canUseDOM'; + +declare global { + interface Document { + documentMode?: unknown; + } + + interface Window { + MSStream?: unknown; + } +} + +const documentMode = + CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null; + +export const IS_APPLE: boolean = + CAN_USE_DOM && /Mac|iPod|iPhone|iPad/.test(navigator.platform); + +export const IS_FIREFOX: boolean = + CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent); + +export const CAN_USE_BEFORE_INPUT: boolean = + CAN_USE_DOM && 'InputEvent' in window && !documentMode + ? 'getTargetRanges' in new window.InputEvent('input') + : false; + +export const IS_SAFARI: boolean = + CAN_USE_DOM && /Version\/[\d.]+.*Safari/.test(navigator.userAgent); + +export const IS_IOS: boolean = + CAN_USE_DOM && + /iPad|iPhone|iPod/.test(navigator.userAgent) && + !window.MSStream; + +export const IS_ANDROID: boolean = + CAN_USE_DOM && /Android/.test(navigator.userAgent); + +// Keep these in case we need to use them in the future. +// export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform); +export const IS_CHROME: boolean = + CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent); +// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode; + +export const IS_APPLE_WEBKIT = + CAN_USE_DOM && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && !IS_CHROME; diff --git a/shared/src/invariant.ts b/shared/src/invariant.ts new file mode 100644 index 0000000..0e73848 --- /dev/null +++ b/shared/src/invariant.ts @@ -0,0 +1,26 @@ +/** + * 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. + * + */ + +// invariant(condition, message) will refine types based on "condition", and +// if "condition" is false will throw an error. This function is special-cased +// in flow itself, so we can't name it anything else. +export default function invariant( + cond?: boolean, + message?: string, + ...args: string[] +): asserts cond { + 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/shared/src/simpleDiffWithCursor.ts b/shared/src/simpleDiffWithCursor.ts new file mode 100644 index 0000000..39f3d3b --- /dev/null +++ b/shared/src/simpleDiffWithCursor.ts @@ -0,0 +1,49 @@ +/** + * 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. + * + */ + +export default function simpleDiffWithCursor( + a: string, + b: string, + cursor: number, +): {index: number; insert: string; remove: number} { + const aLength = a.length; + const bLength = b.length; + let left = 0; // number of same characters counting from left + let right = 0; // number of same characters counting from right + // Iterate left to the right until we find a changed character + // First iteration considers the current cursor position + while ( + left < aLength && + left < bLength && + a[left] === b[left] && + left < cursor + ) { + left++; + } + // Iterate right to the left until we find a changed character + while ( + right + left < aLength && + right + left < bLength && + a[aLength - right - 1] === b[bLength - right - 1] + ) { + right++; + } + // Try to iterate left further to the right without caring about the current cursor position + while ( + right + left < aLength && + right + left < bLength && + a[left] === b[left] + ) { + left++; + } + return { + index: left, + insert: b.slice(left, bLength - right), + remove: aLength - left - right, + }; +} diff --git a/shared/src/useLayoutEffect.svelte.ts b/shared/src/useLayoutEffect.svelte.ts new file mode 100644 index 0000000..1daf50a --- /dev/null +++ b/shared/src/useLayoutEffect.svelte.ts @@ -0,0 +1,15 @@ +/** + * 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 { CAN_USE_DOM } from "shared/canUseDOM"; + +const useLayoutEffectImpl = (a: () => void, b?: any) => { + $effect(a); +}; + +export default useLayoutEffectImpl; diff --git a/shared/src/warnOnlyOnce.ts b/shared/src/warnOnlyOnce.ts new file mode 100644 index 0000000..d29e99e --- /dev/null +++ b/shared/src/warnOnlyOnce.ts @@ -0,0 +1,20 @@ +/** + * 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. + * + */ + +export default function warnOnlyOnce(message: string) { + if (!__DEV__) { + return; + } + let run = false; + return () => { + if (!run) { + console.warn(message); + } + run = true; + }; +} diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..2276315 --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,105 @@ + + +
+
+

Vite + Svelte

+ +
+ +
+
+

+ Check out SvelteKit, the official Svelte app framework powered by Vite! +

+ +

Click on the Vite and Svelte logos to learn more

+
+ + diff --git a/src/Editor.svelte b/src/Editor.svelte new file mode 100644 index 0000000..c0d2034 --- /dev/null +++ b/src/Editor.svelte @@ -0,0 +1,41 @@ + + + diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..617f5e9 --- /dev/null +++ b/src/app.css @@ -0,0 +1,79 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/appSettings.ts b/src/appSettings.ts new file mode 100644 index 0000000..caea64c --- /dev/null +++ b/src/appSettings.ts @@ -0,0 +1,34 @@ +/** + * 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. + * + */ + +const hostName = window.location.hostname; +export const isDevPlayground: boolean = + hostName !== "playground.lexical.dev" && + hostName !== "lexical-playground.vercel.app"; + +export const DEFAULT_SETTINGS = { + disableBeforeInput: false, + emptyEditor: isDevPlayground, + isAutocomplete: false, + isCharLimit: false, + isCharLimitUtf8: false, + isCollab: false, + isMaxLength: false, + isRichText: true, + measureTypingPerf: false, + shouldUseLexicalContextMenu: false, + showNestedEditorTreeView: false, + showTableOfContents: false, + showTreeView: true, + tableCellBackgroundColor: true, + tableCellMerge: true, +}; + +export type SettingName = keyof typeof DEFAULT_SETTINGS; + +export type Settings = typeof DEFAULT_SETTINGS; diff --git a/src/lib/LexicalComposer.svelte b/src/lib/LexicalComposer.svelte new file mode 100644 index 0000000..522a0fc --- /dev/null +++ b/src/lib/LexicalComposer.svelte @@ -0,0 +1,137 @@ + + + + + diff --git a/src/lib/LexicalComposerContext.ts b/src/lib/LexicalComposerContext.ts new file mode 100644 index 0000000..f4d98f4 --- /dev/null +++ b/src/lib/LexicalComposerContext.ts @@ -0,0 +1,69 @@ +/** + * 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 { EditorThemeClasses, LexicalEditor } from "lexical"; + +import invariant from "shared/invariant"; +import { getContext, setContext } from "svelte"; + +export type LexicalComposerContextType = { + getTheme: () => EditorThemeClasses | null | undefined; +}; + +export type LexicalComposerContextWithEditor = [ + LexicalEditor, + LexicalComposerContextType +]; +const KEY = "svelte-composer"; +export const setLexicalComposerContext = ( + LexicalComposerContextWithEditor: LexicalComposerContextWithEditor +) => { + setContext( + KEY, + LexicalComposerContextWithEditor + ); +}; + +export const getLexicalComposerContext = () => + getContext(KEY) as LexicalComposerContextWithEditor; + +export function createLexicalComposerContext( + parent: LexicalComposerContextWithEditor | null | undefined, + theme: EditorThemeClasses | null | undefined +): LexicalComposerContextType { + let parentContext: LexicalComposerContextType | null = null; + + if (parent != null) { + parentContext = parent[1]; + } + + function getTheme() { + if (theme != null) { + return theme; + } + + return parentContext != null ? parentContext.getTheme() : null; + } + + return { + getTheme, + }; +} + +export function useLexicalComposerContext(): LexicalComposerContextWithEditor { + const composerContext = getLexicalComposerContext(); + + if (composerContext == null) { + invariant( + false, + "LexicalComposerContext.useLexicalComposerContext: cannot find a LexicalComposerContext" + ); + } + + return composerContext; +} diff --git a/src/lib/LexicalContentEditable.svelte b/src/lib/LexicalContentEditable.svelte new file mode 100644 index 0000000..a2056e8 --- /dev/null +++ b/src/lib/LexicalContentEditable.svelte @@ -0,0 +1,72 @@ + + +
diff --git a/src/lib/LexicalNodeMenuPlugin.svelte b/src/lib/LexicalNodeMenuPlugin.svelte new file mode 100644 index 0000000..26fbf4b --- /dev/null +++ b/src/lib/LexicalNodeMenuPlugin.svelte @@ -0,0 +1,130 @@ + + + + +/** * 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. * */ + +{#if resolution !== null && editor != null} + +{/if} diff --git a/src/lib/LexicalPlainTextPlugin.svelte b/src/lib/LexicalPlainTextPlugin.svelte new file mode 100644 index 0000000..24fc8a4 --- /dev/null +++ b/src/lib/LexicalPlainTextPlugin.svelte @@ -0,0 +1,26 @@ + + + + diff --git a/src/lib/placeholder.svelte b/src/lib/placeholder.svelte new file mode 100644 index 0000000..098e488 --- /dev/null +++ b/src/lib/placeholder.svelte @@ -0,0 +1,17 @@ + + +{#if typeof content === "function"} + {content(editable)} +{:else} + {content ?? ""} +{/if} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..c7d8548 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,4 @@ +export type LexicalSubscription = { + initialValueFn: () => T; + subscribe: (callback: (value: T) => void) => () => void; +}; diff --git a/src/lib/useLexicalEditable.ts b/src/lib/useLexicalEditable.ts new file mode 100644 index 0000000..9a124b0 --- /dev/null +++ b/src/lib/useLexicalEditable.ts @@ -0,0 +1,25 @@ +/** + * 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 { LexicalSubscription } from "./types"; +import type { LexicalEditor } from "lexical"; + +import { useLexicalSubscription } from "./useLexicalSubscription"; + +function subscription(editor: LexicalEditor): LexicalSubscription { + return { + initialValueFn: () => editor.isEditable(), + subscribe: (callback) => { + return editor.registerEditableListener(callback); + }, + }; +} + +export default function useLexicalEditable(): boolean { + return useLexicalSubscription(subscription); +} diff --git a/src/lib/useLexicalSubscription.ts b/src/lib/useLexicalSubscription.ts new file mode 100644 index 0000000..dfca22f --- /dev/null +++ b/src/lib/useLexicalSubscription.ts @@ -0,0 +1,39 @@ +import type { LexicalEditor } from "lexical"; +import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; +import { useMemo, useRef, useState } from "react"; +import useLayoutEffect from "shared/useLayoutEffect.svelte"; + +export type LexicalSubscription = { + initialValueFn: () => T; + subscribe: (callback: (value: T) => void) => () => void; +}; + +/** + * Shortcut to Lexical subscriptions when values are used for render. + */ +export function useLexicalSubscription( + subscription: (editor: LexicalEditor) => LexicalSubscription +): T { + const [editor] = useLexicalComposerContext(); + const initializedSubscription = useMemo( + () => subscription(editor), + [editor, subscription] + ); + const valueRef = useRef(initializedSubscription.initialValueFn()); + const [value, setValue] = useState(valueRef.current); + useLayoutEffect(() => { + const { initialValueFn, subscribe } = initializedSubscription; + const currentValue = initialValueFn(); + if (valueRef.current !== currentValue) { + valueRef.current = currentValue; + setValue(currentValue); + } + + return subscribe((newValue: T) => { + valueRef.current = newValue; + setValue(newValue); + }); + }); + + return value(); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..23223e4 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,9 @@ +import { mount } from "svelte"; +import "./app.css"; +import App from "./App.svelte"; + +const app = mount(App, { + target: document.getElementById("app"), +}); + +export default app; diff --git a/src/playground/PlaygroundNodes.ts b/src/playground/PlaygroundNodes.ts new file mode 100644 index 0000000..6bd5b4a --- /dev/null +++ b/src/playground/PlaygroundNodes.ts @@ -0,0 +1,38 @@ +/** + * 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 { Klass, LexicalNode } from "lexical"; + +import { CodeHighlightNode, CodeNode } from "@lexical/code"; +import { HashtagNode } from "@lexical/hashtag"; +import { AutoLinkNode, LinkNode } from "@lexical/link"; +import { ListItemNode, ListNode } from "@lexical/list"; +import { MarkNode } from "@lexical/mark"; +import { OverflowNode } from "@lexical/overflow"; +import { HeadingNode, QuoteNode } from "@lexical/rich-text"; +import { TableCellNode, TableNode, TableRowNode } from "@lexical/table"; + +const PlaygroundNodes: Array> = [ + HeadingNode, + ListNode, + ListItemNode, + QuoteNode, + CodeNode, + TableNode, + TableCellNode, + TableRowNode, + HashtagNode, + CodeHighlightNode, + AutoLinkNode, + LinkNode, + OverflowNode, + + MarkNode, +]; + +export default PlaygroundNodes; diff --git a/src/react.svelte.ts b/src/react.svelte.ts new file mode 100644 index 0000000..3f6916f --- /dev/null +++ b/src/react.svelte.ts @@ -0,0 +1,39 @@ +import { SvelteComponent, mount, tick } from "svelte"; + +export type pluginTypes = any; + +export function flushSync(fn) { + tick().then(fn); +} +export function useState(state: T) { + let s = $state(state); + + return [ + () => s, + (newValue: T) => { + s = newValue; + }, + ] as const; +} + +export function useEffect(func: () => void, dep: D) { + $effect(func); +} +export function createPortal( + SvelteComponent: SvelteComponent, + element: HTMLElement, + name: any +) { + mount(SvelteComponent, { target: element, props: name }); +} +export function useCallback(fn: T, dep: any) { + return fn; +} + +export function useMemo(fn, dep: any) { + const r = $derived(fn()); + return r; +} +export function useRef(param: T) { + return { current: param }; +} diff --git a/src/shared/LexicalMenu.svelte b/src/shared/LexicalMenu.svelte new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/LexicalMenu.ts b/src/shared/LexicalMenu.ts new file mode 100644 index 0000000..603981c --- /dev/null +++ b/src/shared/LexicalMenu.ts @@ -0,0 +1,569 @@ +/** + * 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 { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; +import { mergeRegister } from "@lexical/utils"; +import { + $getSelection, + $isRangeSelection, + COMMAND_PRIORITY_LOW, + CommandListenerPriority, + createCommand, + KEY_ARROW_DOWN_COMMAND, + KEY_ARROW_UP_COMMAND, + KEY_ENTER_COMMAND, + KEY_ESCAPE_COMMAND, + KEY_TAB_COMMAND, + LexicalCommand, + LexicalEditor, + TextNode, +} from "lexical"; +import { useCallback, useState } from "react"; + +import useLayoutEffect from "shared/useLayoutEffect"; +import { ComponentType, SvelteComponent } from "svelte"; + +export type MenuTextMatch = { + leadOffset: number; + matchingString: string; + replaceableString: string; +}; + +export type MenuResolution = { + match?: MenuTextMatch; + getRect: () => DOMRect; +}; + +export const PUNCTUATION = + "\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%'\"~=<>_:;"; + +export class MenuOption { + key: string; + ref?: HTMLElement | null; + + constructor(key: string) { + this.key = key; + this.ref = null; + this.setRefElement = this.setRefElement.bind(this); + } + + setRefElement(element: HTMLElement | null) { + this.ref = element; + } +} + +export type MenuRenderFn = ( + anchorElementRef: HTMLElement | null, + itemProps: { + selectedIndex: number | null; + selectOptionAndCleanUp: (option: TOption) => void; + setHighlightedIndex: (index: number) => void; + options: Array; + }, + matchingString: string | null +) => SvelteComponent; + +const scrollIntoViewIfNeeded = (target: HTMLElement) => { + const typeaheadContainerNode = document.getElementById("typeahead-menu"); + if (!typeaheadContainerNode) return; + + const typeaheadRect = typeaheadContainerNode.getBoundingClientRect(); + + if (typeaheadRect.top + typeaheadRect.height > window.innerHeight) { + typeaheadContainerNode.scrollIntoView({ + block: "center", + }); + } + + if (typeaheadRect.top < 0) { + typeaheadContainerNode.scrollIntoView({ + block: "center", + }); + } + + target.scrollIntoView({ block: "nearest" }); +}; + +/** + * Walk backwards along user input and forward through entity title to try + * and replace more of the user's text with entity. + */ +function getFullMatchOffset( + documentText: string, + entryText: string, + offset: number +): number { + let triggerOffset = offset; + for (let i = triggerOffset; i <= entryText.length; i++) { + if (documentText.substr(-i) === entryText.substr(0, i)) { + triggerOffset = i; + } + } + return triggerOffset; +} + +/** + * Split Lexical TextNode and return a new TextNode only containing matched text. + * Common use cases include: removing the node, replacing with a new node. + */ +function $splitNodeContainingQuery(match: MenuTextMatch): TextNode | null { + const selection = $getSelection(); + if (!$isRangeSelection(selection) || !selection.isCollapsed()) { + return null; + } + const anchor = selection.anchor; + if (anchor.type !== "text") { + return null; + } + const anchorNode = anchor.getNode(); + if (!anchorNode.isSimpleText()) { + return null; + } + const selectionOffset = anchor.offset; + const textContent = anchorNode.getTextContent().slice(0, selectionOffset); + const characterOffset = match.replaceableString.length; + const queryOffset = getFullMatchOffset( + textContent, + match.matchingString, + characterOffset + ); + const startOffset = selectionOffset - queryOffset; + if (startOffset < 0) { + return null; + } + let newNode; + if (startOffset === 0) { + [newNode] = anchorNode.splitText(selectionOffset); + } else { + [, newNode] = anchorNode.splitText(startOffset, selectionOffset); + } + + return newNode; +} + +// Got from https://stackoverflow.com/a/42543908/2013580 +export function getScrollParent( + element: HTMLElement, + includeHidden: boolean +): HTMLElement | HTMLBodyElement { + let style = getComputedStyle(element); + const excludeStaticParent = style.position === "absolute"; + const overflowRegex = includeHidden + ? /(auto|scroll|hidden)/ + : /(auto|scroll)/; + if (style.position === "fixed") { + return document.body; + } + for ( + let parent: HTMLElement | null = element; + (parent = parent.parentElement); + + ) { + style = getComputedStyle(parent); + if (excludeStaticParent && style.position === "static") { + continue; + } + if ( + overflowRegex.test(style.overflow + style.overflowY + style.overflowX) + ) { + return parent; + } + } + return document.body; +} + +function isTriggerVisibleInNearestScrollContainer( + targetElement: HTMLElement, + containerElement: HTMLElement +): boolean { + const tRect = targetElement.getBoundingClientRect(); + const cRect = containerElement.getBoundingClientRect(); + return tRect.top > cRect.top && tRect.top < cRect.bottom; +} + +// Reposition the menu on scroll, window resize, and element resize. +export function useDynamicPositioning( + resolution: typeof $state, + targetElement: HTMLElement | null, + onReposition: () => void, + onVisibilityChange?: (isInView: boolean) => void +) { + const [editor] = useLexicalComposerContext(); + $effect(() => { + if (targetElement != null && resolution != null) { + const rootElement = editor.getRootElement(); + const rootScrollParent = + rootElement != null + ? getScrollParent(rootElement, false) + : document.body; + let ticking = false; + let previousIsInView = isTriggerVisibleInNearestScrollContainer( + targetElement, + rootScrollParent + ); + const handleScroll = function () { + if (!ticking) { + window.requestAnimationFrame(function () { + onReposition(); + ticking = false; + }); + ticking = true; + } + const isInView = isTriggerVisibleInNearestScrollContainer( + targetElement, + rootScrollParent + ); + if (isInView !== previousIsInView) { + previousIsInView = isInView; + if (onVisibilityChange != null) { + onVisibilityChange(isInView); + } + } + }; + const resizeObserver = new ResizeObserver(onReposition); + window.addEventListener("resize", onReposition); + document.addEventListener("scroll", handleScroll, { + capture: true, + passive: true, + }); + resizeObserver.observe(targetElement); + return () => { + resizeObserver.unobserve(targetElement); + window.removeEventListener("resize", onReposition); + document.removeEventListener("scroll", handleScroll, true); + }; + } + }); +} + +export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{ + index: number; + option: MenuOption; +}> = createCommand("SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND"); + +export function LexicalMenu({ + close, + editor, + anchorElementRef, + resolution, + options, + menuRenderFn, + onSelectOption, + shouldSplitNodeWithQuery = false, + commandPriority = COMMAND_PRIORITY_LOW, +}: { + close: () => void; + editor: LexicalEditor; + anchorElementRef: HTMLElement; + resolution: MenuResolution; + options: Array; + shouldSplitNodeWithQuery?: boolean; + menuRenderFn: MenuRenderFn; + onSelectOption: ( + option: TOption, + textNodeContainingQuery: TextNode | null, + closeMenu: () => void, + matchingString: string + ) => void; + commandPriority?: CommandListenerPriority; +}): SvelteComponent | null { + const [selectedIndex, setHighlightedIndex] = useState(null); + + const matchingString = resolution.match && resolution.match.matchingString; + + $effect(() => { + setHighlightedIndex(0); + }); + + const selectOptionAndCleanUp = $derived((selectedEntry: TOption) => { + editor.update(() => { + const textNodeContainingQuery = + resolution.match != null && shouldSplitNodeWithQuery + ? $splitNodeContainingQuery(resolution.match) + : null; + + onSelectOption( + selectedEntry, + textNodeContainingQuery, + close, + resolution.match ? resolution.match.matchingString : "" + ); + }); + }); + + const updateSelectedIndex = useCallback( + (index: number) => { + const rootElem = editor.getRootElement(); + if (rootElem !== null) { + rootElem.setAttribute( + "aria-activedescendant", + "typeahead-item-" + index + ); + setHighlightedIndex(index); + } + }, + [editor] + ); + + $effect(() => { + return () => { + const rootElem = editor.getRootElement(); + if (rootElem !== null) { + rootElem.removeAttribute("aria-activedescendant"); + } + }; + }); + + useLayoutEffect(() => { + if (options === null) { + setHighlightedIndex(null); + } else if (selectedIndex() === null) { + updateSelectedIndex(0); + } + }); + + $effect(() => { + return mergeRegister( + editor.registerCommand( + SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, + ({ option }) => { + if (option.ref && option.ref != null) { + scrollIntoViewIfNeeded(option.ref); + return true; + } + + return false; + }, + commandPriority + ) + ); + }); + + $effect(() => { + return mergeRegister( + editor.registerCommand( + KEY_ARROW_DOWN_COMMAND, + (payload) => { + const event = payload; + if (options !== null && options.length && selectedIndex() !== null) { + const newSelectedIndex = + selectedIndex() !== options.length - 1 ? selectedIndex() + 1 : 0; + updateSelectedIndex(newSelectedIndex); + const option = options[newSelectedIndex]; + if (option.ref != null && option.ref) { + editor.dispatchCommand( + SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, + { + index: newSelectedIndex, + option, + } + ); + } + event.preventDefault(); + event.stopImmediatePropagation(); + } + return true; + }, + commandPriority + ), + editor.registerCommand( + KEY_ARROW_UP_COMMAND, + (payload) => { + const event = payload; + let ret = selectedIndex(); + if (options !== null && options.length && ret !== null) { + const newSelectedIndex = ret !== 0 ? ret - 1 : options.length - 1; + updateSelectedIndex(newSelectedIndex); + const option = options[newSelectedIndex]; + if (option.ref != null && option.ref) { + scrollIntoViewIfNeeded(option.ref); + } + event.preventDefault(); + event.stopImmediatePropagation(); + } + return true; + }, + commandPriority + ), + editor.registerCommand( + KEY_ESCAPE_COMMAND, + (payload) => { + const event = payload; + event.preventDefault(); + event.stopImmediatePropagation(); + close(); + return true; + }, + commandPriority + ), + editor.registerCommand( + KEY_TAB_COMMAND, + (payload) => { + const event = payload; + if ( + options === null || + selectedIndex === null || + options[selectedIndex] == null + ) { + return false; + } + event.preventDefault(); + event.stopImmediatePropagation(); + selectOptionAndCleanUp(options[selectedIndex]); + return true; + }, + commandPriority + ), + editor.registerCommand( + KEY_ENTER_COMMAND, + (event: KeyboardEvent | null) => { + if ( + options === null || + selectedIndex === null || + options[selectedIndex] == null + ) { + return false; + } + if (event !== null) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + selectOptionAndCleanUp(options[selectedIndex]); + return true; + }, + commandPriority + ) + ); + }, [ + selectOptionAndCleanUp, + close, + editor, + options, + selectedIndex, + updateSelectedIndex, + commandPriority, + ]); + + const listItemProps = $derived(() => ({ + options, + selectOptionAndCleanUp, + selectedIndex, + setHighlightedIndex, + })); + + return menuRenderFn( + anchorElementRef, + listItemProps, + resolution.match ? resolution.match.matchingString : "" + ); +} + +export function useMenuAnchorRef( + resolution: MenuResolution | null, + setResolution: (r: MenuResolution | null) => void, + className?: string +): MutableRefObject { + const [editor] = useLexicalComposerContext(); + const anchorElementRef = useRef(document.createElement("div")); + const positionMenu = useCallback(() => { + anchorElementRef.current.style.top = anchorElementRef.current.style.bottom; + const rootElement = editor.getRootElement(); + const containerDiv = anchorElementRef.current; + + const menuEle = containerDiv.firstChild as HTMLElement; + if (rootElement !== null && resolution !== null) { + const { left, top, width, height } = resolution.getRect(); + const anchorHeight = anchorElementRef.current.offsetHeight; // use to position under anchor + containerDiv.style.top = `${ + top + window.pageYOffset + anchorHeight + 3 + }px`; + containerDiv.style.left = `${left + window.pageXOffset}px`; + containerDiv.style.height = `${height}px`; + containerDiv.style.width = `${width}px`; + if (menuEle !== null) { + menuEle.style.top = `${top}`; + const menuRect = menuEle.getBoundingClientRect(); + const menuHeight = menuRect.height; + const menuWidth = menuRect.width; + + const rootElementRect = rootElement.getBoundingClientRect(); + + if (left + menuWidth > rootElementRect.right) { + containerDiv.style.left = `${ + rootElementRect.right - menuWidth + window.pageXOffset + }px`; + } + if ( + (top + menuHeight > window.innerHeight || + top + menuHeight > rootElementRect.bottom) && + top - rootElementRect.top > menuHeight + ) { + containerDiv.style.top = `${ + top - menuHeight + window.pageYOffset - height + }px`; + } + } + + if (!containerDiv.isConnected) { + if (className != null) { + containerDiv.className = className; + } + containerDiv.setAttribute("aria-label", "Typeahead menu"); + containerDiv.setAttribute("id", "typeahead-menu"); + containerDiv.setAttribute("role", "listbox"); + containerDiv.style.display = "block"; + containerDiv.style.position = "absolute"; + document.body.append(containerDiv); + } + anchorElementRef.current = containerDiv; + rootElement.setAttribute("aria-controls", "typeahead-menu"); + } + }, [editor, resolution, className]); + + useEffect(() => { + const rootElement = editor.getRootElement(); + if (resolution !== null) { + positionMenu(); + return () => { + if (rootElement !== null) { + rootElement.removeAttribute("aria-controls"); + } + + const containerDiv = anchorElementRef.current; + if (containerDiv !== null && containerDiv.isConnected) { + containerDiv.remove(); + } + }; + } + }, [editor, positionMenu, resolution]); + + const onVisibilityChange = useCallback( + (isInView: boolean) => { + if (resolution !== null) { + if (!isInView) { + setResolution(null); + } + } + }, + [resolution, setResolution] + ); + + useDynamicPositioning( + resolution, + anchorElementRef.current, + positionMenu, + onVisibilityChange + ); + + return anchorElementRef; +} + +export type TriggerFn = ( + text: string, + editor: LexicalEditor +) => MenuTextMatch | null; diff --git a/src/shared/useCanShowPlaceholder.ts b/src/shared/useCanShowPlaceholder.ts new file mode 100644 index 0000000..e853f5f --- /dev/null +++ b/src/shared/useCanShowPlaceholder.ts @@ -0,0 +1,49 @@ +/** + * 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 { LexicalEditor } from "lexical"; + +import { $canShowPlaceholderCurry } from "@lexical/text"; +import { mergeRegister } from "@lexical/utils"; +import { useState } from "react"; +import useLayoutEffect from "shared/useLayoutEffect.svelte"; + +function canShowPlaceholderFromCurrentEditorState( + editor: LexicalEditor +): boolean { + const currentCanShowPlaceholder = editor + .getEditorState() + .read($canShowPlaceholderCurry(editor.isComposing())); + + return currentCanShowPlaceholder; +} + +export function useCanShowPlaceholder(editor: LexicalEditor): boolean { + const [canShowPlaceholder, setCanShowPlaceholder] = useState(() => + canShowPlaceholderFromCurrentEditorState(editor) + ); + + useLayoutEffect(() => { + function resetCanShowPlaceholder() { + const currentCanShowPlaceholder = + canShowPlaceholderFromCurrentEditorState(editor); + setCanShowPlaceholder(currentCanShowPlaceholder); + } + resetCanShowPlaceholder(); + return mergeRegister( + editor.registerUpdateListener(() => { + resetCanShowPlaceholder(); + }), + editor.registerEditableListener(() => { + resetCanShowPlaceholder(); + }) + ); + }, [editor]); + + return canShowPlaceholder; +} diff --git a/src/shared/useCharacterLimit.ts b/src/shared/useCharacterLimit.ts new file mode 100644 index 0000000..df592eb --- /dev/null +++ b/src/shared/useCharacterLimit.ts @@ -0,0 +1,293 @@ +/** + * 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 {LexicalEditor, LexicalNode} from 'lexical'; + +import { + $createOverflowNode, + $isOverflowNode, + OverflowNode, +} from '@lexical/overflow'; +import {$rootTextContent} from '@lexical/text'; +import {$dfs, mergeRegister} from '@lexical/utils'; +import { + $getSelection, + $isLeafNode, + $isRangeSelection, + $isTextNode, + $setSelection, +} from 'lexical'; +import {useEffect} from 'react'; +import invariant from 'shared/invariant'; + +type OptionalProps = { + remainingCharacters?: (characters: number) => void; + strlen?: (input: string) => number; +}; + +export function useCharacterLimit( + editor: LexicalEditor, + maxCharacters: number, + optional: OptionalProps = Object.freeze({}), +): void { + const { + strlen = (input) => input.length, + // UTF-16 + remainingCharacters = () => { + return; + }, + } = optional; + + useEffect(() => { + if (!editor.hasNodes([OverflowNode])) { + invariant( + false, + 'useCharacterLimit: OverflowNode not registered on editor', + ); + } + }, [editor]); + + useEffect(() => { + let text = editor.getEditorState().read($rootTextContent); + let lastComputedTextLength = 0; + + return mergeRegister( + editor.registerTextContentListener((currentText: string) => { + text = currentText; + }), + editor.registerUpdateListener(({dirtyLeaves, dirtyElements}) => { + const isComposing = editor.isComposing(); + const hasContentChanges = + dirtyLeaves.size > 0 || dirtyElements.size > 0; + + if (isComposing || !hasContentChanges) { + return; + } + + const textLength = strlen(text); + const textLengthAboveThreshold = + textLength > maxCharacters || + (lastComputedTextLength !== null && + lastComputedTextLength > maxCharacters); + const diff = maxCharacters - textLength; + + remainingCharacters(diff); + + if (lastComputedTextLength === null || textLengthAboveThreshold) { + const offset = findOffset(text, maxCharacters, strlen); + editor.update( + () => { + $wrapOverflowedNodes(offset); + }, + { + tag: 'history-merge', + }, + ); + } + + lastComputedTextLength = textLength; + }), + ); + }, [editor, maxCharacters, remainingCharacters, strlen]); +} + +function findOffset( + text: string, + maxCharacters: number, + strlen: (input: string) => number, +): number { + // @ts-ignore This is due to be added in a later version of TS + const Segmenter = Intl.Segmenter; + let offsetUtf16 = 0; + let offset = 0; + + if (typeof Segmenter === 'function') { + const segmenter = new Segmenter(); + const graphemes = segmenter.segment(text); + + for (const {segment: grapheme} of graphemes) { + const nextOffset = offset + strlen(grapheme); + + if (nextOffset > maxCharacters) { + break; + } + + offset = nextOffset; + offsetUtf16 += grapheme.length; + } + } else { + const codepoints = Array.from(text); + const codepointsLength = codepoints.length; + + for (let i = 0; i < codepointsLength; i++) { + const codepoint = codepoints[i]; + const nextOffset = offset + strlen(codepoint); + + if (nextOffset > maxCharacters) { + break; + } + + offset = nextOffset; + offsetUtf16 += codepoint.length; + } + } + + return offsetUtf16; +} + +function $wrapOverflowedNodes(offset: number): void { + const dfsNodes = $dfs(); + const dfsNodesLength = dfsNodes.length; + let accumulatedLength = 0; + + for (let i = 0; i < dfsNodesLength; i += 1) { + const {node} = dfsNodes[i]; + + if ($isOverflowNode(node)) { + const previousLength = accumulatedLength; + const nextLength = accumulatedLength + node.getTextContentSize(); + + if (nextLength <= offset) { + const parent = node.getParent(); + const previousSibling = node.getPreviousSibling(); + const nextSibling = node.getNextSibling(); + $unwrapNode(node); + const selection = $getSelection(); + + // Restore selection when the overflow children are removed + if ( + $isRangeSelection(selection) && + (!selection.anchor.getNode().isAttached() || + !selection.focus.getNode().isAttached()) + ) { + if ($isTextNode(previousSibling)) { + previousSibling.select(); + } else if ($isTextNode(nextSibling)) { + nextSibling.select(); + } else if (parent !== null) { + parent.select(); + } + } + } else if (previousLength < offset) { + const descendant = node.getFirstDescendant(); + const descendantLength = + descendant !== null ? descendant.getTextContentSize() : 0; + const previousPlusDescendantLength = previousLength + descendantLength; + // For simple text we can redimension the overflow into a smaller and more accurate + // container + const firstDescendantIsSimpleText = + $isTextNode(descendant) && descendant.isSimpleText(); + const firstDescendantDoesNotOverflow = + previousPlusDescendantLength <= offset; + + if (firstDescendantIsSimpleText || firstDescendantDoesNotOverflow) { + $unwrapNode(node); + } + } + } else if ($isLeafNode(node)) { + const previousAccumulatedLength = accumulatedLength; + accumulatedLength += node.getTextContentSize(); + + if (accumulatedLength > offset && !$isOverflowNode(node.getParent())) { + const previousSelection = $getSelection(); + let overflowNode; + + // For simple text we can improve the limit accuracy by splitting the TextNode + // on the split point + if ( + previousAccumulatedLength < offset && + $isTextNode(node) && + node.isSimpleText() + ) { + const [, overflowedText] = node.splitText( + offset - previousAccumulatedLength, + ); + overflowNode = $wrapNode(overflowedText); + } else { + overflowNode = $wrapNode(node); + } + + if (previousSelection !== null) { + $setSelection(previousSelection); + } + + mergePrevious(overflowNode); + } + } + } +} + +function $wrapNode(node: LexicalNode): OverflowNode { + const overflowNode = $createOverflowNode(); + node.insertBefore(overflowNode); + overflowNode.append(node); + return overflowNode; +} + +function $unwrapNode(node: OverflowNode): LexicalNode | null { + const children = node.getChildren(); + const childrenLength = children.length; + + for (let i = 0; i < childrenLength; i++) { + node.insertBefore(children[i]); + } + + node.remove(); + return childrenLength > 0 ? children[childrenLength - 1] : null; +} + +export function mergePrevious(overflowNode: OverflowNode): void { + const previousNode = overflowNode.getPreviousSibling(); + + if (!$isOverflowNode(previousNode)) { + return; + } + + const firstChild = overflowNode.getFirstChild(); + const previousNodeChildren = previousNode.getChildren(); + const previousNodeChildrenLength = previousNodeChildren.length; + + if (firstChild === null) { + overflowNode.append(...previousNodeChildren); + } else { + for (let i = 0; i < previousNodeChildrenLength; i++) { + firstChild.insertBefore(previousNodeChildren[i]); + } + } + + const selection = $getSelection(); + + if ($isRangeSelection(selection)) { + const anchor = selection.anchor; + const anchorNode = anchor.getNode(); + const focus = selection.focus; + const focusNode = anchor.getNode(); + + if (anchorNode.is(previousNode)) { + anchor.set(overflowNode.getKey(), anchor.offset, 'element'); + } else if (anchorNode.is(overflowNode)) { + anchor.set( + overflowNode.getKey(), + previousNodeChildrenLength + anchor.offset, + 'element', + ); + } + + if (focusNode.is(previousNode)) { + focus.set(overflowNode.getKey(), focus.offset, 'element'); + } else if (focusNode.is(overflowNode)) { + focus.set( + overflowNode.getKey(), + previousNodeChildrenLength + focus.offset, + 'element', + ); + } + } + + previousNode.remove(); +} diff --git a/src/shared/useDecorators.svelte.ts b/src/shared/useDecorators.svelte.ts new file mode 100644 index 0000000..76c26b6 --- /dev/null +++ b/src/shared/useDecorators.svelte.ts @@ -0,0 +1,56 @@ +/** + * 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 { LexicalEditor } from "lexical"; + +import { createPortal, useEffect, useMemo, useState } from "../react.svelte"; +import useLayoutEffect from "shared/useLayoutEffect.svelte"; +import { SvelteComponent, flushSync } from "svelte"; + +export function useDecorators(editor: LexicalEditor) { + const [decorators, setDecorators] = useState>( + editor.getDecorators() + ); + + // Subscribe to changes + useLayoutEffect(() => { + return editor.registerDecoratorListener( + (nextDecorators) => { + flushSync(() => { + setDecorators(nextDecorators); + }); + } + ); + }); + + useEffect(() => { + // If the content editable mounts before the subscription is added, then + // nothing will be rendered on initial pass. We can get around that by + // ensuring that we set the value. + setDecorators(editor.getDecorators()); + }, [editor]); + + // Return decorators defined as React Portals + + $effect(() => { + const decoratedPortals = []; + const decoratorKeys = Object.keys(decorators); + + for (let i = 0; i < decoratorKeys.length; i++) { + const nodeKey = decoratorKeys[i]; + + const element = editor.getElementByKey(nodeKey); + + if (element !== null) { + decoratedPortals.push( + createPortal(decorators()[nodeKey], element, nodeKey) + ); + } + } + }); +} diff --git a/src/shared/useHistory.ts b/src/shared/useHistory.ts new file mode 100644 index 0000000..66ba0bc --- /dev/null +++ b/src/shared/useHistory.ts @@ -0,0 +1,28 @@ +/** + * 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 {HistoryState} from '@lexical/history'; +import type {LexicalEditor} from 'lexical'; + +import {createEmptyHistoryState, registerHistory} from '@lexical/history'; +import {useEffect, useMemo} from 'react'; + +export function useHistory( + editor: LexicalEditor, + externalHistoryState?: HistoryState, + delay = 1000, +): void { + const historyState: HistoryState = useMemo( + () => externalHistoryState || createEmptyHistoryState(), + [externalHistoryState], + ); + + useEffect(() => { + return registerHistory(editor, historyState, delay); + }, [delay, editor, historyState]); +} diff --git a/src/shared/useList.ts b/src/shared/useList.ts new file mode 100644 index 0000000..f7ba6b8 --- /dev/null +++ b/src/shared/useList.ts @@ -0,0 +1,65 @@ +/** + * 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 {LexicalEditor} from 'lexical'; + +import { + $handleListInsertParagraph, + INSERT_ORDERED_LIST_COMMAND, + INSERT_UNORDERED_LIST_COMMAND, + insertList, + REMOVE_LIST_COMMAND, + removeList, +} from '@lexical/list'; +import {mergeRegister} from '@lexical/utils'; +import {COMMAND_PRIORITY_LOW, INSERT_PARAGRAPH_COMMAND} from 'lexical'; +import {useEffect} from 'react'; + +export function useList(editor: LexicalEditor): void { + useEffect(() => { + return mergeRegister( + editor.registerCommand( + INSERT_ORDERED_LIST_COMMAND, + () => { + insertList(editor, 'number'); + return true; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + INSERT_UNORDERED_LIST_COMMAND, + () => { + insertList(editor, 'bullet'); + return true; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + REMOVE_LIST_COMMAND, + () => { + removeList(editor); + return true; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + INSERT_PARAGRAPH_COMMAND, + () => { + const hasHandledInsertParagraph = $handleListInsertParagraph(); + + if (hasHandledInsertParagraph) { + return true; + } + + return false; + }, + COMMAND_PRIORITY_LOW, + ), + ); + }, [editor]); +} diff --git a/src/shared/usePlainTextSetup.ts b/src/shared/usePlainTextSetup.ts new file mode 100644 index 0000000..f9f9964 --- /dev/null +++ b/src/shared/usePlainTextSetup.ts @@ -0,0 +1,26 @@ +/** + * 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 { LexicalEditor } from "lexical"; + +import { registerDragonSupport } from "@lexical/dragon"; +import { registerPlainText } from "@lexical/plain-text"; +import { mergeRegister } from "@lexical/utils"; +import useLayoutEffect from "shared/useLayoutEffect.svelte"; + +export function usePlainTextSetup(editor: LexicalEditor): void { + useLayoutEffect(() => { + return mergeRegister( + registerPlainText(editor), + registerDragonSupport(editor) + ); + + // We only do this for init + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editor]); +} diff --git a/src/shared/useRichTextSetup.ts b/src/shared/useRichTextSetup.ts new file mode 100644 index 0000000..5a83ea0 --- /dev/null +++ b/src/shared/useRichTextSetup.ts @@ -0,0 +1,26 @@ +/** + * 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 { LexicalEditor } from "lexical"; + +import { registerDragonSupport } from "@lexical/dragon"; +import { registerRichText } from "@lexical/rich-text"; +import { mergeRegister } from "@lexical/utils"; +import useLayoutEffect from "shared/useLayoutEffect.svelte"; + +export function useRichTextSetup(editor: LexicalEditor): void { + useLayoutEffect(() => { + return mergeRegister( + registerRichText(editor), + registerDragonSupport(editor) + ); + + // We only do this for init + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editor]); +} diff --git a/src/shared/useYjsCollaboration.tsx b/src/shared/useYjsCollaboration.tsx new file mode 100644 index 0000000..4da2ad1 --- /dev/null +++ b/src/shared/useYjsCollaboration.tsx @@ -0,0 +1,421 @@ +/** + * 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 {Binding, ExcludedProperties, Provider} from '@lexical/yjs'; +import type {LexicalEditor} from 'lexical'; + +import {mergeRegister} from '@lexical/utils'; +import { + CONNECTED_COMMAND, + createBinding, + createUndoManager, + initLocalState, + setLocalStateFocus, + syncCursorPositions, + syncLexicalUpdateToYjs, + syncYjsChangesToLexical, + TOGGLE_CONNECT_COMMAND, +} from '@lexical/yjs'; +import { + $createParagraphNode, + $getRoot, + $getSelection, + BLUR_COMMAND, + CAN_REDO_COMMAND, + CAN_UNDO_COMMAND, + COMMAND_PRIORITY_EDITOR, + FOCUS_COMMAND, + REDO_COMMAND, + UNDO_COMMAND, +} from 'lexical'; +import * as React from 'react'; +import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {createPortal} from 'react-dom'; +import {Doc, Transaction, UndoManager, YEvent} from 'yjs'; + +import {InitialEditorStateType} from '../LexicalComposer'; + +export type CursorsContainerRef = React.MutableRefObject; + +export function useYjsCollaboration( + editor: LexicalEditor, + id: string, + provider: Provider, + docMap: Map, + name: string, + color: string, + shouldBootstrap: boolean, + cursorsContainerRef?: CursorsContainerRef, + initialEditorState?: InitialEditorStateType, + excludedProperties?: ExcludedProperties, + awarenessData?: object, +): [JSX.Element, Binding] { + const isReloadingDoc = useRef(false); + const [doc, setDoc] = useState(docMap.get(id)); + + const binding = useMemo( + () => createBinding(editor, provider, id, doc, docMap, excludedProperties), + [editor, provider, id, docMap, doc, excludedProperties], + ); + + const connect = useCallback(() => { + provider.connect(); + }, [provider]); + + const disconnect = useCallback(() => { + try { + provider.disconnect(); + } catch (e) { + // Do nothing + } + }, [provider]); + + useEffect(() => { + const {root} = binding; + const {awareness} = provider; + + const onStatus = ({status}: {status: string}) => { + editor.dispatchCommand(CONNECTED_COMMAND, status === 'connected'); + }; + + const onSync = (isSynced: boolean) => { + if ( + shouldBootstrap && + isSynced && + root.isEmpty() && + root._xmlText._length === 0 && + isReloadingDoc.current === false + ) { + initializeEditor(editor, initialEditorState); + } + + isReloadingDoc.current = false; + }; + + const onAwarenessUpdate = () => { + syncCursorPositions(binding, provider); + }; + + const onYjsTreeChanges = ( + // The below `any` type is taken directly from the vendor types for YJS. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + events: Array>, + transaction: Transaction, + ) => { + const origin = transaction.origin; + if (origin !== binding) { + const isFromUndoManger = origin instanceof UndoManager; + syncYjsChangesToLexical(binding, provider, events, isFromUndoManger); + } + }; + + initLocalState( + provider, + name, + color, + document.activeElement === editor.getRootElement(), + awarenessData || {}, + ); + + const onProviderDocReload = (ydoc: Doc) => { + clearEditorSkipCollab(editor, binding); + setDoc(ydoc); + docMap.set(id, ydoc); + isReloadingDoc.current = true; + }; + + provider.on('reload', onProviderDocReload); + provider.on('status', onStatus); + provider.on('sync', onSync); + awareness.on('update', onAwarenessUpdate); + // This updates the local editor state when we recieve updates from other clients + root.getSharedType().observeDeep(onYjsTreeChanges); + const removeListener = editor.registerUpdateListener( + ({ + prevEditorState, + editorState, + dirtyLeaves, + dirtyElements, + normalizedNodes, + tags, + }) => { + if (tags.has('skip-collab') === false) { + syncLexicalUpdateToYjs( + binding, + provider, + prevEditorState, + editorState, + dirtyElements, + dirtyLeaves, + normalizedNodes, + tags, + ); + } + }, + ); + connect(); + + return () => { + if (isReloadingDoc.current === false) { + disconnect(); + } + + provider.off('sync', onSync); + provider.off('status', onStatus); + provider.off('reload', onProviderDocReload); + awareness.off('update', onAwarenessUpdate); + root.getSharedType().unobserveDeep(onYjsTreeChanges); + docMap.delete(id); + removeListener(); + }; + }, [ + binding, + color, + connect, + disconnect, + docMap, + editor, + id, + initialEditorState, + name, + provider, + shouldBootstrap, + awarenessData, + ]); + const cursorsContainer = useMemo(() => { + const ref = (element: null | HTMLElement) => { + binding.cursorsContainer = element; + }; + + return createPortal( +
, + (cursorsContainerRef && cursorsContainerRef.current) || document.body, + ); + }, [binding, cursorsContainerRef]); + + useEffect(() => { + return editor.registerCommand( + TOGGLE_CONNECT_COMMAND, + (payload) => { + if (connect !== undefined && disconnect !== undefined) { + const shouldConnect = payload; + + if (shouldConnect) { + // eslint-disable-next-line no-console + console.log('Collaboration connected!'); + connect(); + } else { + // eslint-disable-next-line no-console + console.log('Collaboration disconnected!'); + disconnect(); + } + } + + return true; + }, + COMMAND_PRIORITY_EDITOR, + ); + }, [connect, disconnect, editor]); + + return [cursorsContainer, binding]; +} + +export function useYjsFocusTracking( + editor: LexicalEditor, + provider: Provider, + name: string, + color: string, + awarenessData?: object, +) { + useEffect(() => { + return mergeRegister( + editor.registerCommand( + FOCUS_COMMAND, + () => { + setLocalStateFocus(provider, name, color, true, awarenessData || {}); + return false; + }, + COMMAND_PRIORITY_EDITOR, + ), + editor.registerCommand( + BLUR_COMMAND, + () => { + setLocalStateFocus(provider, name, color, false, awarenessData || {}); + return false; + }, + COMMAND_PRIORITY_EDITOR, + ), + ); + }, [color, editor, name, provider, awarenessData]); +} + +export function useYjsHistory( + editor: LexicalEditor, + binding: Binding, +): () => void { + const undoManager = useMemo( + () => createUndoManager(binding, binding.root.getSharedType()), + [binding], + ); + + useEffect(() => { + const undo = () => { + undoManager.undo(); + }; + + const redo = () => { + undoManager.redo(); + }; + + return mergeRegister( + editor.registerCommand( + UNDO_COMMAND, + () => { + undo(); + return true; + }, + COMMAND_PRIORITY_EDITOR, + ), + editor.registerCommand( + REDO_COMMAND, + () => { + redo(); + return true; + }, + COMMAND_PRIORITY_EDITOR, + ), + ); + }); + const clearHistory = useCallback(() => { + undoManager.clear(); + }, [undoManager]); + + // Exposing undo and redo states + React.useEffect(() => { + const updateUndoRedoStates = () => { + editor.dispatchCommand( + CAN_UNDO_COMMAND, + undoManager.undoStack.length > 0, + ); + editor.dispatchCommand( + CAN_REDO_COMMAND, + undoManager.redoStack.length > 0, + ); + }; + undoManager.on('stack-item-added', updateUndoRedoStates); + undoManager.on('stack-item-popped', updateUndoRedoStates); + undoManager.on('stack-cleared', updateUndoRedoStates); + return () => { + undoManager.off('stack-item-added', updateUndoRedoStates); + undoManager.off('stack-item-popped', updateUndoRedoStates); + undoManager.off('stack-cleared', updateUndoRedoStates); + }; + }, [editor, undoManager]); + + return clearHistory; +} + +function initializeEditor( + editor: LexicalEditor, + initialEditorState?: InitialEditorStateType, +): void { + editor.update( + () => { + const root = $getRoot(); + + if (root.isEmpty()) { + if (initialEditorState) { + switch (typeof initialEditorState) { + case 'string': { + const parsedEditorState = + editor.parseEditorState(initialEditorState); + editor.setEditorState(parsedEditorState, {tag: 'history-merge'}); + break; + } + case 'object': { + editor.setEditorState(initialEditorState, {tag: 'history-merge'}); + break; + } + case 'function': { + editor.update( + () => { + const root1 = $getRoot(); + if (root1.isEmpty()) { + initialEditorState(editor); + } + }, + {tag: 'history-merge'}, + ); + break; + } + } + } else { + const paragraph = $createParagraphNode(); + root.append(paragraph); + const {activeElement} = document; + + if ( + $getSelection() !== null || + (activeElement !== null && + activeElement === editor.getRootElement()) + ) { + paragraph.select(); + } + } + } + }, + { + tag: 'history-merge', + }, + ); +} + +function clearEditorSkipCollab(editor: LexicalEditor, binding: Binding) { + // reset editor state + editor.update( + () => { + const root = $getRoot(); + root.clear(); + root.select(); + }, + { + tag: 'skip-collab', + }, + ); + + if (binding.cursors == null) { + return; + } + + const cursors = binding.cursors; + + if (cursors == null) { + return; + } + const cursorsContainer = binding.cursorsContainer; + + if (cursorsContainer == null) { + return; + } + + // reset cursors in dom + const cursorsArr = Array.from(cursors.values()); + + for (let i = 0; i < cursorsArr.length; i++) { + const cursor = cursorsArr[i]; + const selection = cursor.selection; + + if (selection && selection.selections != null) { + const selections = selection.selections; + + for (let j = 0; j < selections.length; j++) { + cursorsContainer.removeChild(selections[i]); + } + } + } +} diff --git a/src/themes/CommentEditorTheme.css b/src/themes/CommentEditorTheme.css new file mode 100644 index 0000000..e50318c --- /dev/null +++ b/src/themes/CommentEditorTheme.css @@ -0,0 +1,13 @@ +/** + * 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. + * + * + */ + +.CommentEditorTheme__paragraph { + margin: 0; + position: 'relative'; +} diff --git a/src/themes/CommentEditorTheme.ts b/src/themes/CommentEditorTheme.ts new file mode 100644 index 0000000..cb31e69 --- /dev/null +++ b/src/themes/CommentEditorTheme.ts @@ -0,0 +1,20 @@ +/** + * 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 {EditorThemeClasses} from 'lexical'; + +import './CommentEditorTheme.css'; + +import baseTheme from './PlaygroundEditorTheme'; + +const theme: EditorThemeClasses = { + ...baseTheme, + paragraph: 'CommentEditorTheme__paragraph', +}; + +export default theme; diff --git a/src/themes/PlaygroundEditorTheme.css b/src/themes/PlaygroundEditorTheme.css new file mode 100644 index 0000000..e812a5a --- /dev/null +++ b/src/themes/PlaygroundEditorTheme.css @@ -0,0 +1,441 @@ +/** + * 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. + * + * + */ +.PlaygroundEditorTheme__ltr { + text-align: left; +} +.PlaygroundEditorTheme__rtl { + text-align: right; +} +.PlaygroundEditorTheme__paragraph { + margin: 0; + position: relative; +} +.PlaygroundEditorTheme__quote { + margin: 0; + margin-left: 20px; + margin-bottom: 10px; + font-size: 15px; + color: rgb(101, 103, 107); + border-left-color: rgb(206, 208, 212); + border-left-width: 4px; + border-left-style: solid; + padding-left: 16px; +} +.PlaygroundEditorTheme__h1 { + font-size: 24px; + color: rgb(5, 5, 5); + font-weight: 400; + margin: 0; +} +.PlaygroundEditorTheme__h2 { + font-size: 15px; + color: rgb(101, 103, 107); + font-weight: 700; + margin: 0; + text-transform: uppercase; +} +.PlaygroundEditorTheme__h3 { + font-size: 12px; + margin: 0; + text-transform: uppercase; +} +.PlaygroundEditorTheme__indent { + --lexical-indent-base-value: 40px; +} +.PlaygroundEditorTheme__textBold { + font-weight: bold; +} +.PlaygroundEditorTheme__textItalic { + font-style: italic; +} +.PlaygroundEditorTheme__textUnderline { + text-decoration: underline; +} +.PlaygroundEditorTheme__textStrikethrough { + text-decoration: line-through; +} +.PlaygroundEditorTheme__textUnderlineStrikethrough { + text-decoration: underline line-through; +} +.PlaygroundEditorTheme__textSubscript { + font-size: 0.8em; + vertical-align: sub !important; +} +.PlaygroundEditorTheme__textSuperscript { + font-size: 0.8em; + vertical-align: super; +} +.PlaygroundEditorTheme__textCode { + background-color: rgb(240, 242, 245); + padding: 1px 0.25rem; + font-family: Menlo, Consolas, Monaco, monospace; + font-size: 94%; +} +.PlaygroundEditorTheme__hashtag { + background-color: rgba(88, 144, 255, 0.15); + border-bottom: 1px solid rgba(88, 144, 255, 0.3); +} +.PlaygroundEditorTheme__link { + color: rgb(33, 111, 219); + text-decoration: none; +} +.PlaygroundEditorTheme__link:hover { + text-decoration: underline; + cursor: pointer; +} +.PlaygroundEditorTheme__code { + background-color: rgb(240, 242, 245); + font-family: Menlo, Consolas, Monaco, monospace; + display: block; + padding: 8px 8px 8px 52px; + line-height: 1.53; + font-size: 13px; + margin: 0; + margin-top: 8px; + margin-bottom: 8px; + overflow-x: auto; + position: relative; + tab-size: 2; +} +.PlaygroundEditorTheme__code:before { + content: attr(data-gutter); + position: absolute; + background-color: #eee; + left: 0; + top: 0; + border-right: 1px solid #ccc; + padding: 8px; + color: #777; + white-space: pre-wrap; + text-align: right; + min-width: 25px; +} +.PlaygroundEditorTheme__table { + border-collapse: collapse; + border-spacing: 0; + overflow-y: scroll; + overflow-x: scroll; + table-layout: fixed; + width: max-content; + margin: 30px 0; +} +.PlaygroundEditorTheme__tableSelection *::selection { + background-color: transparent; +} +.PlaygroundEditorTheme__tableSelected { + outline: 2px solid rgb(60, 132, 244); +} +.PlaygroundEditorTheme__tableCell { + border: 1px solid #bbb; + width: 75px; + min-width: 75px; + vertical-align: top; + text-align: start; + padding: 6px 8px; + position: relative; + outline: none; +} +.PlaygroundEditorTheme__tableCellSortedIndicator { + display: block; + opacity: 0.5; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 4px; + background-color: #999; +} +.PlaygroundEditorTheme__tableCellResizer { + position: absolute; + right: -4px; + height: 100%; + width: 8px; + cursor: ew-resize; + z-index: 10; + top: 0; +} +.PlaygroundEditorTheme__tableCellHeader { + background-color: #f2f3f5; + text-align: start; +} +.PlaygroundEditorTheme__tableCellSelected { + background-color: #c9dbf0; +} +.PlaygroundEditorTheme__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; +} +.PlaygroundEditorTheme__tableCellEditing { + box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); + border-radius: 3px; +} +.PlaygroundEditorTheme__tableAddColumns { + position: absolute; + top: 0; + width: 20px; + background-color: #eee; + height: 100%; + right: -25px; + animation: table-controls 0.2s ease; + border: 0; + cursor: pointer; +} +.PlaygroundEditorTheme__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; +} +.PlaygroundEditorTheme__tableAddColumns:hover { + background-color: #c9dbf0; +} +.PlaygroundEditorTheme__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; +} +.PlaygroundEditorTheme__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; +} +.PlaygroundEditorTheme__tableAddRows:hover { + background-color: #c9dbf0; +} +@keyframes table-controls { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +.PlaygroundEditorTheme__tableCellResizeRuler { + display: block; + position: absolute; + width: 1px; + background-color: rgb(60, 132, 244); + height: 100%; + top: 0; +} +.PlaygroundEditorTheme__tableCellActionButtonContainer { + display: block; + right: 5px; + top: 6px; + position: absolute; + z-index: 4; + width: 20px; + height: 20px; +} +.PlaygroundEditorTheme__tableCellActionButton { + background-color: #eee; + display: block; + border: 0; + border-radius: 20px; + width: 20px; + height: 20px; + color: #222; + cursor: pointer; +} +.PlaygroundEditorTheme__tableCellActionButton:hover { + background-color: #ddd; +} +.PlaygroundEditorTheme__characterLimit { + display: inline; + background-color: #ffbbbb !important; +} +.PlaygroundEditorTheme__ol1 { + padding: 0; + margin: 0; + list-style-position: inside; +} +.PlaygroundEditorTheme__ol2 { + padding: 0; + margin: 0; + list-style-type: upper-alpha; + list-style-position: inside; +} +.PlaygroundEditorTheme__ol3 { + padding: 0; + margin: 0; + list-style-type: lower-alpha; + list-style-position: inside; +} +.PlaygroundEditorTheme__ol4 { + padding: 0; + margin: 0; + list-style-type: upper-roman; + list-style-position: inside; +} +.PlaygroundEditorTheme__ol5 { + padding: 0; + margin: 0; + list-style-type: lower-roman; + list-style-position: inside; +} +.PlaygroundEditorTheme__ul { + padding: 0; + margin: 0; + list-style-position: inside; +} +.PlaygroundEditorTheme__listItem { + margin: 0 32px; +} +.PlaygroundEditorTheme__listItemChecked, +.PlaygroundEditorTheme__listItemUnchecked { + position: relative; + margin-left: 8px; + margin-right: 8px; + padding-left: 24px; + padding-right: 24px; + list-style-type: none; + outline: none; +} +.PlaygroundEditorTheme__listItemChecked { + text-decoration: line-through; +} +.PlaygroundEditorTheme__listItemUnchecked:before, +.PlaygroundEditorTheme__listItemChecked:before { + content: ''; + width: 16px; + height: 16px; + top: 2px; + left: 0; + cursor: pointer; + display: block; + background-size: cover; + position: absolute; +} +.PlaygroundEditorTheme__listItemUnchecked[dir='rtl']:before, +.PlaygroundEditorTheme__listItemChecked[dir='rtl']:before { + left: auto; + right: 0; +} +.PlaygroundEditorTheme__listItemUnchecked:focus:before, +.PlaygroundEditorTheme__listItemChecked:focus:before { + box-shadow: 0 0 0 2px #a6cdfe; + border-radius: 2px; +} +.PlaygroundEditorTheme__listItemUnchecked:before { + border: 1px solid #999; + border-radius: 2px; +} +.PlaygroundEditorTheme__listItemChecked:before { + border: 1px solid rgb(61, 135, 245); + border-radius: 2px; + background-color: #3d87f5; + background-repeat: no-repeat; +} +.PlaygroundEditorTheme__listItemChecked:after { + content: ''; + cursor: pointer; + border-color: #fff; + border-style: solid; + position: absolute; + display: block; + top: 6px; + width: 3px; + left: 7px; + right: 7px; + height: 6px; + transform: rotate(45deg); + border-width: 0 2px 2px 0; +} +.PlaygroundEditorTheme__nestedListItem { + list-style-type: none; +} +.PlaygroundEditorTheme__nestedListItem:before, +.PlaygroundEditorTheme__nestedListItem:after { + display: none; +} +.PlaygroundEditorTheme__tokenComment { + color: slategray; +} +.PlaygroundEditorTheme__tokenPunctuation { + color: #999; +} +.PlaygroundEditorTheme__tokenProperty { + color: #905; +} +.PlaygroundEditorTheme__tokenSelector { + color: #690; +} +.PlaygroundEditorTheme__tokenOperator { + color: #9a6e3a; +} +.PlaygroundEditorTheme__tokenAttr { + color: #07a; +} +.PlaygroundEditorTheme__tokenVariable { + color: #e90; +} +.PlaygroundEditorTheme__tokenFunction { + color: #dd4a68; +} +.PlaygroundEditorTheme__mark { + background: rgba(255, 212, 0, 0.14); + border-bottom: 2px solid rgba(255, 212, 0, 0.3); + padding-bottom: 2px; +} +.PlaygroundEditorTheme__markOverlap { + background: rgba(255, 212, 0, 0.3); + border-bottom: 2px solid rgba(255, 212, 0, 0.7); +} +.PlaygroundEditorTheme__mark.selected { + background: rgba(255, 212, 0, 0.5); + border-bottom: 2px solid rgba(255, 212, 0, 1); +} +.PlaygroundEditorTheme__markOverlap.selected { + background: rgba(255, 212, 0, 0.7); + border-bottom: 2px solid rgba(255, 212, 0, 0.7); +} +.PlaygroundEditorTheme__embedBlock { + user-select: none; +} +.PlaygroundEditorTheme__embedBlockFocus { + outline: 2px solid rgb(60, 132, 244); +} +.PlaygroundEditorTheme__layoutContaner { + display: grid; + gap: 10px; + margin: 10px 0; +} +.PlaygroundEditorTheme__layoutItem { + border: 1px dashed #ddd; + padding: 8px 16px; +} diff --git a/src/themes/PlaygroundEditorTheme.ts b/src/themes/PlaygroundEditorTheme.ts new file mode 100644 index 0000000..8dd2273 --- /dev/null +++ b/src/themes/PlaygroundEditorTheme.ts @@ -0,0 +1,118 @@ +/** + * 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 {EditorThemeClasses} from 'lexical'; + +import './PlaygroundEditorTheme.css'; + +const theme: EditorThemeClasses = { + blockCursor: 'PlaygroundEditorTheme__blockCursor', + characterLimit: 'PlaygroundEditorTheme__characterLimit', + code: 'PlaygroundEditorTheme__code', + codeHighlight: { + atrule: 'PlaygroundEditorTheme__tokenAttr', + attr: 'PlaygroundEditorTheme__tokenAttr', + boolean: 'PlaygroundEditorTheme__tokenProperty', + builtin: 'PlaygroundEditorTheme__tokenSelector', + cdata: 'PlaygroundEditorTheme__tokenComment', + char: 'PlaygroundEditorTheme__tokenSelector', + class: 'PlaygroundEditorTheme__tokenFunction', + 'class-name': 'PlaygroundEditorTheme__tokenFunction', + comment: 'PlaygroundEditorTheme__tokenComment', + constant: 'PlaygroundEditorTheme__tokenProperty', + deleted: 'PlaygroundEditorTheme__tokenProperty', + doctype: 'PlaygroundEditorTheme__tokenComment', + entity: 'PlaygroundEditorTheme__tokenOperator', + function: 'PlaygroundEditorTheme__tokenFunction', + important: 'PlaygroundEditorTheme__tokenVariable', + inserted: 'PlaygroundEditorTheme__tokenSelector', + keyword: 'PlaygroundEditorTheme__tokenAttr', + namespace: 'PlaygroundEditorTheme__tokenVariable', + number: 'PlaygroundEditorTheme__tokenProperty', + operator: 'PlaygroundEditorTheme__tokenOperator', + prolog: 'PlaygroundEditorTheme__tokenComment', + property: 'PlaygroundEditorTheme__tokenProperty', + punctuation: 'PlaygroundEditorTheme__tokenPunctuation', + regex: 'PlaygroundEditorTheme__tokenVariable', + selector: 'PlaygroundEditorTheme__tokenSelector', + string: 'PlaygroundEditorTheme__tokenSelector', + symbol: 'PlaygroundEditorTheme__tokenProperty', + tag: 'PlaygroundEditorTheme__tokenProperty', + url: 'PlaygroundEditorTheme__tokenOperator', + variable: 'PlaygroundEditorTheme__tokenVariable', + }, + embedBlock: { + base: 'PlaygroundEditorTheme__embedBlock', + focus: 'PlaygroundEditorTheme__embedBlockFocus', + }, + hashtag: 'PlaygroundEditorTheme__hashtag', + heading: { + h1: 'PlaygroundEditorTheme__h1', + h2: 'PlaygroundEditorTheme__h2', + h3: 'PlaygroundEditorTheme__h3', + h4: 'PlaygroundEditorTheme__h4', + h5: 'PlaygroundEditorTheme__h5', + h6: 'PlaygroundEditorTheme__h6', + }, + image: 'editor-image', + indent: 'PlaygroundEditorTheme__indent', + inlineImage: 'inline-editor-image', + layoutContainer: 'PlaygroundEditorTheme__layoutContaner', + layoutItem: 'PlaygroundEditorTheme__layoutItem', + link: 'PlaygroundEditorTheme__link', + list: { + listitem: 'PlaygroundEditorTheme__listItem', + listitemChecked: 'PlaygroundEditorTheme__listItemChecked', + listitemUnchecked: 'PlaygroundEditorTheme__listItemUnchecked', + nested: { + listitem: 'PlaygroundEditorTheme__nestedListItem', + }, + olDepth: [ + 'PlaygroundEditorTheme__ol1', + 'PlaygroundEditorTheme__ol2', + 'PlaygroundEditorTheme__ol3', + 'PlaygroundEditorTheme__ol4', + 'PlaygroundEditorTheme__ol5', + ], + ul: 'PlaygroundEditorTheme__ul', + }, + ltr: 'PlaygroundEditorTheme__ltr', + mark: 'PlaygroundEditorTheme__mark', + markOverlap: 'PlaygroundEditorTheme__markOverlap', + paragraph: 'PlaygroundEditorTheme__paragraph', + quote: 'PlaygroundEditorTheme__quote', + rtl: 'PlaygroundEditorTheme__rtl', + table: 'PlaygroundEditorTheme__table', + tableAddColumns: 'PlaygroundEditorTheme__tableAddColumns', + tableAddRows: 'PlaygroundEditorTheme__tableAddRows', + tableCell: 'PlaygroundEditorTheme__tableCell', + tableCellActionButton: 'PlaygroundEditorTheme__tableCellActionButton', + tableCellActionButtonContainer: + 'PlaygroundEditorTheme__tableCellActionButtonContainer', + tableCellEditing: 'PlaygroundEditorTheme__tableCellEditing', + tableCellHeader: 'PlaygroundEditorTheme__tableCellHeader', + tableCellPrimarySelected: 'PlaygroundEditorTheme__tableCellPrimarySelected', + tableCellResizer: 'PlaygroundEditorTheme__tableCellResizer', + tableCellSelected: 'PlaygroundEditorTheme__tableCellSelected', + tableCellSortedIndicator: 'PlaygroundEditorTheme__tableCellSortedIndicator', + tableResizeRuler: 'PlaygroundEditorTheme__tableCellResizeRuler', + tableSelected: 'PlaygroundEditorTheme__tableSelected', + tableSelection: 'PlaygroundEditorTheme__tableSelection', + text: { + bold: 'PlaygroundEditorTheme__textBold', + code: 'PlaygroundEditorTheme__textCode', + italic: 'PlaygroundEditorTheme__textItalic', + strikethrough: 'PlaygroundEditorTheme__textStrikethrough', + subscript: 'PlaygroundEditorTheme__textSubscript', + superscript: 'PlaygroundEditorTheme__textSuperscript', + underline: 'PlaygroundEditorTheme__textUnderline', + underlineStrikethrough: 'PlaygroundEditorTheme__textUnderlineStrikethrough', + }, +}; + +export default theme; diff --git a/src/themes/StickyEditorTheme.css b/src/themes/StickyEditorTheme.css new file mode 100644 index 0000000..f563869 --- /dev/null +++ b/src/themes/StickyEditorTheme.css @@ -0,0 +1,13 @@ +/** + * 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. + * + * + */ + +.StickyEditorTheme__paragraph { + margin: 0; + position: 'relative'; +} diff --git a/src/themes/StickyEditorTheme.ts b/src/themes/StickyEditorTheme.ts new file mode 100644 index 0000000..d2adc55 --- /dev/null +++ b/src/themes/StickyEditorTheme.ts @@ -0,0 +1,20 @@ +/** + * 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 {EditorThemeClasses} from 'lexical'; + +import './StickyEditorTheme.css'; + +import baseTheme from './PlaygroundEditorTheme'; + +const theme: EditorThemeClasses = { + ...baseTheme, + paragraph: 'StickyEditorTheme__paragraph', +}; + +export default theme; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..de2ddd6 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5b5c03a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "verbatimModuleSyntax": false, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "paths": { + "shared/*": ["./shared/src/*"], + "@lexical/react/*": ["./src/lib/*"], + "react": ["./src/react.svelte.ts"] + }, + "isolatedModules": true + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..494bfe0 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler" + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..7dcc1b7 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import tsconfigPaths from "vite-tsconfig-paths"; +import path from "path"; + +// https://vitejs.dev/config/ +export default defineConfig({ + base: "./", + resolve: { + alias: { + shared: path.resolve(__dirname, "shared/src/"), + "@lexical/react": path.resolve(__dirname, "src/lib/"), + react: path.resolve(__dirname, "src/react.svelte"), + }, + }, + plugins: [svelte()], +});