mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-09 20:29:17 +02:00
Render multiple images in note as gallery
This commit is contained in:
parent
c6b5df0426
commit
1148093517
5
.changeset/silver-phones-develop.md
Normal file
5
.changeset/silver-phones-develop.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Render multiple images as image gallery
|
@ -1,8 +1,10 @@
|
||||
import { Box, Image, ImageProps, Link, useDisclosure } from "@chakra-ui/react";
|
||||
import { Box, Image, ImageProps, Link, SimpleGrid, useDisclosure } from "@chakra-ui/react";
|
||||
import appSettings from "../../services/settings/app-settings";
|
||||
import { ImageGalleryLink } from "../image-gallery";
|
||||
import { useTrusted } from "../../providers/trust";
|
||||
import OpenGraphCard from "../open-graph-card";
|
||||
import { EmbedableContent, defaultGetLocation } from "../../helpers/embeds";
|
||||
import { matchLink } from "../../helpers/regexp";
|
||||
|
||||
const BlurredImage = (props: ImageProps) => {
|
||||
const { isOpen, onOpen } = useDisclosure();
|
||||
@ -26,13 +28,21 @@ const BlurredImage = (props: ImageProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const EmbeddedImage = ({ src }: { src: string }) => {
|
||||
const EmbeddedImage = ({ src, inGallery }: { src: string; inGallery?: boolean }) => {
|
||||
const trusted = useTrusted();
|
||||
const ImageComponent = trusted || !appSettings.value.blurImages ? Image : BlurredImage;
|
||||
const thumbnail = appSettings.value.imageProxy
|
||||
? new URL(`/256,fit/${src}`, appSettings.value.imageProxy).toString()
|
||||
: src;
|
||||
|
||||
if (inGallery) {
|
||||
return (
|
||||
<ImageGalleryLink href={src} target="_blank">
|
||||
<ImageComponent src={thumbnail} cursor="pointer" />
|
||||
</ImageGalleryLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ImageGalleryLink href={src} target="_blank" display="block" mx="-2">
|
||||
<ImageComponent src={thumbnail} cursor="pointer" maxH={["initial", "35vh"]} mx={["auto", 0]} />
|
||||
@ -40,8 +50,79 @@ const EmbeddedImage = ({ src }: { src: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
// note1n06jceulg3gukw836ghd94p0ppwaz6u3mksnnz960d8vlcp2fnqsgx3fu9
|
||||
function ImageGallery({ images }: { images: string[] }) {
|
||||
return (
|
||||
<SimpleGrid columns={[2, 2, 3, 3, 4]}>
|
||||
{images.map((src) => (
|
||||
<EmbeddedImage key={src} src={src} inGallery />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
);
|
||||
}
|
||||
|
||||
const imageExt = [".svg", ".gif", ".png", ".jpg", ".jpeg", ".webp", ".avif"];
|
||||
export function embedImageGallery(content: EmbedableContent): EmbedableContent {
|
||||
return content
|
||||
.map((subContent, i) => {
|
||||
if (typeof subContent === "string") {
|
||||
const matches = Array.from(subContent.matchAll(matchLink));
|
||||
|
||||
const newContent: EmbedableContent = [];
|
||||
let lastBatchEnd = 0;
|
||||
let batch: RegExpMatchArray[] = [];
|
||||
|
||||
const renderBatch = () => {
|
||||
if (batch.length > 1) {
|
||||
// render previous batch
|
||||
const lastMatchPosition = defaultGetLocation(batch[batch.length - 1]);
|
||||
const before = subContent.substring(lastBatchEnd, defaultGetLocation(batch[0]).start);
|
||||
const render = <ImageGallery images={batch.map((m) => m[0])} />;
|
||||
|
||||
newContent.push(before, render);
|
||||
lastBatchEnd = lastMatchPosition.end;
|
||||
}
|
||||
|
||||
batch = [];
|
||||
};
|
||||
|
||||
for (const match of matches) {
|
||||
try {
|
||||
const url = new URL(match[0]);
|
||||
if (!imageExt.some((ext) => url.pathname.endsWith(ext))) throw new Error("not an image");
|
||||
|
||||
// if this is the first image, add it to the batch
|
||||
if (batch.length === 0) {
|
||||
batch = [match];
|
||||
continue;
|
||||
}
|
||||
|
||||
const last = defaultGetLocation(batch[batch.length - 1]);
|
||||
const position = defaultGetLocation(match);
|
||||
const space = subContent.substring(last.end, position.start).trim();
|
||||
|
||||
// if there was a non-space between this and the last batch
|
||||
if (space.length > 0) renderBatch();
|
||||
|
||||
batch.push(match);
|
||||
} catch (e) {
|
||||
// start a new batch without current match
|
||||
batch = [];
|
||||
}
|
||||
}
|
||||
|
||||
renderBatch();
|
||||
|
||||
newContent.push(subContent.substring(lastBatchEnd));
|
||||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
return subContent;
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
|
||||
// note1n06jceulg3gukw836ghd94p0ppwaz6u3mksnnz960d8vlcp2fnqsgx3fu9
|
||||
export function renderImageUrl(match: URL) {
|
||||
if (!imageExt.some((ext) => match.pathname.endsWith(ext))) return null;
|
||||
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
renderVideoUrl,
|
||||
embedEmoji,
|
||||
renderOpenGraphUrl,
|
||||
embedImageGallery,
|
||||
} from "../embed-types";
|
||||
import { ImageGalleryProvider } from "../image-gallery";
|
||||
import { renderRedditUrl } from "../embed-types/reddit";
|
||||
@ -24,6 +25,9 @@ import { renderRedditUrl } from "../embed-types/reddit";
|
||||
function buildContents(event: NostrEvent | DraftNostrEvent) {
|
||||
let content: EmbedableContent = [event.content.trim()];
|
||||
|
||||
// image gallery
|
||||
content = embedImageGallery(content);
|
||||
|
||||
// common
|
||||
content = embedUrls(content, [
|
||||
renderYoutubeUrl,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { cloneElement } from "react";
|
||||
import { matchLink } from "./regexp";
|
||||
|
||||
export type EmbedableContent = (string | JSX.Element)[];
|
||||
export type EmbedType = {
|
||||
@ -8,7 +9,7 @@ export type EmbedType = {
|
||||
getLocation?: (match: RegExpMatchArray) => { start: number; end: number };
|
||||
};
|
||||
|
||||
function defaultGetLocation(match: RegExpMatchArray) {
|
||||
export function defaultGetLocation(match: RegExpMatchArray) {
|
||||
if (match.index === undefined) throw new Error("match dose not have index");
|
||||
return {
|
||||
start: match.index,
|
||||
@ -72,7 +73,7 @@ export type LinkEmbedHandler = (link: URL) => JSX.Element | string | null;
|
||||
export function embedUrls(content: EmbedableContent, handlers: LinkEmbedHandler[]) {
|
||||
return embedJSX(content, {
|
||||
name: "embedUrls",
|
||||
regexp: /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:]*)/giu,
|
||||
regexp: matchLink,
|
||||
render: (match) => {
|
||||
try {
|
||||
const url = new URL(match[0]);
|
||||
|
@ -4,3 +4,4 @@ export const matchImageUrls =
|
||||
|
||||
export const matchNostrLink = /(nostr:|@)?((npub|note|nprofile|nevent)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58,})/gi;
|
||||
export const matchHashtag = /(^|[^\p{L}])#([\p{L}\p{N}]+)/giu;
|
||||
export const matchLink = /https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:]*)/gu;
|
||||
|
Loading…
x
Reference in New Issue
Block a user