feat(F-062): completed feature
This commit is contained in:
639
project/apps/admin/package-lock.json
generated
639
project/apps/admin/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
360
project/apps/admin/src/features/cms/components/LexicalEditor.tsx
Normal file
360
project/apps/admin/src/features/cms/components/LexicalEditor.tsx
Normal 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
Reference in New Issue
Block a user