diff --git a/src/components/EditorContent/ImagePreview.jsx b/src/components/EditorContent/ImagePreview.jsx
new file mode 100644
index 00000000..ec2bfc0b
--- /dev/null
+++ b/src/components/EditorContent/ImagePreview.jsx
@@ -0,0 +1,52 @@
+import React, { useEffect, useRef, useState } from "react";
+
+import { useOnClickOutside } from "neetocommons/react-utils";
+import { Close } from "neetoicons";
+import { Button, Spinner } from "neetoui";
+import { createPortal } from "react-dom";
+
+const ImagePreview = ({ imagePreviewUrl, setImagePreviewUrl }) => {
+ const [isLoading, setIsLoading] = useState(true);
+
+ const imagePreviewRef = useRef(null);
+
+ useOnClickOutside(imagePreviewRef, () => setImagePreviewUrl(null), {
+ enabled: true,
+ });
+
+ useEffect(() => {
+ document.addEventListener(
+ "keydown",
+ e => e.key === "Escape" && setImagePreviewUrl(null)
+ );
+
+ return () =>
+ document.removeEventListener(
+ "keydown",
+ e => e.key === "Escape" && setImagePreviewUrl(null)
+ );
+ }, []);
+
+ return createPortal(
+
+
+
+ {isLoading &&
}
+
setIsLoading(false)}
+ />
+
,
+ document.body
+ );
+};
+
+export default ImagePreview;
diff --git a/src/components/EditorContent/index.jsx b/src/components/EditorContent/index.jsx
index 92055bfb..3b1b9c92 100644
--- a/src/components/EditorContent/index.jsx
+++ b/src/components/EditorContent/index.jsx
@@ -1,11 +1,13 @@
-import React, { useEffect, useRef } from "react";
+import React, { useEffect, useRef, useState } from "react";
import classnames from "classnames";
import DOMPurify from "dompurify";
import CopyToClipboardButton from "neetomolecules/CopyToClipboardButton";
+import { not } from "ramda";
import { createRoot } from "react-dom/client";
import { EDITOR_CONTENT_CLASSNAME, SANITIZE_OPTIONS } from "./constants";
+import ImagePreview from "./ImagePreview";
import { highlightCode, substituteVariables } from "./utils";
const EditorContent = ({
@@ -14,6 +16,7 @@ const EditorContent = ({
className,
...otherProps
}) => {
+ const [imagePreviewUrl, setImagePreviewUrl] = useState(null);
const editorContentRef = useRef(null);
const htmlContent = substituteVariables(highlightCode(content), variables);
@@ -37,6 +40,22 @@ const EditorContent = ({
);
preTag.appendChild(button);
});
+
+ const figureTags = editorContentRef.current?.querySelectorAll("figure");
+ figureTags.forEach(figureTag => {
+ const image = figureTag.querySelector("img");
+ const link = figureTag.querySelector("a");
+ if (
+ !image ||
+ not(window.getComputedStyle(link).pointerEvents === "none")
+ ) {
+ return;
+ }
+ figureTag.style.cursor = "pointer";
+ figureTag.addEventListener("click", () => {
+ setImagePreviewUrl(image.src);
+ });
+ });
};
useEffect(() => {
@@ -44,17 +63,22 @@ const EditorContent = ({
}, [content]);
return (
-
+ <>
+
+ {imagePreviewUrl && (
+
+ )}
+ >
);
};
diff --git a/src/index.scss b/src/index.scss
index e467a127..d76db390 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -7,6 +7,7 @@
@import "./styles/editor/editor-content";
@import "./styles/editor/menu";
@import "./styles/editor/media-uploader";
+@import "./styles/editor/image-preview";
@import "./styles/editor/emoji";
@import "./styles/editor/table";
@import "./styles/editor/link-popover";
diff --git a/src/styles/editor/_image-preview.scss b/src/styles/editor/_image-preview.scss
new file mode 100644
index 00000000..3529e8b8
--- /dev/null
+++ b/src/styles/editor/_image-preview.scss
@@ -0,0 +1,29 @@
+.image-preview-wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(var(--neeto-ui-gray-800), 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 999999;
+
+ .close-button {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ }
+
+ .image-preview {
+ max-width: 70%;
+ max-height: 70%;
+ }
+
+ .spinner {
+ i {
+ background-color: rgb(var(--neeto-ui-gray-300));
+ }
+ }
+}