diff --git a/apps/renderer/package.json b/apps/renderer/package.json index d68e858dd..2d9feadac 100644 --- a/apps/renderer/package.json +++ b/apps/renderer/package.json @@ -76,6 +76,7 @@ "path-to-regexp": "8.1.0", "posthog-js": "1.163.0", "re-resizable": "6.9.18", + "react-blurhash": "^0.3.0", "react-error-boundary": "4.0.13", "react-fast-marquee": "1.6.5", "react-hook-form": "7.53.0", diff --git a/apps/renderer/src/components/ui/markdown/Markdown.tsx b/apps/renderer/src/components/ui/markdown/Markdown.tsx index 69cb7984d..881bf3b08 100644 --- a/apps/renderer/src/components/ui/markdown/Markdown.tsx +++ b/apps/renderer/src/components/ui/markdown/Markdown.tsx @@ -56,7 +56,7 @@ const HTMLImpl = ( useEffect(() => { setRemarkOptions((options) => { - if (renderInlineStyle === options.renderInlineStyle || noMedia === options.noMedia) { + if (JSON.stringify(options) === JSON.stringify({ renderInlineStyle, noMedia })) { return options } diff --git a/apps/renderer/src/components/ui/media/hooks.tsx b/apps/renderer/src/components/ui/media/hooks.tsx index ab9b82215..58fac6d56 100644 --- a/apps/renderer/src/components/ui/media/hooks.tsx +++ b/apps/renderer/src/components/ui/media/hooks.tsx @@ -25,6 +25,6 @@ export const usePreviewMedia = (entryId?: string) => { clickOutsideToDismiss: true, }) }, - [present], + [entryId, present], ) } diff --git a/apps/renderer/src/components/ui/media/preview-media.tsx b/apps/renderer/src/components/ui/media/preview-media.tsx index 33e51bdbe..b1fa305db 100644 --- a/apps/renderer/src/components/ui/media/preview-media.tsx +++ b/apps/renderer/src/components/ui/media/preview-media.tsx @@ -1,6 +1,7 @@ import type { MediaModel } from "@follow/shared/hono" import type { FC } from "react" import { Fragment, useCallback, useEffect, useRef, useState } from "react" +import { Blurhash } from "react-blurhash" import { Keyboard, Mousewheel } from "swiper/modules" import type { SwiperRef } from "swiper/react" import { Swiper, SwiperSlide } from "swiper/react" @@ -125,6 +126,9 @@ export const PreviewMediaContent: FC<{ className="h-full w-auto object-contain" alt="cover" src={src} + height={media[0].height} + width={media[0].width} + blurhash={media[0].blurhash} /> )} @@ -236,8 +240,9 @@ const FallbackableImage: FC< src: string containerClassName?: string fallbackUrl?: string + blurhash?: string } -> = ({ src, onError, fallbackUrl, containerClassName, ...props }) => { +> = ({ src, onError, fallbackUrl, containerClassName, blurhash, ...props }) => { const [currentSrc, setCurrentSrc] = useState(() => replaceImgUrlIfNeed(src)) const [isAllError, setIsAllError] = useState(false) @@ -281,11 +286,32 @@ const FallbackableImage: FC<
{isLoading && !isAllError && (
- + {blurhash ? ( + + ) : ( + + )}
)} {!isAllError && ( - setIsLoading(false)} onError={handleError} {...props} /> + setIsLoading(false)} + onError={handleError} + height={props.height} + width={props.width} + {...props} + className={cn( + blurhash && !isLoading ? "duration-500 ease-in-out animate-in fade-in-0" : "", + props.className, + )} + /> )} {isAllError && (
tag !== "img" && tag !== "picture", + ) + } else { + rehypeSchema.tagNames = [...rehypeSchema.tagNames!, "video", "style"] + rehypeSchema.attributes = { + ...rehypeSchema.attributes, + "*": renderInlineStyle + ? [...rehypeSchema.attributes!["*"], "style", "class"] + : rehypeSchema.attributes!["*"], + video: ["src", "poster"], + } + } + const pipeline = unified() .use(rehypeParse, { fragment: true }) - .use( - rehypeSanitize, - noMedia - ? { - ...defaultSchema, - tagNames: defaultSchema.tagNames?.filter((tag) => tag !== "img" && tag !== "picture"), - } - : { - ...defaultSchema, - tagNames: [...defaultSchema.tagNames!, "video", "style"], - attributes: { - ...defaultSchema.attributes, - - "*": renderInlineStyle - ? [...defaultSchema.attributes!["*"], "style", "class"] - : defaultSchema.attributes!["*"], - - video: ["src", "poster"], - }, - }, - ) + .use(rehypeSanitize, rehypeSchema) .use(rehypeInferDescriptionMeta) .use(rehypeStringify) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7012aeec..57c4b0a54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -522,6 +522,9 @@ importers: re-resizable: specifier: 6.9.18 version: 6.9.18(patch_hash=yitcmpfwcomg2fky72uc3lhk2i)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-blurhash: + specifier: ^0.3.0 + version: 0.3.0(blurhash@2.0.5)(react@18.3.1) react-error-boundary: specifier: 4.0.13 version: 4.0.13(react@18.3.1) @@ -3958,6 +3961,9 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + blurhash@2.0.5: + resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} + bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} @@ -7370,6 +7376,12 @@ packages: react: ^16.13.1 || ^17.0.0 || ^18.0.0 react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-blurhash@0.3.0: + resolution: {integrity: sha512-XlKr4Ns1iYFRnk6DkAblNbAwN/bTJvxTVoxMvmTcURdc5oLoXZwqAF9N3LZUh/HT+QFlq5n6IS6VsDGsviYAiQ==} + peerDependencies: + blurhash: ^2.0.3 + react: '>=15' + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -12851,6 +12863,8 @@ snapshots: bluebird@3.7.2: {} + blurhash@2.0.5: {} + bn.js@4.12.0: {} bn.js@5.2.1: {} @@ -16971,6 +16985,11 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-blurhash@0.3.0(blurhash@2.0.5)(react@18.3.1): + dependencies: + blurhash: 2.0.5 + react: 18.3.1 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0