feat(F-062): completed feature

This commit is contained in:
chattie
2026-08-19 17:02:14 +02:00
parent e0411bc5a4
commit 52e30c39d7
12 changed files with 1246 additions and 20 deletions

View File

@@ -8,6 +8,15 @@
"name": "@mercadodevida/admin",
"version": "0.1.0",
"dependencies": {
"@lexical/history": "^0.49.0",
"@lexical/html": "^0.49.0",
"@lexical/link": "^0.49.0",
"@lexical/list": "^0.49.0",
"@lexical/markdown": "^0.49.0",
"@lexical/react": "^0.49.0",
"@lexical/rich-text": "^0.49.0",
"@lexical/utils": "^0.49.0",
"lexical": "^0.49.0",
"next": "16.3.1",
"react": "19.2.8",
"react-dom": "19.2.8",
@@ -454,6 +463,59 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
"integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.8.0",
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/react": {
"version": "0.27.20",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.20.tgz",
"integrity": "sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.1.9",
"@floating-ui/utils": "^0.2.12",
"tabbable": "^6.0.0"
},
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz",
"integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.8.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
"license": "MIT"
},
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@@ -1070,6 +1132,491 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lexical/a11y": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/a11y/-/a11y-0.49.0.tgz",
"integrity": "sha512-ypvO0SI9DCzEAjxkRLq+gNzRqfjg632RCkwu16Hk94PCBrEmcDhx5iUGg3SRJC7WnM2wXC/F/SB8yWDEfq7tEw==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/clipboard": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.49.0.tgz",
"integrity": "sha512-AVKj21xH1qU7JAFA/v0hCoafa+Yti1I7cHG+JQIgc/EqCtP8ePeJyIfTPNN/tXskoCdq++HewQoiSXRwwlocVg==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"@lexical/html": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/list": "0.49.0",
"@lexical/selection": "0.49.0",
"@lexical/utils": "0.49.0",
"@types/trusted-types": "^2.0.7",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/code-core": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/code-core/-/code-core-0.49.0.tgz",
"integrity": "sha512-29sXI4ydcN/OyBJNlZXkGKtQXzfiIWet+YN20+wT97zkqityOvRilK6qzTRz9Cd0ungEPlJENqXQhsy6BJeXpg==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"@lexical/html": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/devtools-core": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.49.0.tgz",
"integrity": "sha512-LgjNlvRiuO/QF36aG6kfR4ptEvSw587o2+YF6SOsaOB7iw2Z3pqpsKloWSEF4QXaLFI65Li8JjMdQj+BaFMIOA==",
"license": "MIT",
"dependencies": {
"@lexical/html": "0.49.0",
"@lexical/link": "0.49.0",
"@lexical/mark": "0.49.0",
"@lexical/table": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"react": ">=18.x",
"react-dom": ">=18.x",
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/dragon": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.49.0.tgz",
"integrity": "sha512-62/4DP5qyX/l4Yf5qRyyQrs9BV725eRU3OmLUW6g7T5xrcHXxAo7tia/NvqjqvXdpvQzyHjWgsy7dMitGITUsw==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/extension": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/extension/-/extension-0.49.0.tgz",
"integrity": "sha512-Wv0VsuqxorbxHCK4ms1PwAu6cXIGNLtflq66auF+zwxOtBprkSFV8VzzzdKLOYD2admP0VK6EqVCeGXFK1GggA==",
"license": "MIT",
"dependencies": {
"@lexical/internal": "0.49.0",
"@lexical/utils": "0.49.0",
"@preact/signals-core": "^1.14.1",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/hashtag": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.49.0.tgz",
"integrity": "sha512-aQTpNjrEO5apsaohWGl4ku22EgHpWUzZg5CIzvriS2A1vNmIfzLoCL+lDFpzAwk3DT1Kj1yvGHh5TvhYsb9D8w==",
"license": "MIT",
"dependencies": {
"@lexical/text": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/history": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.49.0.tgz",
"integrity": "sha512-uQdtEd34gIJklXNSdHS2Wko1zxx1xUMVXbiodcLO6a3GeFTE5bKnx6af1zGX78KpYhUQYnHWorKuUvtdZlJPaA==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/html": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.49.0.tgz",
"integrity": "sha512-NQqAydKzRjQl7Jx+bTTyC2iuz5uhuDaQX0SSPrdfgaFDCPjqQEejcBkCt1QXnu1CkbVHutZKVGVzljx40Y6y+Q==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/selection": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/internal": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/internal/-/internal-0.49.0.tgz",
"integrity": "sha512-s+XjPC7Qb39A/Xx9ahcz1s69CPix4ultaqyW+MDG0AXYW2quDr7m0USqReKIAiuoLIWtGN5HW8BwV2Y6t4qr6Q==",
"license": "MIT",
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/link": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.49.0.tgz",
"integrity": "sha512-CsZVu1OfnPOn6IKWVO5DbmKwIuicGtgWSzrZMdMB8w6yPoMgFyPWeVulhPE7LNz2FhaQCWv+lVVYVAiJrUzJsg==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"@lexical/html": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/list": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.49.0.tgz",
"integrity": "sha512-zs6wYkxakDRcJO0KmwrPPHgeLLIxzjD+P2CRu+scJCHRuA5e2iSw2iFjH5n/LlWBrlnPMSjeQse4QqsRy9nnqA==",
"license": "MIT",
"dependencies": {
"@lexical/extension": "0.49.0",
"@lexical/html": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/mark": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.49.0.tgz",
"integrity": "sha512-JSYD8UYQQOMT6oDYh1KsLPK3KMGfQzU7E9fJkugKEO0/XebfsmiuNtkLCGDVIZOo8bNHlwb1YVvmswC2bjotMQ==",
"license": "MIT",
"dependencies": {
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/markdown": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.49.0.tgz",
"integrity": "sha512-n7x/3OLdi5R0ztICCn8udxRIWFZFVtmQS3a06Tjkyvm/j/dPlFQ/henKSOFjjX/IqVzQzSmiNaDhqKWxXM5npQ==",
"license": "MIT",
"dependencies": {
"@lexical/code-core": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/link": "0.49.0",
"@lexical/list": "0.49.0",
"@lexical/rich-text": "0.49.0",
"@lexical/selection": "0.49.0",
"@lexical/text": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/overflow": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.49.0.tgz",
"integrity": "sha512-h5Qjv2SkpI76kaukvKJzvWehG9hwB06GEmAnp6aJaioOzoDcBAeWpmYBebtBUwRm0PNLFe3YICFFLpZ5Tg5VoQ==",
"license": "MIT",
"dependencies": {
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/plain-text": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.49.0.tgz",
"integrity": "sha512-l7IuUj9n9CtFfz4Fz6zU6mmO+VkcnvyyebABx+lHevvTa7B6YI5PPVgABFfzdyPanyW/FGs7qpKBf59inAqbkg==",
"license": "MIT",
"dependencies": {
"@lexical/clipboard": "0.49.0",
"@lexical/dragon": "0.49.0",
"@lexical/extension": "0.49.0",
"@lexical/selection": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/react": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.49.0.tgz",
"integrity": "sha512-NqXF4i/IBFKVDDLDJYTwmBXE++MQuIrTQs7VTux6IPyDEpyfeFMn1rQTFb949S3f7rs33Rcu1uGtFbODQfS1Iw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.27.19",
"@lexical/a11y": "0.49.0",
"@lexical/devtools-core": "0.49.0",
"@lexical/dragon": "0.49.0",
"@lexical/extension": "0.49.0",
"@lexical/hashtag": "0.49.0",
"@lexical/history": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/link": "0.49.0",
"@lexical/list": "0.49.0",
"@lexical/mark": "0.49.0",
"@lexical/markdown": "0.49.0",
"@lexical/overflow": "0.49.0",
"@lexical/plain-text": "0.49.0",
"@lexical/rich-text": "0.49.0",
"@lexical/table": "0.49.0",
"@lexical/text": "0.49.0",
"@lexical/utils": "0.49.0",
"@lexical/yjs": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"react": ">=18.x",
"react-dom": ">=18.x",
"typescript": ">=5.2",
"yjs": ">=13.5.22"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
},
"yjs": {
"optional": true
}
}
},
"node_modules/@lexical/rich-text": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.49.0.tgz",
"integrity": "sha512-LM+d8ULmQvi2Av6UaW4NAIa/Bj9FOo9oh6+dglTzuoFPFQ3sogy5c38i2K9k0ul7aE+bFsXkSmHbu4LkrHIkBQ==",
"license": "MIT",
"dependencies": {
"@lexical/clipboard": "0.49.0",
"@lexical/dragon": "0.49.0",
"@lexical/extension": "0.49.0",
"@lexical/html": "0.49.0",
"@lexical/selection": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/selection": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.49.0.tgz",
"integrity": "sha512-08Vd1+VoC6YnztWOFWVsqF/Hxw5EP8qeL1c7t3+rVCV8revLzXxdQw+vbPX3tgM4fmjOBDUXk6Ws1+rTtsqjcQ==",
"license": "MIT",
"dependencies": {
"@lexical/internal": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/table": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.49.0.tgz",
"integrity": "sha512-PG+dxSTBPbUZUY2kBZm0pJuqCEOGom4RrGrc6Jlj/7o0F37k/gGRVprFSRKuf3CqO03QIP6qRovXGDuKb+Exuw==",
"license": "MIT",
"dependencies": {
"@lexical/clipboard": "0.49.0",
"@lexical/extension": "0.49.0",
"@lexical/html": "0.49.0",
"@lexical/internal": "0.49.0",
"@lexical/utils": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/text": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.49.0.tgz",
"integrity": "sha512-mowedvbvx0HDaW+ymVdYmWVhueqgauhhqIWaCtbJj33tPk8pOu1BB0pZuJQbOB+1bDnFiZhkJV8W/CvR2M9lEA==",
"license": "MIT",
"dependencies": {
"@lexical/internal": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/utils": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.49.0.tgz",
"integrity": "sha512-Jaa6DERBqxiFOFa49VPRV1WOb7mzRbMZ5U+v+RFagjzTmBhfxDmEkbQQ9nYTU3n3DPfdT8Hkyffextmw04etXg==",
"license": "MIT",
"dependencies": {
"@lexical/internal": "0.49.0",
"@lexical/selection": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@lexical/yjs": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.49.0.tgz",
"integrity": "sha512-uqV3D0AOLqQ1f/Bo6ZtuDLoJYGlBm0RlucNYhNvfMAncEw0f/xHc1pTyXeWjdCI/iWgWR/MV6hgVi26TSPs+Vg==",
"license": "MIT",
"dependencies": {
"@lexical/internal": "0.49.0",
"@lexical/selection": "0.49.0",
"lexical": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2",
"yjs": ">=13.5.22"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz",
@@ -1317,6 +1864,16 @@
"node": ">=12.4.0"
}
},
"node_modules/@preact/signals-core": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.4.tgz",
"integrity": "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1666,6 +2223,12 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.67.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz",
@@ -4568,6 +5131,17 @@
"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",
"peer": true,
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad"
}
},
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -4733,6 +5307,45 @@
"node": ">= 0.8.0"
}
},
"node_modules/lexical": {
"version": "0.49.0",
"resolved": "https://registry.npmjs.org/lexical/-/lexical-0.49.0.tgz",
"integrity": "sha512-9V1ZIzGpJEd8rIN+nN7veL4fW4fFWbS66Un4JNqSZB4D5t9euzN9+3+jEXy83FjNjNy0MiiUI3+DQaGCLYko0w==",
"license": "MIT",
"dependencies": {
"@lexical/internal": "0.49.0"
},
"peerDependencies": {
"typescript": ">=5.2"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/lib0": {
"version": "0.2.117",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz",
"integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==",
"license": "MIT",
"peer": true,
"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.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -6255,6 +6868,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tabbable": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz",
"integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==",
"license": "MIT"
},
"node_modules/tailwindcss": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
@@ -6477,7 +7096,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -6738,6 +7357,24 @@
"dev": true,
"license": "ISC"
},
"node_modules/yjs": {
"version": "13.6.32",
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.32.tgz",
"integrity": "sha512-lfiJIIC4Xayt5ItynE407ehlE03pCjeOc4hkR4yxxvvNJ4kuiN25B0g+Qp8XagYz361LLL7DCzR5bvFJ81QKtQ==",
"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",

View File

@@ -10,6 +10,15 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@lexical/history": "^0.49.0",
"@lexical/html": "^0.49.0",
"@lexical/link": "^0.49.0",
"@lexical/list": "^0.49.0",
"@lexical/markdown": "^0.49.0",
"@lexical/react": "^0.49.0",
"@lexical/rich-text": "^0.49.0",
"@lexical/utils": "^0.49.0",
"lexical": "^0.49.0",
"next": "16.3.1",
"react": "19.2.8",
"react-dom": "19.2.8",

View File

@@ -1,6 +1,7 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import { cmsApi } from '@/lib/api-client';
import LexicalEditor from '@/features/cms/components/LexicalEditor';
interface Page { id: string; slug: string; title: string; body: string; status: string; createdAt: string; updatedAt: string; }
@@ -92,7 +93,11 @@ export default function CmsPage() {
))}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Contenido *</label>
<textarea value={form.body} onChange={e => setForm({...form,body:e.target.value})} rows={6} className="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-[#2D6A4F] outline-none resize-none font-mono" />
<LexicalEditor
value={form.body}
onChange={(html) => setForm((f) => ({ ...f, body: html }))}
placeholder="Escribe el contenido de la página…"
/>
</div>
<div className="flex gap-3">
<button onClick={handleSave} disabled={saving || !form.slug || !form.title || !form.body} className="px-5 py-2.5 bg-[#2D6A4F] hover:bg-[#1B4332] disabled:opacity-50 text-white text-sm font-semibold rounded-xl">{saving ? 'Guardando...' : (editing ? 'Guardar cambios' : 'Crear')}</button>

View File

@@ -0,0 +1,360 @@
'use client';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
import { HeadingNode, QuoteNode, $createHeadingNode } from '@lexical/rich-text';
import { ListItemNode, ListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
import { LinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
import {
$getRoot,
$createParagraphNode,
EditorState,
LexicalEditor as LexicalEditorInstance,
FORMAT_TEXT_COMMAND,
$isElementNode,
ElementNode,
$getSelection,
$isRangeSelection,
$createParagraphNode as createParagraph,
} from 'lexical';
import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
const EMPTY_EDITOR_STATE = JSON.stringify({
root: {
children: [
{
children: [],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'root',
version: 1,
},
});
const editorTheme = {
paragraph: 'lex-paragraph',
heading: {
h1: 'lex-h1',
h2: 'lex-h2',
h3: 'lex-h3',
h4: 'lex-h4',
},
list: {
ul: 'lex-ul',
ol: 'lex-ol',
listitem: 'lex-li',
},
link: 'lex-link',
text: {
bold: 'lex-bold',
italic: 'lex-italic',
underline: 'lex-underline',
},
};
function makeConfig(disabled: boolean) {
return {
namespace: 'MDVCmsEditor',
nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode],
editorState: EMPTY_EDITOR_STATE,
editable: !disabled,
onError(error: Error) {
throw error;
},
theme: editorTheme,
};
}
type FormatState = { bold: boolean; italic: boolean; underline: boolean };
function readFormatFromDom(): FormatState {
if (typeof window === 'undefined') return { bold: false, italic: false, underline: false };
const domSel = window.getSelection?.();
let bold = false;
let italic = false;
let underline = false;
if (domSel && domSel.rangeCount > 0) {
let node: Node | null = domSel.getRangeAt(0).startContainer;
while (node && node !== document.body) {
if (node instanceof HTMLElement) {
const fw = node.style?.fontWeight;
if (node.tagName === 'B' || node.tagName === 'STRONG' || fw === 'bold' || fw === '700') bold = true;
if (node.tagName === 'I' || node.tagName === 'EM') italic = true;
if (node.tagName === 'U' || node.style?.textDecoration?.includes('underline')) underline = true;
}
node = node.parentNode;
}
}
return { bold, italic, underline };
}
function ToolbarButton({
active,
disabled,
onClick,
title,
children,
}: {
active?: boolean;
disabled?: boolean;
onClick: (e: React.MouseEvent) => void;
title: string;
children: React.ReactNode;
}) {
return (
<button
type="button"
disabled={disabled}
onMouseDown={(e) => e.preventDefault()}
onClick={onClick}
title={title}
aria-label={title}
className={`px-2.5 py-1.5 text-sm font-medium rounded-md transition-colors ${
active ? 'bg-[#2D6A4F] text-white' : 'text-gray-700 hover:bg-gray-100'
} ${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}`}
>
{children}
</button>
);
}
function Toolbar({ disabled }: { disabled?: boolean }) {
const [editor] = useLexicalComposerContext();
const [active, setActive] = useState<FormatState>({ bold: false, italic: false, underline: false });
useEffect(() => {
return editor.registerUpdateListener(() => {
// Defer to next microtask so React state updates are batched inside the editor's update loop.
queueMicrotask(() => setActive(readFormatFromDom()));
});
}, [editor]);
const setBlockType = (type: 'paragraph' | 'h2' | 'h3') => {
editor.update(() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) return;
const anchor = selection.anchor.getNode();
let target: ElementNode | null = anchor.getParent();
while (target && target.getType() !== 'paragraph' && target.getType() !== 'heading') {
target = target.getParent();
}
if (!target) return;
const currentType = target.getType();
if (type === 'paragraph') {
if (currentType === 'heading') {
const para = createParagraph();
para.append(...target.getChildren());
target.replace(para);
}
} else if (currentType === 'paragraph') {
const heading = $createHeadingNode(type);
heading.append(...target.getChildren());
target.replace(heading);
} else if (currentType === 'heading') {
// Re-create the heading so the new tag is honoured (ElementNode type is immutable).
const heading = $createHeadingNode(type);
heading.append(...target.getChildren());
target.replace(heading);
}
});
};
return (
<div className="flex flex-wrap gap-1 border-b border-gray-200 p-2 bg-gray-50 rounded-t-xl">
<ToolbarButton
disabled={disabled}
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')}
title="Negrita (Ctrl+B)"
active={active.bold}
>
<span className="font-bold">B</span>
</ToolbarButton>
<ToolbarButton
disabled={disabled}
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')}
title="Cursiva (Ctrl+I)"
active={active.italic}
>
<span className="italic">I</span>
</ToolbarButton>
<ToolbarButton
disabled={disabled}
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')}
title="Subrayado (Ctrl+U)"
active={active.underline}
>
<span className="underline">U</span>
</ToolbarButton>
<div className="w-px bg-gray-300 mx-1 self-stretch" />
<ToolbarButton
disabled={disabled}
onClick={() => setBlockType('h2')}
title="Encabezado 2"
>
<span className="text-xs">H2</span>
</ToolbarButton>
<ToolbarButton
disabled={disabled}
onClick={() => setBlockType('h3')}
title="Encabezado 3"
>
<span className="text-xs">H3</span>
</ToolbarButton>
<ToolbarButton
disabled={disabled}
onClick={() => setBlockType('paragraph')}
title="Párrafo normal"
>
<span className="text-xs"></span>
</ToolbarButton>
<div className="w-px bg-gray-300 mx-1 self-stretch" />
<ToolbarButton
disabled={disabled}
onClick={() => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)}
title="Lista con viñetas"
>
</ToolbarButton>
<ToolbarButton
disabled={disabled}
onClick={() => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)}
title="Lista numerada"
>
1.
</ToolbarButton>
<ToolbarButton
disabled={disabled}
onClick={() => {
if (typeof window === 'undefined') return;
const url = window.prompt('URL del enlace (vacío para quitar):', 'https://');
if (url === null) return;
editor.dispatchCommand(TOGGLE_LINK_COMMAND, url === '' ? null : url);
}}
title="Insertar / quitar enlace"
>
🔗
</ToolbarButton>
</div>
);
}
function InitialHtmlPlugin({ initialHtml }: { initialHtml: string }) {
const [editor] = useLexicalComposerContext();
const applied = useRef(false);
useEffect(() => {
if (applied.current) return;
if (!initialHtml || !initialHtml.trim()) return;
applied.current = true;
editor.update(() => {
const parser = new DOMParser();
const domDoc = parser.parseFromString(`<div>${initialHtml}</div>`, 'text/html');
const nodes = $generateNodesFromDOM(editor, domDoc.body);
const root = $getRoot();
root.clear();
const ensured: ElementNode[] = [];
nodes.forEach((node) => {
if ($isElementNode(node)) {
ensured.push(node);
} else {
const p = $createParagraphNode();
p.append(node as never);
ensured.push(p);
}
});
if (ensured.length === 0) {
ensured.push($createParagraphNode());
}
ensured.forEach((n) => root.append(n));
});
}, [editor, initialHtml]);
return null;
}
export interface LexicalEditorProps {
value: string;
onChange: (html: string) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
}
export default function LexicalEditor({
value,
onChange,
placeholder = 'Escribe el contenido…',
disabled,
className = '',
}: LexicalEditorProps) {
const handleChange = useCallback(
(editorState: EditorState, _editor: LexicalEditorInstance) => {
editorState.read(() => {
const html = $generateHtmlFromNodes(_editor, null);
onChange(html);
});
},
[onChange],
);
const config = useMemo(() => makeConfig(!!disabled), [disabled]);
return (
<div className={`border border-gray-300 rounded-xl overflow-hidden bg-white ${className}`}>
<LexicalComposer initialConfig={config}>
<Toolbar disabled={disabled} />
<div className="relative">
<RichTextPlugin
contentEditable={
<ContentEditable
className="min-h-[220px] px-4 py-3 text-sm text-gray-800 focus:outline-none prose-sm max-w-none"
aria-label="Contenido CMS"
/>
}
placeholder={
<div className="absolute top-3 left-4 text-sm text-gray-400 pointer-events-none select-none">
{placeholder}
</div>
}
ErrorBoundary={LexicalErrorBoundary}
/>
</div>
<HistoryPlugin />
<ListPlugin />
<LinkPlugin />
<InitialHtmlPlugin initialHtml={value} />
<OnChangePlugin onChange={handleChange} ignoreSelectionChange />
</LexicalComposer>
<style jsx global>{`
.lex-paragraph { margin: 0 0 0.5rem 0; }
.lex-paragraph:last-child { margin-bottom: 0; }
.lex-h2 { font-size: 1.5rem; font-weight: 700; margin: 1rem 0 0.5rem; color: #111827; }
.lex-h3 { font-size: 1.25rem; font-weight: 600; margin: 0.75rem 0 0.5rem; color: #111827; }
.lex-h4 { font-size: 1.05rem; font-weight: 600; margin: 0.5rem 0 0.25rem; color: #111827; }
.lex-ul { list-style: disc; padding-left: 1.5rem; margin: 0.25rem 0; }
.lex-ol { list-style: decimal; padding-left: 1.5rem; margin: 0.25rem 0; }
.lex-li { margin: 0.125rem 0; }
.lex-link { color: #2D6A4F; text-decoration: underline; }
.lex-bold { font-weight: 700; }
.lex-italic { font-style: italic; }
.lex-underline { text-decoration: underline; }
`}</style>
</div>
);
}

File diff suppressed because one or more lines are too long