diff --git a/client/src/GestionImages/enableLightbox.tsx b/client/src/GestionImages/enableLightbox.tsx new file mode 100644 index 0000000..20b83f5 --- /dev/null +++ b/client/src/GestionImages/enableLightbox.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import ReactDOM from 'react-dom/client'; +import Lightbox from 'yet-another-react-lightbox'; +import 'yet-another-react-lightbox/styles.css'; + +let root: ReactDOM.Root | null = null; +let container: HTMLDivElement | null = null; + +function ensureContainer() { + if (!container) { + container = document.createElement('div'); + container.id = 'react-lightbox-container'; + document.body.appendChild(container); + root = ReactDOM.createRoot(container); + } +} + +function LightboxWrapper({ images, index }: { images: string[]; index: number }) { + const [open, setOpen] = useState(true); + const [currentIndex, setCurrentIndex] = useState(index); + + if (!open) return null; + + return ( + setOpen(false)} + slides={images.map((src) => ({ src }))} + index={currentIndex} + on={{ view: ({ index }) => setCurrentIndex(index) }} + /> + ); +} + +// Appelée depuis le DOM +window.openLightboxFromDOM = (src: string, all: string[], index: number) => { + console.log("Opening lightbox for:", src); + ensureContainer(); + root?.render(); +}; + +// Auto-attach au chargement, exactement comme ton ancienne version +document.addEventListener('click', (e) => { + const target = e.target as HTMLElement; + if (target.tagName === 'IMG' && target.classList.contains('markdown-image')) { + const clickedSrc = target.getAttribute('src'); + const allImgs = Array.from(document.querySelectorAll('img.markdown-image')) as HTMLImageElement[]; + const allSrcs = allImgs.map((img) => img.src); + const index = allSrcs.indexOf(clickedSrc || ''); + if (index !== -1) { + window.openLightboxFromDOM(clickedSrc!, allSrcs, index); + } + } +}); diff --git a/client/src/GestionImages/markedConfig.ts b/client/src/GestionImages/markedConfig.ts new file mode 100644 index 0000000..024c2f3 --- /dev/null +++ b/client/src/GestionImages/markedConfig.ts @@ -0,0 +1,31 @@ +import { marked, Renderer } from 'marked'; + +declare global { + interface Window { + openLightboxFromDOM: (src: string, all: string[], index: number) => void; + } +} + +const renderer = new Renderer(); + +renderer.image = ({ href, text }) => { + return ` + ${text} + `; +}; + +marked.use({ renderer }); + +export default marked; diff --git a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts index 548e32c..cd27017 100644 --- a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts @@ -1,5 +1,4 @@ -import marked, {attachImageModalListeners} from 'src/markedConfig'; - +import { marked } from 'marked'; import katex from 'katex'; import { TextFormat } from 'gift-pegjs'; import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc. @@ -51,11 +50,11 @@ export function FormattedTextTemplate(formattedText: TextFormat): string { // Strip outer paragraph tags (not a great approach with regex) result = formatText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2'); break; - case 'markdown': - parsedText = marked.parse(formatText, { breaks: true, gfm: true }) as string; //
for newlines - attachImageModalListeners(); - result = parsedText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2'); - break; + case 'markdown': + parsedText = marked.parse(formatText, { breaks: true, gfm: true }) as string; + result = parsedText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2'); + break; + default: throw new Error(`Unsupported text format: ${formattedText.format}`); } diff --git a/client/src/markedConfig.ts b/client/src/markedConfig.ts deleted file mode 100644 index fac63ac..0000000 --- a/client/src/markedConfig.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { marked, Renderer } from 'marked'; - -// Customized renderer to support image width and height -// see https://github.com/markedjs/marked/issues/339#issuecomment-1146363560 -declare global { - interface Window { - showImageInModal: (src: string, alt: string) => void; - } -} - -const renderer = new Renderer(); - -renderer.image = ({ href, title, text }) => { - const [width, height] = title?.startsWith('=') ? title.slice(1).split('x').map(v => v.trim()).filter(Boolean) : []; - const maxHeight = '15rem'; // Limite maximale de hauteur - - return ` -

