diff --git a/package-lock.json b/package-lock.json index a26285a..0d2530c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,15 @@ "name": "www.cialloo.com", "version": "0.0.0", "dependencies": { + "@lexical/code": "^0.37.0", + "@lexical/list": "^0.37.0", + "@lexical/react": "^0.37.0", + "@lexical/rich-text": "^0.37.0", + "@lexical/selection": "^0.37.0", + "@lexical/utils": "^0.37.0", "i18next": "^25.5.3", "i18next-browser-languagedetector": "^8.2.0", + "lexical": "^0.37.0", "react": "^19.1.1", "react-dom": "^19.1.1", "react-i18next": "^16.0.0", @@ -60,6 +67,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -508,6 +516,59 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", + "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -610,6 +671,281 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lexical/clipboard": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.37.0.tgz", + "integrity": "sha512-hRwASFX/ilaI5r8YOcZuQgONFshRgCPfdxfofNL7uruSFYAO6LkUhsjzZwUgf0DbmCJmbBADFw15FSthgCUhGA==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.37.0", + "@lexical/list": "0.37.0", + "@lexical/selection": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/code": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.37.0.tgz", + "integrity": "sha512-ZXA4j/S8yLrxjrTnEp39VeDMp4Rd8bLYUlT4Buy1MQlS1WafxOiMhNQJG7k0BP/pO96YPkAebpA81ATKJL0IgA==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.37.0", + "lexical": "0.37.0", + "prismjs": "^1.30.0" + } + }, + "node_modules/@lexical/devtools-core": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.37.0.tgz", + "integrity": "sha512-iOR+aKLJR92nKYcEOW3K/bgjTN7dJIRC/OM4OvzigU0Xygxped0lXV6UmkYBp0eoqOOwckB8+rZWZszj9lKA8Q==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.37.0", + "@lexical/link": "0.37.0", + "@lexical/mark": "0.37.0", + "@lexical/table": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/dragon": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.37.0.tgz", + "integrity": "sha512-iC4OKivEPtt7cGVSwZylLfz5T7Oqr9q9EOosS6E/byMyoqwkYWGjXn/qFiwIv1Xo3+G19vhfChi/+ZcYLXpHPw==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/extension": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/extension/-/extension-0.37.0.tgz", + "integrity": "sha512-Z58f2tIdz9bn8gltUu5cVg37qROGha38dUZv20gI2GeNugXAkoPzJYEcxlI1D/26tkevJ/7VaFUr9PTk+iKmaA==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.37.0", + "@preact/signals-core": "^1.11.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/hashtag": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.37.0.tgz", + "integrity": "sha512-DHoDpiokJRBu+GnC0qQH529hamn9YNjL7vzzkTAeEMKsT9+4O848Cq6F2GJn8QjQToySlkVZW3mkh76uf/XLfg==", + "license": "MIT", + "dependencies": { + "@lexical/text": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/history": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.37.0.tgz", + "integrity": "sha512-QKkrWCw4bsn/ZeLIkMVIpbtWKPhMYeax1nE7erHqTEwE52QR6pmZsZBgGSQDO73Ae29vahOmqlN7+ZJFvTKMVA==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/html": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.37.0.tgz", + "integrity": "sha512-oTsBc45eL8/lmF7fqGR+UCjrJYP04gumzf5nk4TczrxWL2pM4GIMLLKG1mpQI2H1MDiRLzq3T/xdI7Gh74z7Zw==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/link": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.37.0.tgz", + "integrity": "sha512-gglkjE99tKYnGAxQbrUq9TcaVKBQhidXhgPPbVw3x1Fba9biMafkbSJhE/7/pzQTPoQBAIl0w7DOUWmBOv+JbQ==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/list": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.37.0.tgz", + "integrity": "sha512-AOC6yAA3mfNvJKbwo+kvAbPJI+13yF2ISA65vbA578CugvJ08zIVgM+pSzxquGhD0ioJY3cXVW7+gdkCP1qu5g==", + "license": "MIT", + "dependencies": { + "@lexical/extension": "0.37.0", + "@lexical/selection": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/mark": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.37.0.tgz", + "integrity": "sha512-ncjaL6kNHVioekx6vI5oJRDExFDJLbnXT7AdMnUv2LE3sxn/ea+JsZO/MDI4Ygmxq+lGtgZvbBDER8Yh/+5jdA==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/markdown": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.37.0.tgz", + "integrity": "sha512-pcLMpxWkSxU2QaN2GLA3hNy4lV2A8sJOvb5YEkcsFEcVvFFbAz7lxgyKVYtDboRCW1eZFks1UGGuJEogLeEFdg==", + "license": "MIT", + "dependencies": { + "@lexical/code": "0.37.0", + "@lexical/link": "0.37.0", + "@lexical/list": "0.37.0", + "@lexical/rich-text": "0.37.0", + "@lexical/text": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/offset": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.37.0.tgz", + "integrity": "sha512-q9Ckftfhb+VepJQeaClOYzpuV+WqWWGkSUuoexV4zjAm/HVjOie9lrNF4NkhQe5crnIBXI5zOofhuEfiCQWsbQ==", + "license": "MIT", + "dependencies": { + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/overflow": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.37.0.tgz", + "integrity": "sha512-GC5qoQJQzaofCq1eMMvv9wIGMAbpFbFwny5BKA1C2Nmn+/2bi6v+7qlHwiBlbSVqfLVPvT4nYdrmNdnKoE0jZg==", + "license": "MIT", + "dependencies": { + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/plain-text": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.37.0.tgz", + "integrity": "sha512-4IxG9Tr0NnQ+clN1eoXfe2W8JTgw0xtPMzqvHP2IaO7RILUE6H8VFSOdhAOI0dHrjlXRMUS3I2Fhqr2ZRq8kdQ==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.37.0", + "@lexical/dragon": "0.37.0", + "@lexical/selection": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/react": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.37.0.tgz", + "integrity": "sha512-PGIGmI5xDSAguqpAStd+89TfWsi6hs/R4a3hQAyNwXXDEt4anUFJic4Qet4YftybLGajP3vMvouLE5hrkmBihg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.16", + "@lexical/devtools-core": "0.37.0", + "@lexical/dragon": "0.37.0", + "@lexical/extension": "0.37.0", + "@lexical/hashtag": "0.37.0", + "@lexical/history": "0.37.0", + "@lexical/link": "0.37.0", + "@lexical/list": "0.37.0", + "@lexical/mark": "0.37.0", + "@lexical/markdown": "0.37.0", + "@lexical/overflow": "0.37.0", + "@lexical/plain-text": "0.37.0", + "@lexical/rich-text": "0.37.0", + "@lexical/table": "0.37.0", + "@lexical/text": "0.37.0", + "@lexical/utils": "0.37.0", + "@lexical/yjs": "0.37.0", + "lexical": "0.37.0", + "react-error-boundary": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/rich-text": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.37.0.tgz", + "integrity": "sha512-A9i5Es/RrZv71tB6dDSyd4TYdbkn/+oUrUdTwnWa+B8EZW26q0h+wgxCGwPtTU7ho4JNP9HOot+EIhe2DbyaYg==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.37.0", + "@lexical/dragon": "0.37.0", + "@lexical/selection": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/selection": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.37.0.tgz", + "integrity": "sha512-Lix1s2r71jHfsTEs4q/YqK2s3uXKOnyA3fd1VDMWysO+bZzRwEO5+qyDvENZ0WrXSDCnlibNFV1HttWX9/zqyw==", + "license": "MIT", + "dependencies": { + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/table": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.37.0.tgz", + "integrity": "sha512-g7S8ml8kIujEDLWlzYKETgPCQ2U9oeWqdytRuHjHGi/rjAAGHSej5IRqTPIMxNP3VVQHnBoQ+Y9hBtjiuddhgQ==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.37.0", + "@lexical/extension": "0.37.0", + "@lexical/utils": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/text": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.37.0.tgz", + "integrity": "sha512-qByNjHp88mlUWHxfYutH4vhSs3nzfCGHKsf/MqUMOC8K7Kmp0V1NK6cOW1sgsHpzkovfpgcNOGDzZxTNCFgHtg==", + "license": "MIT", + "dependencies": { + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/utils": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.37.0.tgz", + "integrity": "sha512-CFp4diY/kR5RqhzQSl/7SwsMod1sgLpI1FBifcOuJ6L/S6YywGpEB4B7aV5zqW21A/jU2T+2NZtxSUn6S+9gMg==", + "license": "MIT", + "dependencies": { + "@lexical/list": "0.37.0", + "@lexical/selection": "0.37.0", + "@lexical/table": "0.37.0", + "lexical": "0.37.0" + } + }, + "node_modules/@lexical/yjs": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.37.0.tgz", + "integrity": "sha512-7UjHvXDd+Is/qTdNkpQ/K04Zduh2uh7UTlSWbMiqwbQh8VRJNXXgcH8iK0TXLwc7M3VgVk+FlnNApNvcReKB6g==", + "license": "MIT", + "dependencies": { + "@lexical/offset": "0.37.0", + "@lexical/selection": "0.37.0", + "lexical": "0.37.0" + }, + "peerDependencies": { + "yjs": ">=13.5.22" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz", @@ -681,6 +1017,16 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@preact/signals-core": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.12.1.tgz", + "integrity": "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.39", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.39.tgz", @@ -1002,6 +1348,7 @@ "integrity": "sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1062,6 +1409,7 @@ "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", @@ -1314,6 +1662,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1442,6 +1791,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -1640,6 +1990,7 @@ "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2033,6 +2384,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -2131,6 +2483,16 @@ "dev": true, "license": "ISC" }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "license": "MIT", + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2222,6 +2584,33 @@ "node": ">= 0.8.0" } }, + "node_modules/lexical": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.37.0.tgz", + "integrity": "sha512-r5VJR2TioQPAsZATfktnJFrGIiy6gjQN8b/+0a2u1d7/QTH7lhbB7byhGSvcq1iaa1TV/xcf/pFV55a5V5hTDQ==", + "license": "MIT" + }, + "node_modules/lib0": { + "version": "0.2.114", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz", + "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==", + "license": "MIT", + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -2735,6 +3124,15 @@ "node": ">= 0.8.0" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2771,6 +3169,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2780,6 +3179,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -2787,6 +3187,18 @@ "react": "^19.1.1" } }, + "node_modules/react-error-boundary": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz", + "integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-i18next": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.0.0.tgz", @@ -3028,6 +3440,12 @@ "node": ">=8" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -3069,6 +3487,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3129,6 +3548,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3209,6 +3629,7 @@ "integrity": "sha512-JREtUS+Lpa3s5Ha3ajf2F4LMS4BFxlVjpGz0k0ZR8rV3ZO3tzk5hukqyi9yRBcrvnTUg/BEForyCDahALFYAZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/runtime": "0.90.0", "fdir": "^6.5.0", @@ -3303,6 +3724,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3352,6 +3774,24 @@ "dev": true, "license": "ISC" }, + "node_modules/yjs": { + "version": "13.6.27", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", + "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", + "license": "MIT", + "peer": true, + "dependencies": { + "lib0": "^0.2.99" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 5adffe9..7ca73d1 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "@lexical/code": "^0.37.0", + "@lexical/list": "^0.37.0", + "@lexical/react": "^0.37.0", + "@lexical/rich-text": "^0.37.0", + "@lexical/selection": "^0.37.0", + "@lexical/utils": "^0.37.0", "i18next": "^25.5.3", "i18next-browser-languagedetector": "^8.2.0", + "lexical": "^0.37.0", "react": "^19.1.1", "react-dom": "^19.1.1", "react-i18next": "^16.0.0", diff --git a/src/editor/README.md b/src/editor/README.md new file mode 100644 index 0000000..73de55e --- /dev/null +++ b/src/editor/README.md @@ -0,0 +1,95 @@ +# Rich Text Editor + +A basic rich text editor built with Lexical, ported from the lexical-playground project. + +## Features + +### Toolbar Controls + +1. **Undo/Redo** - Navigate through editing history +2. **Text Size** - Select from multiple font sizes (10px - 36px) +3. **Bold** - Make text bold +4. **Italic** - Italicize text +5. **Underline** - Underline text +6. **Strikethrough** - Strike through text +7. **Code** - Format text as inline code +8. **Text Color** - Choose from 30 predefined colors +9. **Background Color** - Choose background color for text + +### Additional Features + +- Quote blocks +- Code blocks (without syntax highlighting) +- Lists (bullet and numbered) +- Headings (H1, H2, H3) + +## Usage + +### Basic Usage + +```tsx +import RichTextEditor from './editor/RichTextEditor'; + +function MyComponent() { + return ; +} +``` + +### Accessing the Demo + +Navigate to `/editor` to see the editor in action. + +## Files Structure + +``` +src/editor/ +├── RichTextEditor.tsx # Main editor component +├── index.ts # Export file +├── plugins/ +│ └── ToolbarPlugin.tsx # Toolbar with formatting controls +├── ui/ +│ ├── DropdownColorPicker.tsx # Color picker component +│ └── DropdownColorPicker.css # Color picker styles +├── themes/ +│ └── EditorTheme.ts # Editor theme configuration +└── styles/ + ├── editor.css # Editor styles + └── toolbar.css # Toolbar styles +``` + +## Dependencies + +- `lexical` - Core editor framework +- `@lexical/react` - React bindings +- `@lexical/code` - Code block support +- `@lexical/list` - List support +- `@lexical/rich-text` - Rich text features (headings, quotes) +- `@lexical/selection` - Selection utilities +- `@lexical/utils` - Utility functions + +## Customization + +### Theme + +Edit `src/editor/themes/EditorTheme.ts` to customize the editor's appearance. + +### Toolbar + +Modify `src/editor/plugins/ToolbarPlugin.tsx` to add or remove toolbar buttons. + +### Styles + +- `src/editor/styles/editor.css` - Editor content styles +- `src/editor/styles/toolbar.css` - Toolbar styles + +## Future Enhancements + +Possible additions (not currently implemented): + +- Syntax highlighting for code blocks +- Links +- Images +- Tables +- Alignment controls +- More list types (checklists) +- Markdown shortcuts diff --git a/src/editor/RichTextEditor.tsx b/src/editor/RichTextEditor.tsx new file mode 100644 index 0000000..da7bcf5 --- /dev/null +++ b/src/editor/RichTextEditor.tsx @@ -0,0 +1,51 @@ +import { LexicalComposer } from '@lexical/react/LexicalComposer'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { HeadingNode, QuoteNode } from '@lexical/rich-text'; +import { CodeNode } from '@lexical/code'; +import { ListItemNode, ListNode } from '@lexical/list'; +import { ListPlugin } from '@lexical/react/LexicalListPlugin'; + +import ToolbarPlugin from './plugins/ToolbarPlugin'; +import editorTheme from './themes/EditorTheme'; +import './styles/editor.css'; + +const editorConfig = { + namespace: 'CiallooEditor', + theme: editorTheme, + onError(error: Error) { + console.error(error); + }, + nodes: [ + HeadingNode, + QuoteNode, + CodeNode, + ListNode, + ListItemNode, + ], +}; + +export default function RichTextEditor() { + return ( + +
+ +
+ + } + placeholder={ +
Enter some text...
+ } + ErrorBoundary={LexicalErrorBoundary} + /> + + +
+
+
+ ); +} diff --git a/src/editor/index.ts b/src/editor/index.ts new file mode 100644 index 0000000..934a7bb --- /dev/null +++ b/src/editor/index.ts @@ -0,0 +1 @@ +export { default } from './RichTextEditor'; diff --git a/src/editor/plugins/ToolbarPlugin.tsx b/src/editor/plugins/ToolbarPlugin.tsx new file mode 100644 index 0000000..0fa420d --- /dev/null +++ b/src/editor/plugins/ToolbarPlugin.tsx @@ -0,0 +1,230 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { + CAN_REDO_COMMAND, + CAN_UNDO_COMMAND, + REDO_COMMAND, + UNDO_COMMAND, + SELECTION_CHANGE_COMMAND, + FORMAT_TEXT_COMMAND, + COMMAND_PRIORITY_CRITICAL, + $getSelection, + $isRangeSelection, +} from 'lexical'; +import type { TextFormatType } from 'lexical'; +import { $getSelectionStyleValueForProperty, $patchStyleText } from '@lexical/selection'; + +import DropdownColorPicker from '../ui/DropdownColorPicker'; +import '../styles/toolbar.css'; + +export default function ToolbarPlugin() { + const [editor] = useLexicalComposerContext(); + const toolbarRef = useRef(null); + const [canUndo, setCanUndo] = useState(false); + const [canRedo, setCanRedo] = useState(false); + const [isBold, setIsBold] = useState(false); + const [isItalic, setIsItalic] = useState(false); + const [isUnderline, setIsUnderline] = useState(false); + const [isStrikethrough, setIsStrikethrough] = useState(false); + const [isCode, setIsCode] = useState(false); + const [fontColor, setFontColor] = useState('#000'); + const [bgColor, setBgColor] = useState('#fff'); + const [fontSize, setFontSize] = useState('15px'); + + const updateToolbar = useCallback(() => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + // Update text format + setIsBold(selection.hasFormat('bold')); + setIsItalic(selection.hasFormat('italic')); + setIsUnderline(selection.hasFormat('underline')); + setIsStrikethrough(selection.hasFormat('strikethrough')); + setIsCode(selection.hasFormat('code')); + + // Update color + setFontColor( + $getSelectionStyleValueForProperty(selection, 'color', '#000') + ); + setBgColor( + $getSelectionStyleValueForProperty(selection, 'background-color', '#fff') + ); + setFontSize( + $getSelectionStyleValueForProperty(selection, 'font-size', '15px') + ); + } + }, [editor]); + + useEffect(() => { + return editor.registerCommand( + SELECTION_CHANGE_COMMAND, + () => { + updateToolbar(); + return false; + }, + COMMAND_PRIORITY_CRITICAL + ); + }, [editor, updateToolbar]); + + useEffect(() => { + return editor.registerUpdateListener(({ editorState }) => { + editorState.read(() => { + updateToolbar(); + }); + }); + }, [editor, updateToolbar]); + + useEffect(() => { + return editor.registerCommand( + CAN_UNDO_COMMAND, + (payload) => { + setCanUndo(payload); + return false; + }, + COMMAND_PRIORITY_CRITICAL + ); + }, [editor]); + + useEffect(() => { + return editor.registerCommand( + CAN_REDO_COMMAND, + (payload) => { + setCanRedo(payload); + return false; + }, + COMMAND_PRIORITY_CRITICAL + ); + }, [editor]); + + const formatText = (format: TextFormatType) => { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, format); + }; + + const onFontColorSelect = useCallback( + (value: string) => { + editor.update(() => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + $patchStyleText(selection, { color: value }); + } + }); + }, + [editor] + ); + + const onBgColorSelect = useCallback( + (value: string) => { + editor.update(() => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + $patchStyleText(selection, { 'background-color': value }); + } + }); + }, + [editor] + ); + + const onFontSizeChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + editor.update(() => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + $patchStyleText(selection, { 'font-size': value }); + } + }); + }, + [editor] + ); + + return ( +
+ + +
+ + +
+ + + + + + +
+ + + +
+ ); +} diff --git a/src/editor/styles/editor.css b/src/editor/styles/editor.css new file mode 100644 index 0000000..620d4a7 --- /dev/null +++ b/src/editor/styles/editor.css @@ -0,0 +1,148 @@ +.editor-container { + margin: 20px auto; + border-radius: 8px; + max-width: 1100px; + color: #000; + position: relative; + line-height: 1.7; + font-weight: 400; + text-align: left; + border: 1px solid #e0e0e0; + background: #fff; +} + +.editor-inner { + background: #fff; + position: relative; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; +} + +.editor-input { + min-height: 300px; + resize: vertical; + font-size: 15px; + caret-color: rgb(5, 5, 5); + position: relative; + tab-size: 1; + outline: 0; + padding: 15px 20px; + caret-color: #444; +} + +.editor-placeholder { + color: #999; + overflow: hidden; + position: absolute; + text-overflow: ellipsis; + top: 15px; + left: 20px; + font-size: 15px; + user-select: none; + display: inline-block; + pointer-events: none; +} + +.editor-paragraph { + margin: 0; + margin-bottom: 8px; + position: relative; +} + +.editor-paragraph:last-child { + margin-bottom: 0; +} + +.editor-heading-h1 { + font-size: 2em; + font-weight: 700; + margin: 0; + margin-bottom: 12px; + padding: 0; +} + +.editor-heading-h2 { + font-size: 1.5em; + font-weight: 700; + margin: 0; + margin-bottom: 10px; + padding: 0; +} + +.editor-heading-h3 { + font-size: 1.25em; + font-weight: 700; + margin: 0; + margin-bottom: 8px; + padding: 0; +} + +.editor-quote { + margin: 0; + margin-left: 20px; + margin-bottom: 10px; + font-size: 15px; + color: #666; + border-left: 4px solid #ccc; + padding-left: 16px; +} + +.editor-code { + background-color: #f4f4f4; + font-family: Menlo, Consolas, Monaco, monospace; + display: block; + padding: 8px 12px; + line-height: 1.53; + font-size: 13px; + margin: 0; + margin-top: 8px; + margin-bottom: 8px; + overflow-x: auto; + position: relative; + tab-size: 2; + border-radius: 4px; +} + +.editor-text-bold { + font-weight: bold; +} + +.editor-text-italic { + font-style: italic; +} + +.editor-text-underline { + text-decoration: underline; +} + +.editor-text-strikethrough { + text-decoration: line-through; +} + +.editor-text-code { + background-color: #f4f4f4; + padding: 1px 4px; + font-family: Menlo, Consolas, Monaco, monospace; + font-size: 90%; + border-radius: 3px; +} + +.editor-list-ol { + padding: 0; + margin: 0; + margin-left: 20px; +} + +.editor-list-ul { + padding: 0; + margin: 0; + margin-left: 20px; +} + +.editor-listitem { + margin: 4px 0; +} + +.editor-nested-listitem { + list-style-type: none; +} diff --git a/src/editor/styles/toolbar.css b/src/editor/styles/toolbar.css new file mode 100644 index 0000000..5b2dabb --- /dev/null +++ b/src/editor/styles/toolbar.css @@ -0,0 +1,130 @@ +.toolbar { + display: flex; + background: #fff; + padding: 8px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + vertical-align: middle; + border-bottom: 1px solid #e0e0e0; + gap: 4px; + flex-wrap: wrap; + align-items: center; +} + +.toolbar button { + border: 0; + display: flex; + background: none; + border-radius: 4px; + padding: 6px; + cursor: pointer; + vertical-align: middle; + align-items: center; + justify-content: center; + transition: background-color 0.2s; +} + +.toolbar button:hover:not([disabled]) { + background-color: #f0f0f0; +} + +.toolbar button:disabled { + cursor: not-allowed; + opacity: 0.3; +} + +.toolbar button.active { + background-color: rgba(24, 144, 255, 0.1); +} + +.toolbar-item { + border: 0; + display: flex; + background: none; + border-radius: 4px; + padding: 6px; + cursor: pointer; + vertical-align: middle; + flex-shrink: 0; + align-items: center; + justify-content: space-between; +} + +.toolbar-item.spaced { + margin-right: 2px; +} + +.toolbar-item.block-controls { + background: none; + border: 1px solid #d0d0d0; + border-radius: 4px; + padding: 4px 8px; + font-size: 14px; + cursor: pointer; + min-width: 80px; +} + +.toolbar-item i.format { + background-size: contain; + display: inline-block; + height: 18px; + width: 18px; + vertical-align: -0.25em; + opacity: 0.7; +} + +.toolbar-item.active i.format, +.toolbar-item:hover:not([disabled]) i.format { + opacity: 1; +} + +.divider { + width: 1px; + background-color: #e0e0e0; + margin: 0 4px; + height: 24px; +} + +i.format.undo { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.redo { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.bold { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.italic { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.underline { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.strikethrough { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.format.code { + background-image: url('data:image/svg+xml;utf8,'); +} + +i.icon.font-color { + background-image: url('data:image/svg+xml;utf8,'); + display: inline-block; + height: 18px; + width: 18px; + background-size: contain; +} + +i.icon.bg-color { + background-image: url('data:image/svg+xml;utf8,'); + display: inline-block; + height: 18px; + width: 18px; + background-size: contain; +} diff --git a/src/editor/themes/EditorTheme.ts b/src/editor/themes/EditorTheme.ts new file mode 100644 index 0000000..7812103 --- /dev/null +++ b/src/editor/themes/EditorTheme.ts @@ -0,0 +1,33 @@ +import type { EditorThemeClasses } from 'lexical'; + +const theme: EditorThemeClasses = { + paragraph: 'editor-paragraph', + quote: 'editor-quote', + heading: { + h1: 'editor-heading-h1', + h2: 'editor-heading-h2', + h3: 'editor-heading-h3', + h4: 'editor-heading-h4', + h5: 'editor-heading-h5', + h6: 'editor-heading-h6', + }, + list: { + nested: { + listitem: 'editor-nested-listitem', + }, + ol: 'editor-list-ol', + ul: 'editor-list-ul', + listitem: 'editor-listitem', + }, + text: { + bold: 'editor-text-bold', + italic: 'editor-text-italic', + underline: 'editor-text-underline', + strikethrough: 'editor-text-strikethrough', + code: 'editor-text-code', + }, + code: 'editor-code', + codeHighlight: {}, +}; + +export default theme; diff --git a/src/editor/ui/DropdownColorPicker.css b/src/editor/ui/DropdownColorPicker.css new file mode 100644 index 0000000..3ee739d --- /dev/null +++ b/src/editor/ui/DropdownColorPicker.css @@ -0,0 +1,50 @@ +.color-picker-wrapper { + position: relative; +} + +.color-preview { + display: inline-block; + width: 16px; + height: 16px; + border-radius: 2px; + border: 1px solid #ccc; + margin-left: 4px; + vertical-align: middle; +} + +.color-picker-dropdown { + position: absolute; + top: 100%; + left: 0; + margin-top: 4px; + background: white; + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + z-index: 1000; +} + +.color-picker-basic-color { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 4px; +} + +.color-picker-basic-color button { + width: 24px; + height: 24px; + border: 1px solid #ccc; + border-radius: 2px; + cursor: pointer; + padding: 0; +} + +.color-picker-basic-color button:hover { + border-color: #333; +} + +.color-picker-basic-color button.active { + border: 2px solid #1890ff; + border-radius: 4px; +} diff --git a/src/editor/ui/DropdownColorPicker.tsx b/src/editor/ui/DropdownColorPicker.tsx new file mode 100644 index 0000000..1e5fea0 --- /dev/null +++ b/src/editor/ui/DropdownColorPicker.tsx @@ -0,0 +1,87 @@ +import { useEffect, useRef, useState } from 'react'; +import './DropdownColorPicker.css'; + +const basicColors = [ + '#000000', '#ffffff', '#888888', '#ff0000', '#00ff00', '#0000ff', + '#ffff00', '#00ffff', '#ff00ff', '#c0c0c0', '#808080', '#800000', + '#808000', '#008000', '#800080', '#008080', '#000080', '#ffa500', + '#a52a2a', '#dc143c', '#ff1493', '#ff69b4', '#ffd700', '#adff2f', + '#00fa9a', '#00ced1', '#1e90ff', '#9370db', '#ff6347', '#40e0d0', +]; + +type Props = { + buttonClassName: string; + buttonAriaLabel?: string; + buttonIconClassName?: string; + color: string; + onChange?: (color: string) => void; + title?: string; +}; + +export default function DropdownColorPicker({ + buttonClassName, + buttonAriaLabel, + buttonIconClassName, + color, + onChange, + title, +}: Props) { + const [showPicker, setShowPicker] = useState(false); + const dropdownRef = useRef(null); + const buttonRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + setShowPicker(false); + } + }; + + if (showPicker) { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + } + }, [showPicker]); + + return ( +
+ + {showPicker && ( +
+
+ {basicColors.map((basicColor) => ( +
+
+ )} +
+ ); +} diff --git a/src/main.tsx b/src/main.tsx index ee12b36..646d682 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -14,6 +14,7 @@ import Blog from './pages/Blog.tsx' import Servers from './pages/Servers.tsx' import Forum from './pages/Forum.tsx' import AuthCallback from './pages/AuthCallback.tsx' +import EditorDemo from './pages/EditorDemo.tsx' createRoot(document.getElementById('root')!).render( @@ -30,6 +31,7 @@ createRoot(document.getElementById('root')!).render( } /> } /> } /> + } /> diff --git a/src/pages/EditorDemo.tsx b/src/pages/EditorDemo.tsx new file mode 100644 index 0000000..8e01495 --- /dev/null +++ b/src/pages/EditorDemo.tsx @@ -0,0 +1,32 @@ +import RichTextEditor from '../editor/RichTextEditor'; +import '../editor/styles/editor.css'; +import '../editor/styles/toolbar.css'; + +export default function EditorDemo() { + return ( +
+

+ Rich Text Editor Demo +

+

+ A basic rich text editor with formatting toolbar featuring: + undo/redo, text size, bold, italic, underline, strikethrough, + code blocks, text color, and background color. +

+ +
+ ); +}