diff --git a/package.json b/package.json index 81a67f83..35838eb2 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "lottie-react": "^2.4.0", "mdast-util-to-string": "3.2.0", "mdast-util-toc": "6.1.1", + "medium-zoom": "1.0.8", "mermaid": "10.1.0", "nanoid": "4.0.2", "next": "13.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a897d524..eac46757 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,6 +154,9 @@ dependencies: mdast-util-toc: specifier: 6.1.1 version: 6.1.1 + medium-zoom: + specifier: 1.0.8 + version: 1.0.8 mermaid: specifier: 10.1.0 version: 10.1.0(react-dom@18.2.0)(react@18.2.0) @@ -10170,6 +10173,10 @@ packages: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true + /medium-zoom@1.0.8: + resolution: {integrity: sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==} + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true diff --git a/src/components/dashboard/ImportPreview.tsx b/src/components/dashboard/ImportPreview.tsx index 84675b2b..44b496de 100644 --- a/src/components/dashboard/ImportPreview.tsx +++ b/src/components/dashboard/ImportPreview.tsx @@ -1,10 +1,8 @@ -import { useEffect, useRef, useState } from "react" -import useOnClickOutside from "use-onclickoutside" import type { NoteMetadata } from "crossbell.js" -import { PageContent } from "../common/PageContent" +import { useState } from "react" import { useDate } from "~/hooks/useDate" +import { PageContent } from "../common/PageContent" -import { Button, ButtonGroup } from "../ui/Button" import { useTranslation } from "next-i18next" export const ImportPreview: React.FC<{ diff --git a/src/components/ui/Avatar.tsx b/src/components/ui/Avatar.tsx index b79fc8e9..7e22c286 100644 --- a/src/components/ui/Avatar.tsx +++ b/src/components/ui/Avatar.tsx @@ -9,7 +9,7 @@ export const Avatar: React.FC< name?: string | null size?: number rounded?: boolean - imageRef?: React.Ref + imageRef?: React.MutableRefObject } & React.HTMLAttributes > = ({ images, size, name, className, rounded, imageRef, ...props }) => { size = size || 60 diff --git a/src/components/ui/Image.tsx b/src/components/ui/Image.tsx index 871c5b37..43aebabd 100644 --- a/src/components/ui/Image.tsx +++ b/src/components/ui/Image.tsx @@ -1,22 +1,70 @@ -import React from "react" +import React, { useEffect } from "react" import { default as NextImage, ImageProps } from "next/image" import { toGateway, toIPFS } from "~/lib/ipfs-parser" +import { useMobileLayout } from "~/hooks/useMobileLayout" +import { useGetState } from "~/hooks/useGetState" -export const Image: React.FC< - { - className?: string - src?: string - width?: number | string - height?: number | string - "original-src"?: string - imageRef?: React.Ref - } & React.HTMLAttributes & - ImageProps -> = ({ fill, className, alt, src, width, height, imageRef, ...props }) => { +type TImageProps = { + className?: string + src?: string + width?: number | string + height?: number | string + "original-src"?: string + imageRef?: React.MutableRefObject + zoom?: boolean +} & React.HTMLAttributes & + ImageProps + +export const Image: React.FC = ({ + fill, + className, + alt, + src, + width, + height, + imageRef, + zoom, + ...props +}) => { src = toIPFS(src) const [paddingTop, setPaddingTop] = React.useState("0") const [autoWidth, setAutoWidth] = React.useState(0) const noOptimization = className?.includes("no-optimization") + const imageRefInternal = React.useRef(null) + + const isMobileLayout = useMobileLayout() + const getSrc = useGetState(src) + + useEffect(() => { + if (!imageRef) return + if (!imageRefInternal.current) return + + if (typeof imageRef === "object") { + imageRef.current = imageRefInternal.current + } + }, [imageRef]) + useEffect(() => { + const $image = imageRefInternal.current + if (!$image) return + if (isMobileLayout) { + const clickHandler = () => { + window.open(getSrc(), "_blank") + } + $image.addEventListener("click", clickHandler) + return () => { + $image.removeEventListener("click", clickHandler) + } + } + if (zoom) { + import("medium-zoom").then(({ default: mediumZoom }) => { + mediumZoom($image, { + margin: 10, + background: "rgb(var(--tw-colors-i-white))", + scrollOffset: 0, + }) + }) + } + }, [zoom, isMobileLayout]) if (!src) { return null @@ -57,7 +105,7 @@ export const Image: React.FC< setAutoWidth(naturalWidth) } }} - ref={imageRef} + ref={imageRefInternal} /> ) : ( ) } + +export const ZoomedImage: React.FC = (props) => { + return ( + + {/* eslint-disable-next-line jsx-a11y/alt-text */} + + + ) +} diff --git a/src/markdown/index.ts b/src/markdown/index.ts index 42b453d8..82a8ef84 100644 --- a/src/markdown/index.ts +++ b/src/markdown/index.ts @@ -16,7 +16,7 @@ import { rehypeWrapCode } from "./rehype-wrap-code" import jsYaml from "js-yaml" import rehypeReact from "rehype-react" import { createElement, ReactElement } from "react" -import { Image } from "~/components/ui/Image" +import { ZoomedImage } from "~/components/ui/Image" import remarkDirective from "remark-directive" import remarkDirectiveRehype from "remark-directive-rehype" import { remarkYoutube } from "./remark-youtube" @@ -177,7 +177,7 @@ export const renderPageContent = ( .use(html ? () => (tree: any) => {} : rehypeReact, { createElement: createElement, components: { - img: Image, + img: ZoomedImage, anchor: Element, mention: Mention, mermaid: Mermaid,