- ${text} -
- `; -}; -marked.use({ - renderer: renderer -}); - - -if (typeof window !== 'undefined') { - window.showImageInModal = (src, alt) => { - console.log('showImageInModal called with:', src, alt); - - // Check if a modal already exists - const existingModal = document.querySelector('.image-modal'); - if (existingModal) { - // If the modal exists, remove it (close the modal) - document.body.removeChild(existingModal); - return; - } - - // Create the modal container - const modal = document.createElement('div'); - modal.className = 'image-modal'; // Add a class to identify the modal - modal.style.position = 'fixed'; - modal.style.top = '0'; - modal.style.left = '0'; - modal.style.width = '100vw'; - modal.style.height = '100vh'; - modal.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; - modal.style.display = 'flex'; - modal.style.justifyContent = 'center'; - modal.style.alignItems = 'center'; - modal.style.zIndex = '1000'; - - // Close the modal when the modal itself is clicked - modal.onclick = () => { - document.body.removeChild(modal); - }; - - // Create the image element - const img = document.createElement('img'); - img.src = src; - img.alt = alt; - - img.style.minWidth = '40%'; - img.style.minHeight = '40%'; - - img.style.cursor = 'zoom-out'; - - // Allow the modal to close when clicking on the image - img.onclick = () => { - document.body.removeChild(modal); - }; - - // Append the image to the modal and the modal to the body - modal.appendChild(img); - document.body.appendChild(modal); - }; -} - -export const attachImageModalListeners = () => { - const images = document.querySelectorAll('.modal-image'); - - images.forEach((image) => { - // Remove any existing event listeners by cloning the element - const newImage = image.cloneNode(true) as HTMLElement; - image.parentNode?.replaceChild(newImage, image); - - // Attach a new event listener - newImage.addEventListener('click', (event) => { - const target = event.target as HTMLImageElement; - const src = target.getAttribute('src'); - const alt = target.getAttribute('alt'); - if (src) { - window.showImageInModal(src, alt || ''); - } - }); - }); -}; - -marked.use({ - renderer: renderer, -}); - -export default marked; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0478241 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,185 @@ +{ + "name": "EvalueTonSavoir", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "marked": "^15.0.8", + "react-image-lightbox": "^5.1.4", + "yet-another-react-lightbox": "^3.22.0" + } + }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/marked": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.8.tgz", + "integrity": "sha512-rli4l2LyZqpQuRve5C0rkn6pj3hT8EWPC+zkAxFTAJLxRbENfTAhEQq9itrmf1Y81QtAX5D/MYlGlIomNgj9lA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/react-image-lightbox": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/react-image-lightbox/-/react-image-lightbox-5.1.4.tgz", + "integrity": "sha512-kTiAODz091bgT7SlWNHab0LSMZAPJtlNWDGKv7pLlLY1krmf7FuG1zxE0wyPpeA8gPdwfr3cu6sPwZRqWsc3Eg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2", + "react-modal": "^3.11.1" + }, + "peerDependencies": { + "react": "16.x || 17.x", + "react-dom": "16.x || 17.x" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, + "node_modules/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==", + "license": "MIT", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" + } + }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/yet-another-react-lightbox": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/yet-another-react-lightbox/-/yet-another-react-lightbox-3.22.0.tgz", + "integrity": "sha512-yaXmzUraH/Ftsp7eG/E2leQgXhtrG8c1t+jImlSjC2XtZ7XkvjIV2vP/1kl5kxmsBHjck/98W/9Xxempry+2QQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@types/react": "^16 || ^17 || ^18 || ^19", + "@types/react-dom": "^16 || ^17 || ^18 || ^19", + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c603c23 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "marked": "^15.0.8", + "react-image-lightbox": "^5.1.4", + "yet-another-react-lightbox": "^3.22.0" + } +}