Merge pull request 'feat: group images into a gallery' (#639) from fernandoporazzi/snort:group-post-images into main

Reviewed-on: Kieran/snort#639
This commit is contained in:
Kieran 2023-10-05 13:10:43 +00:00
commit 7c839ae3a8
3 changed files with 166 additions and 49 deletions

View File

@ -90,3 +90,26 @@
border-left: 2px solid var(--font-secondary-color);
padding-left: 12px;
}
.gallery {
grid-template-columns: repeat(4, 1fr);
gap: 2px;
display: grid;
list-style: none;
padding: 0;
-webkit-overflow-scrolling: touch;
margin: 10px 0;
}
.gallery-item {
display: block;
position: relative;
margin: 0;
}
.gallery-item img {
object-fit: cover;
width: 100%;
height: 100%;
display: block;
}

View File

@ -27,6 +27,18 @@ export interface TextProps {
onClick?: (e: React.MouseEvent) => void;
}
const gridConfigMap = new Map<number, number[][]>([
[1, [[4, 3]]],
[2, [[2, 2], [2, 2]]],
[3, [[2, 2], [2, 1], [2, 1]]],
[4, [[2, 1], [2, 1], [2, 1], [2, 1]]],
[5, [[2, 1], [2, 1], [2, 1], [1, 1], [1, 1]]],
[6, [[2, 2], [1, 1], [1, 1], [2, 2], [1, 1], [1, 1]]],
]);
const ROW_HEIGHT = 140;
const GRID_GAP = 2;
export default function Text({
id,
content,
@ -77,67 +89,149 @@ export default function Text({
);
}
const DisableMedia = ({ content }: { content: string }) => (
<a
href={content}
onClick={(e) => e.stopPropagation()}
target="_blank"
rel="noreferrer"
className="ext"
>
{content}
</a>
);
const RevealMediaInstance = ({ content }: { content: string }) => (
<RevealMedia
key={content}
link={content}
creator={creator}
onMediaClick={(e) => {
if (!disableMediaSpotlight) {
e.stopPropagation();
e.preventDefault();
setShowSpotlight(true);
const selected = images.findIndex((b) => b === content);
setImageIdx(selected === -1 ? 0 : selected);
}
}}
/>
);
const renderContent = () => {
let lenCtr = 0;
function renderChunk(a: ParsedFragment) {
let chunks: (JSX.Element | null)[] = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (truncate) {
if (lenCtr > truncate) {
return null;
} else if (lenCtr + a.content.length > truncate) {
lenCtr += a.content.length;
return <div className="text-frag">{a.content.slice(0, truncate - lenCtr)}...</div>;
continue;
} else if (lenCtr + element.content.length > truncate) {
lenCtr += element.content.length;
chunks = chunks.concat(<div className="text-frag">{element.content.slice(0, truncate - lenCtr)}...</div>);
} else {
lenCtr += a.content.length;
lenCtr += element.content.length;
}
}
if (a.type === "media" && !a.mimeType?.startsWith("unknown")) {
if (element.type === "media" && element.mimeType?.startsWith("image")) {
if (disableMedia ?? false) {
return (
<a href={a.content} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
{a.content}
</a>
);
}
return (
<RevealMedia
link={a.content}
creator={creator}
onMediaClick={e => {
if (!disableMediaSpotlight) {
e.stopPropagation();
e.preventDefault();
setShowSpotlight(true);
const selected = images.findIndex(b => b === a.content);
setImageIdx(selected === -1 ? 0 : selected);
chunks = chunks.concat(<DisableMedia content={element.content} />);
} else {
let galleryImages: ParsedFragment[] = [element];
// If the current element is of type media and mimeType starts with image,
// we verify if the next elements are of the same type and mimeType and push to the galleryImages
// Whenever one of the next elements is not longer of the type we are looking for, we break the loop
for (let j = i; j < elements.length; j++) {
const nextElement = elements[j + 1];
if (
nextElement &&
nextElement.type === "media" &&
nextElement.mimeType?.startsWith("image")
) {
galleryImages = galleryImages.concat(nextElement);
i++;
} else {
break;
}
}
// We build a grid layout to render the grouped images
const imagesWithGridConfig = galleryImages.map((gi, index) => {
const config = gridConfigMap.get(galleryImages.length);
let height = ROW_HEIGHT;
if (config && config[index][1] > 1) {
height = (config[index][1] * ROW_HEIGHT) + GRID_GAP;
}
return {
content: gi.content,
gridColumn: config ? config[index][0] : 1,
gridRow: config ? config[index][1] : 1,
height,
}
});
const gallery = (
<div className="gallery">
{
imagesWithGridConfig.map((img) => (
<div
key={img.content}
className="gallery-item"
style={{
height: `${img.height}px`,
gridColumn: `span ${img.gridColumn}`,
gridRow: `span ${img.gridRow}`,
}}
>
<RevealMediaInstance content={img.content} />
</div>
))
}
}}
/>
);
} else {
switch (a.type) {
case "invoice":
return <Invoice invoice={a.content} />;
case "hashtag":
return <Hashtag tag={a.content} />;
case "cashu":
return <CashuNuts token={a.content} />;
case "media":
case "link":
return <HyperText link={a.content} depth={depth} showLinkPreview={!(disableLinkPreview ?? false)} />;
case "custom_emoji":
return <ProxyImg src={a.content} size={15} className="custom-emoji" />;
default:
return (
<div className="text-frag">
{highlighText ? renderContentWithHighlightedText(a.content, highlighText) : a.content}
</div>
);
</div>
);
chunks = chunks.concat(gallery);
}
}
if (element.type === "media" && (element.mimeType?.startsWith("audio") || element.mimeType?.startsWith("video"))) {
if (disableMedia ?? false) {
chunks = chunks.concat(<DisableMedia content={element.content} />);
} else {
chunks = chunks.concat(<RevealMediaInstance content={element.content} />);
}
}
if (element.type === "invoice") {
chunks = chunks.concat(<Invoice invoice={element.content} />);
}
if (element.type === "hashtag") {
chunks = chunks.concat(<Hashtag tag={element.content} />);
}
if (element.type === "cashu") {
chunks = chunks.concat(<CashuNuts token={element.content} />);
}
if(element.type === "link" || (element.type === 'media' && element.mimeType?.startsWith('unknown'))) {
chunks = chunks.concat(<HyperText link={element.content} depth={depth} showLinkPreview={!(disableLinkPreview ?? false)} />);
}
if (element.type === "custom_emoji") {
chunks = chunks.concat(<ProxyImg src={element.content} size={15} className="custom-emoji" />);
}
if (element.type === "text") {
chunks = chunks.concat(
<div className="text-frag">
{highlighText
? renderContentWithHighlightedText(element.content, highlighText)
: element.content}
</div>,
);
}
}
return elements.map(a => renderChunk(a));
return chunks;
};
return (

View File

@ -189,7 +189,7 @@ export function transformText(body: string, tags: Array<Array<string>>) {
fragments = fragments
.map(a => {
if (typeof a === "string") {
if (a.length > 0) {
if (a.trim().length > 0) {
return { type: "text", content: a } as ParsedFragment;
}
} else {