mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Intégration de lightbox pas foncitonnelle
This commit is contained in:
parent
85988697d4
commit
9f91e974a4
6 changed files with 283 additions and 116 deletions
54
client/src/GestionImages/enableLightbox.tsx
Normal file
54
client/src/GestionImages/enableLightbox.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<Lightbox
|
||||||
|
open={open}
|
||||||
|
close={() => 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(<LightboxWrapper images={all} index={index} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
31
client/src/GestionImages/markedConfig.ts
Normal file
31
client/src/GestionImages/markedConfig.ts
Normal file
|
|
@ -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 `
|
||||||
|
<img
|
||||||
|
src="${href}"
|
||||||
|
alt="${text}"
|
||||||
|
class="markdown-image"
|
||||||
|
data-src="${href}"
|
||||||
|
style="
|
||||||
|
display: block;
|
||||||
|
margin: 1rem auto;
|
||||||
|
cursor: zoom-in;
|
||||||
|
max-height: 15rem;
|
||||||
|
max-width: 100%;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
marked.use({ renderer });
|
||||||
|
|
||||||
|
export default marked;
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import marked, {attachImageModalListeners} from 'src/markedConfig';
|
import { marked } from 'marked';
|
||||||
|
|
||||||
import katex from 'katex';
|
import katex from 'katex';
|
||||||
import { TextFormat } from 'gift-pegjs';
|
import { TextFormat } from 'gift-pegjs';
|
||||||
import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc.
|
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)
|
// Strip outer paragraph tags (not a great approach with regex)
|
||||||
result = formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
result = formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
||||||
break;
|
break;
|
||||||
case 'markdown':
|
case 'markdown':
|
||||||
parsedText = marked.parse(formatText, { breaks: true, gfm: true }) as string; // <br> for newlines
|
parsedText = marked.parse(formatText, { breaks: true, gfm: true }) as string;
|
||||||
attachImageModalListeners();
|
result = parsedText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
||||||
result = parsedText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
break;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported text format: ${formattedText.format}`);
|
throw new Error(`Unsupported text format: ${formattedText.format}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 `
|
|
||||||
<div style="max-height: ${maxHeight}; width: ${width || 'auto'}; height: ${height || 'auto'}; text-align: center; display: flex; justify-content: center; align-items: center">
|
|
||||||
<img
|
|
||||||
src="${href}"
|
|
||||||
alt="${text}"
|
|
||||||
class="modal-image"
|
|
||||||
style="max-height: ${maxHeight}; width: ${width || 'auto'}; height: ${height || 'auto'}; cursor: zoom-in;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
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;
|
|
||||||
185
package-lock.json
generated
Normal file
185
package-lock.json
generated
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
package.json
Normal file
7
package.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"marked": "^15.0.8",
|
||||||
|
"react-image-lightbox": "^5.1.4",
|
||||||
|
"yet-another-react-lightbox": "^3.22.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue