(feat): add embeds
All checks were successful
/ build-web (push) Successful in 1m29s
/ build-desktop (linux) (push) Successful in 6m52s
/ build-mobile (push) Successful in 17m8s
/ release (push) Successful in 34s

This commit is contained in:
Alois 2026-06-09 18:44:30 +02:00
commit 1e90171ee9
13 changed files with 549 additions and 9 deletions

View file

@ -0,0 +1,42 @@
import Text from "@tensamin/markdown/text";
import { useStorage } from "@tensamin/storage/context";
import { Tooltip, TooltipContent, TooltipTrigger } from "@tensamin/ui";
import { TriangleAlert } from "lucide-react";
import { useState, useMemo, useEffect } from "react";
export default function Media({ link }: { link: string }) {
const { load } = useStorage();
const [trustedDomains, setTrustedDomains] = useState<string[]>([]);
const [hidden, setHidden] = useState(true);
const hostname = useMemo(() => new URL(link).hostname, [link]);
useEffect(() => {
load("chat_trusted_domains").then(setTrustedDomains);
}, [load]);
return trustedDomains.includes(hostname) ? (
<>
{hidden && <Text value={link} />}
<img
src={link}
hidden={hidden}
onLoad={() => setHidden(false)}
className="max-h-70 max-w-70"
/>
</>
) : (
<div className="flex gap-1 items-center">
<Tooltip>
<TooltipTrigger
render={<TriangleAlert className="size-4 stroke-yellow-400" />}
/>
<TooltipContent>
Link embeds can expose your ip address. You can configure trusted
domains in the settings.
</TooltipContent>
</Tooltip>
<Text value={link} />
</div>
);
}

View file

@ -2,6 +2,7 @@ import * as React from "react";
import type { RawMessage } from "../values";
import Text from "@tensamin/markdown/text";
import { AlertTriangle } from "lucide-react";
import { useEffect, useState } from "react";
import {
Avatar,
@ -15,6 +16,7 @@ import {
import Wrapper from "@tensamin/user/wrapper";
import { useChat } from "../context";
import MessageContextMenu from "./messageContextMenu";
import Media from "./media";
function MessageComponent({
grouped,
@ -28,6 +30,18 @@ function MessageComponent({
const { userId } = useChat();
const actuallyFailed = message.failed && message.message_state === "awaiting";
const [isValidURL, setIsValidURL] = useState(false);
useEffect(() => {
try {
if (message.content.split(" ").length > 1) throw new Error();
new URL(message.content);
setIsValidURL(true);
} catch {
setIsValidURL(false);
}
}, [message.content]);
return (
<div
// pt-3 is to get a gap between messages
@ -86,7 +100,11 @@ function MessageComponent({
</p>
</div>
)}
<Text value={message.content} />
{isValidURL ? (
<Media link={message.content} />
) : (
<Text value={message.content} />
)}
</div>
</>
)}