diff --git a/packages/client/components/app/interface/channels/text/Message.tsx b/packages/client/components/app/interface/channels/text/Message.tsx index 67997ed34..ff89730b7 100644 --- a/packages/client/components/app/interface/channels/text/Message.tsx +++ b/packages/client/components/app/interface/channels/text/Message.tsx @@ -1,7 +1,7 @@ import { For, Match, Show, Switch, createSignal, onMount } from "solid-js"; import { useLingui } from "@lingui-solid/solid/macro"; -import { Message as MessageInterface, WebsiteEmbed } from "stoat.js"; +import { Message as MessageInterface } from "stoat.js"; import { cva } from "styled-system/css"; import { styled } from "styled-system/jsx"; import { decodeTime } from "ulid"; @@ -32,12 +32,6 @@ import { import { EditMessage } from "./EditMessage"; -/** - * Regex for matching URLs - */ -const RE_URL = - /[(http(s)?)://(www.)?a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/; - interface Props { /** * Message @@ -76,20 +70,6 @@ export function Message(props: Props) { const [isHovering, setIsHovering] = createSignal(false); - /** - * Determine whether this message only contains a GIF - */ - const isOnlyGIF = () => - props.message.embeds && - props.message.embeds.length === 1 && - props.message.embeds[0].type === "Website" && - ((props.message.embeds[0] as WebsiteEmbed).specialContent?.type === "GIF" || - (props.message.embeds[0] as WebsiteEmbed).originalUrl?.startsWith( - "https://tenor.com", - )) && - props.message.content && - !props.message.content.replace(RE_URL, "").length; - /** * React with an emoji * @param emoji Emoji @@ -135,7 +115,11 @@ export function Message(props: Props) { /> } - contextMenu={() => } + contextMenu={ + props.editing + ? undefined + : () => + } timestamp={props.message.createdAt} edited={props.message.editedAt} mentioned={props.message.mentioned} @@ -144,31 +128,29 @@ export function Message(props: Props) { isLink={props.isLink} tail={props.tail || state.settings.getValue("appearance:compact_mode")} header={ - - - {(reply_id) => { - /** - * Signal the actual message - */ - const message = () => client().messages.get(reply_id); + + {(reply_id) => { + /** + * Signal the actual message + */ + const message = () => client().messages.get(reply_id); - onMount(() => { - if (!message()) { - props.message.channel!.fetchMessage(reply_id); - } - }); + onMount(() => { + if (!message()) { + props.message.channel!.fetchMessage(reply_id); + } + }); - return ( - - ); - }} - - + return ( + + ); + }} + } info={ }> @@ -272,28 +254,27 @@ export function Message(props: Props) { isServer={!!props.message.server} /> - + {(em) => } + } + > - - - - + + {md}} + /> - - - {(attachment) => ( - - )} - - - - - {(embed) => } - - + + {(attachment) => ( + + )} + >} interactions={props.message.interactions} diff --git a/packages/client/components/markdown/index.tsx b/packages/client/components/markdown/index.tsx index e7f7d6d83..331c34c3a 100644 --- a/packages/client/components/markdown/index.tsx +++ b/packages/client/components/markdown/index.tsx @@ -13,6 +13,7 @@ import remarkRehype from "remark-rehype"; import { unified } from "unified"; import { VFile } from "vfile"; +import { Message } from "stoat.js"; import * as elements from "./elements"; import { injectEmojiSize } from "./emoji/util"; import { RenderCodeblock } from "./plugins/Codeblock"; @@ -46,7 +47,7 @@ import { unicodeEmojiHandler, } from "./plugins/unicodeEmoji"; import { remarkInsertBreaks, sanitise } from "./sanitise"; -import { childrenToSolid } from "./solid-markdown/ast-to-solid"; +import { Container, childrenToSolid } from "./solid-markdown/ast-to-solid"; import { defaults } from "./solid-markdown/defaults"; /** @@ -255,6 +256,12 @@ export interface MarkdownProps { * Content to render */ content?: string; + message?: Message; + + /** + * Container to wrap render in + */ + container?: Container; /** * Whether to prevent big emoji from rendering @@ -283,7 +290,6 @@ export function renderSimpleMarkdown(content: string) { components: replyComponents(), }, schema: html, - listDepth: 0, }, hastNode, ); @@ -297,7 +303,7 @@ export function Markdown(props: MarkdownProps) { * Render some given Markdown content * @param content content */ - function render(content = "") { + function render(content = "", message?: Message, container?: Container) { const file = new VFile(); file.value = sanitise(content); @@ -315,23 +321,26 @@ export function Markdown(props: MarkdownProps) { ...defaults, // @ts-expect-error it doesn't like the td component components: components(), + container, + message, }, schema: html, - listDepth: 0, }, hastNode, ); } // Render once immediately - // eslint-disable-next-line solid/reactivity - const [children, setChildren] = createSignal(render(props.content)); + const [children, setChildren] = createSignal( + // eslint-disable-next-line solid/reactivity + render(props.content, props.message, props.container), + ); // If it ever updates, re-render the whole tree: createEffect( on( - () => props.content, - (content) => setChildren(render(content)), + [() => props.content, () => props.message?.embeds], + (data) => setChildren(render(data[0], props.message, props.container)), { defer: true }, ), ); diff --git a/packages/client/components/markdown/plugins/anchors.tsx b/packages/client/components/markdown/plugins/anchors.tsx index 10af20f37..7ffba5535 100644 --- a/packages/client/components/markdown/plugins/anchors.tsx +++ b/packages/client/components/markdown/plugins/anchors.tsx @@ -1,21 +1,21 @@ import { JSX, Match, Show, Switch, splitProps } from "solid-js"; import { Trans } from "@lingui-solid/solid/macro"; +import { MessageEmbed } from "stoat.js"; import { cva } from "styled-system/css"; import { useClient } from "@revolt/client"; import { useModals } from "@revolt/modal"; +import { trimURL } from "@revolt/modal/modals/LinkWarning"; import { paramsFromPathname } from "@revolt/routing"; import { useState } from "@revolt/state"; -import { Avatar, iconSize } from "@revolt/ui"; +import { Avatar, Embed, iconSize } from "@revolt/ui"; import { Invite } from "@revolt/ui/components/features/messaging/elements/Invite"; import { Symbol } from "@revolt/ui/components/utils/Symbol"; import MdChat from "@material-design-icons/svg/outlined/chat.svg?component-solid"; import MdChevronRight from "@material-design-icons/svg/outlined/chevron_right.svg?component-solid"; import MdPeople from "@material-design-icons/svg/outlined/people.svg?component-solid"; -// import { determineLink } from "../../../lib/links"; -// import { modalController } from "../../../controllers/modals/ModalController"; const link = cva({ base: { @@ -58,23 +58,32 @@ function inAppScope(link: URL): boolean { } export function RenderAnchor( - props: { disabled?: boolean } & JSX.AnchorHTMLAttributes, + props: { + disabled?: boolean; + embeds?: MessageEmbed[]; + node?: Element; + } & JSX.AnchorHTMLAttributes, ) { /* eslint-disable solid/reactivity */ /* eslint-disable solid/components-return-once */ - const [localProps, remoteProps] = splitProps(props, [ - "href", - "target", - "disabled", - ]); + const text = Array.isArray(props.children) && props.children[0]?.toString(), + plainLink = text === props.href && !props.disabled; - // Handle case where there is no link - if (!localProps.href) return {remoteProps.children}; + // Handle empty link + if ( + !props.href || + (props.node && + !text && + // Nested anchor, continue down the stack + [...props.node.children].find((el) => el.tagName === "a")) + ) + return props.children; // Handle links that navigate internally try { - let url = new URL(localProps.href); + let url = new URL(props.href), + internal = false; // Remap discover links to native links if (url.origin === "https://rvlt.gg" || url.origin === "https://stt.gg") { @@ -92,7 +101,6 @@ export function RenderAnchor( if (params.exactChannel) { const channel = () => client().channels.get(params.channelId!); - const internalUrl = () => new URL( (channel()!.serverId @@ -116,7 +124,7 @@ export function RenderAnchor( tag @@ -148,7 +156,7 @@ export function RenderAnchor( {server()?.name} @@ -156,30 +164,26 @@ export function RenderAnchor( ); - } else if ( - params.inviteId && - // only display invites if it is just the plain link: - Array.isArray(remoteProps.children) && - remoteProps.children[0] === localProps.href && - !localProps.disabled - ) { + } else if (params.inviteId && plainLink) { return ; - } else { - const internalUrl = () => - new URL(url.pathname, location.origin).toString(); + } + internal = true; + } - return ( - - ); + //Inline link embed + if (plainLink && props.embeds) { + const href = trimURL(url, true); + for (let i = 0, l = props.embeds.length, em; i < l; ++i) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + em = props.embeds[i] as any; + if (trimURL(em.originalUrl || em.url, true) === href) { + props.embeds.splice(i, 1); + return ; + } } } - // ... all other links: + // All other links const state = useState(); const { openModal } = useModals(); @@ -193,29 +197,33 @@ export function RenderAnchor( openModal({ type: "link_warning", url, - display: event.currentTarget!.innerText, + display: text || url.href, }); } } + //Override internal URL + if (internal) + url = new URL(url.pathname + url.search + url.hash, location.origin); + return ( } > @@ -225,7 +233,7 @@ export function RenderAnchor( // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_) { // invalid URL - return {props.children}; + return props.children; } } diff --git a/packages/client/components/markdown/solid-markdown/ast-to-solid.tsx b/packages/client/components/markdown/solid-markdown/ast-to-solid.tsx index 0b0efe111..9767758c2 100644 --- a/packages/client/components/markdown/solid-markdown/ast-to-solid.tsx +++ b/packages/client/components/markdown/solid-markdown/ast-to-solid.tsx @@ -1,5 +1,4 @@ -/* eslint-disable */ -// @ts-nocheck +/* eslint-disable @typescript-eslint/no-explicit-any */ /** * This file is provided under the MIT License * Copyright (c) 2015 Espen Hovlandsdal @@ -11,7 +10,7 @@ import { Dynamic } from "solid-js/web"; import { stringify as commas } from "comma-separated-tokens"; import type { Comment, - DocType, + Doctype, Element, ElementContent, Root, @@ -22,6 +21,8 @@ import { find, hastToReact, svg } from "property-information"; import { stringify as spaces } from "space-separated-tokens"; import style from "style-to-object"; +import { Embed } from "@revolt/ui"; +import { Message, MessageEmbed } from "stoat.js"; import type { NormalComponents, SolidMarkdownProps } from "./complex-types"; export type Position = { @@ -36,7 +37,8 @@ type Raw = { type Context = { options: Options; schema: Schema; - listDepth: number; + _listDepth?: number; + _embeds?: MessageEmbed[]; }; type TransformLink = ( href: string, @@ -108,6 +110,8 @@ type SpecialComponents = { type Components = Partial> & Partial; +export type Container = (md: JSX.Element[]) => JSX.Element; + export type Options = { sourcePos: boolean; rawSourcePos: boolean; @@ -117,6 +121,8 @@ export type Options = { transformImageUri?: TransformImage; linkTarget: TransformLinkTargetType | TransformLinkTarget; components: Components; + container?: Container; + message?: Message; }; const own = {}.hasOwnProperty; @@ -129,15 +135,20 @@ export function childrenToSolid( context: Context, node: Element | Root, ): JSX.Element[] { - const children: JSX.Element[] = []; - let childIndex = -1; + const isRoot = node.type === "root"; + let children: JSX.Element[] = [], + childIndex = -1; + + if (isRoot) context._listDepth = 0; - // let child: Comment | DocType | Element | Raw | Text; + //Copy embeds to track use + const msgRoot = isRoot ? context.options.message : undefined; + if (msgRoot?.embeds) context._embeds = [...msgRoot.embeds]; while (++childIndex < node.children.length) { const child = node.children[childIndex] as | Comment - | DocType + | Doctype | Element | Raw | Text; @@ -160,6 +171,14 @@ export function childrenToSolid( } } + //Apply container + if (isRoot && context.options.container) + children = [context.options.container(children)]; + + //Append unused embeds + if (msgRoot?.embeds) + for (const em of context._embeds!) children.push(); + return children; } @@ -190,22 +209,18 @@ function toSolid( } } - if (name === "ol" || name === "ul") { - context.listDepth++; - } + if (name === "ol" || name === "ul") context._listDepth!++; const children = childrenToSolid(context, node); - if (name === "ol" || name === "ul") { - context.listDepth--; - } + if (name === "ol" || name === "ul") context._listDepth!--; // Restore parent schema. context.schema = parentSchema; // Nodes created by plugins do not have positional info, in which case we use // an object that matches the position interface. - const position = node.position || { + const position = (node.position as Position) || { start: { line: null, column: null, offset: null }, end: { line: null, column: null, offset: null }, }; @@ -287,7 +302,7 @@ function toSolid( if (!basic && (name === "ol" || name === "ul")) { properties.ordered = name === "ol"; - properties.depth = context.listDepth; + properties.depth = context._listDepth; } if (name === "td" || name === "th") { @@ -324,6 +339,7 @@ function toSolid( if (!basic) { properties.node = node; + properties.embeds = context._embeds; } return ( diff --git a/packages/client/components/modal/modals/LinkWarning.tsx b/packages/client/components/modal/modals/LinkWarning.tsx index c3161ae68..0e4a8ce37 100644 --- a/packages/client/components/modal/modals/LinkWarning.tsx +++ b/packages/client/components/modal/modals/LinkWarning.tsx @@ -8,33 +8,56 @@ import { Checkbox, Column, Dialog, DialogProps, Text } from "@revolt/ui"; import { Modals } from "../types"; -/** - * Modal to warn the user about a potentially unsafe link - */ +const URL_TRIM = /\/+$/; + +/** Trim trailing slash from URL +@param noHash Remove #hash before trimming +*/ +export function trimURL(url: string | URL, noHash?: boolean) { + if (!(url instanceof URL)) { + try { + url = new URL(url); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_) { + return url as string; + } + } + + let base = url.protocol + "//"; + if (url.username || url.password) + base += `${url.username}${url.password ? ":" + url.password : ""}@`; + + return ( + base + + url.host + + url.pathname.replace(URL_TRIM, "") + + url.search + + (noHash ? "" : url.hash) + ); +} + +/** Modal to warn the user about a potentially unsafe link */ export function LinkWarningModal( props: DialogProps & Modals & { type: "link_warning" }, ) { const state = useState(); const [value, setValue] = createSignal(false); + // eslint-disable-next-line solid/reactivity + const urlStr = trimURL(props.url), + // eslint-disable-next-line solid/reactivity + dispStr = trimURL(props.display); + const scrutiny = createMemo(() => { - const destUrlString = props.url.toString(); - if (destUrlString !== props.display) { - try { - const displayUrl = new URL(props.display); - if (destUrlString !== displayUrl.toString()) { - return 2; - } else { - return 1; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (_) { - // URL parsing failed; the link is likely not intentionally misleading. - return 1; - } - } + if (dispStr === urlStr) return 0; - return 0; + try { + return dispStr === trimURL(new URL(dispStr)) ? 1 : 2; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_) { + // URL parsing failed; the link is likely not intentionally misleading. + return 1; + } }); return ( @@ -47,11 +70,8 @@ export function LinkWarningModal( { text: Continue, onClick: () => { - window.open(props.url, "_blank", "noopener"); - - if (value() && scrutiny() === 0) { - state.linkSafety.trust(props.url); - } + open(urlStr, "_blank", "noopener"); + if (value() && scrutiny() === 0) state.linkSafety.trust(props.url); }, isDisabled: scrutiny() === 2 && !value(), }, @@ -60,7 +80,7 @@ export function LinkWarningModal( Are you sure you want to go to - {props.url.toString()}? + {urlStr}? - You clicked on "{props.display}" + You clicked on "{dispStr}" @@ -84,7 +104,7 @@ export function LinkWarningModal( This is not the same as the link that was displayed: - {props.display} + {dispStr} setValue((v) => !v)}> I understand the consequences diff --git a/packages/client/components/ui/components/features/messaging/elements/Container.tsx b/packages/client/components/ui/components/features/messaging/elements/Container.tsx index 46f1764d4..dd9e57f47 100644 --- a/packages/client/components/ui/components/features/messaging/elements/Container.tsx +++ b/packages/client/components/ui/components/features/messaging/elements/Container.tsx @@ -199,7 +199,7 @@ const Info = styled("div", { display: "flex", flexShrink: 0, justifyContent: "end", - padding: "2px var(--gap-sm)", + padding: "2px 5px", }, variants: { tail: { @@ -313,7 +313,7 @@ export function MessageContainer(props: Props) { onMouseEnter={() => props.onHover && props.onHover(true)} onMouseLeave={() => props.onHover && props.onHover(false)} class={ - "group " + + "message group " + base({ tail: props.tail, mentioned: props.mentioned, diff --git a/packages/client/components/ui/components/features/messaging/elements/Embed.tsx b/packages/client/components/ui/components/features/messaging/elements/Embed.tsx index 14318fac8..6aa9ae3d4 100644 --- a/packages/client/components/ui/components/features/messaging/elements/Embed.tsx +++ b/packages/client/components/ui/components/features/messaging/elements/Embed.tsx @@ -17,7 +17,7 @@ import { TextEmbed } from "./TextEmbed"; /** * Render a given embed */ -export function Embed(props: { embed: MessageEmbed }) { +export function Embed(props: { embed: MessageEmbed; link?: URL }) { const { openModal } = useModals(); /** @@ -49,7 +49,11 @@ export function Embed(props: { embed: MessageEmbed }) { return ( - + - + diff --git a/packages/client/components/ui/components/features/messaging/elements/FileInfo.tsx b/packages/client/components/ui/components/features/messaging/elements/FileInfo.tsx index 0243ceb5e..cdbaefad1 100644 --- a/packages/client/components/ui/components/features/messaging/elements/FileInfo.tsx +++ b/packages/client/components/ui/components/features/messaging/elements/FileInfo.tsx @@ -15,11 +15,15 @@ import { Column, Row } from "@revolt/ui/components/layout"; import { humanFileSize } from "@revolt/ui/components/utils"; import { Symbol } from "@revolt/ui/components/utils/Symbol"; -/** - * Base container - */ -const Base = styled(Row, { - base: {}, +const InfoColumn = styled(Column, { + base: { + overflow: "hidden", + + "& > *": { + overflow: "hidden", + textOverflow: "ellipsis", + }, + }, }); interface Props { @@ -38,8 +42,15 @@ interface Props { * Information about a given attachment or embed */ export function FileInfo(props: Props) { + function download(url: string, name?: string) { + const link = document.createElement("a"); + link.href = url; + if (name) link.download = name; + link.click(); + } + return ( - + }> - + {props.file?.filename} {humanFileSize(props.file!.size!)} - + - + download(props.file!.originalUrl, props.file?.filename) + } > - - download - - + download + - + ); } diff --git a/packages/client/components/ui/components/features/messaging/elements/MessageReply.tsx b/packages/client/components/ui/components/features/messaging/elements/MessageReply.tsx index 8913fe5e5..00fc7731b 100644 --- a/packages/client/components/ui/components/features/messaging/elements/MessageReply.tsx +++ b/packages/client/components/ui/components/features/messaging/elements/MessageReply.tsx @@ -90,28 +90,48 @@ const Attachments = styled("em", { }, }); -/** - * Link styling - */ const Link = styled("a", { base: { minWidth: 0, display: "flex", - alignItems: "center", gap: "var(--gap-md)", + marginInlineEnd: "var(--gap-lg)", + }, +}); + +const ReplyContent = styled("div", { + base: { + overflow: "hidden", + alignItems: "center", + whiteSpace: "nowrap", + textOverflow: "ellipsis", }, }); +const ReplyMax = 128, + URLClip = ReplyMax + 8, + R_Trim = /\s+/g, + R_LastURL = /(?:^|\s)https?:\/\/[\w_.~!*''();:@&=+$,/?#[%-]+([\s\S]{0,7})$/, + R_URLEnd = /^[\w_.~!*''();:@&=+$,/?#[%-]+/; + /** * Message being replied to */ export function MessageReply(props: Props) { const renderReplyContent = (content: string) => { - if (content.length > 128) { - content = content.slice(0, 128) + "..."; + if (content.length > ReplyMax) { + const cut = content.slice(0, URLClip); + let match, len; + + //Clip without interrupting links + content = + (match = cut.match(R_LastURL)) && //Has URL at end + (match[1] || (len = content.slice(URLClip).match(R_URLEnd)?.[0].length)) //Remaining length + ? content.slice(0, URLClip + (len || -match[1].length)) + " ..." //Ensure full link + : cut.slice(0, ReplyMax) + "..."; //No link } - return renderSimpleMarkdown(content.replace(/\n/g, " ")); + return renderSimpleMarkdown(content.replace(R_Trim, " ")); }; return ( @@ -155,13 +175,3 @@ export function MessageReply(props: Props) { ); } - -const ReplyContent = styled("div", { - base: { - display: "flex", - overflow: "hidden", - alignItems: "center", - whiteSpace: "nowrap", - textOverflow: "ellipsis", - }, -}); diff --git a/packages/client/components/ui/components/features/messaging/elements/TextEmbed.tsx b/packages/client/components/ui/components/features/messaging/elements/TextEmbed.tsx index 902ad5708..c24b40752 100644 --- a/packages/client/components/ui/components/features/messaging/elements/TextEmbed.tsx +++ b/packages/client/components/ui/components/features/messaging/elements/TextEmbed.tsx @@ -81,11 +81,26 @@ const Description = styled("div", { /** * Text Embed */ -export function TextEmbed(props: { embed: TextEmbedClass | WebsiteEmbed }) { +export function TextEmbed(props: { + embed: TextEmbedClass | WebsiteEmbed; + link?: URL; +}) { const { openModal } = useModals(); + function nameFromURL() { + let name; + try { + name = new URL(props.embed.url!).hostname; + // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-empty + } catch (_) {} + return name || "Website"; + } + return ( - + - - + + - <OverflowingText>{props.embed.title}</OverflowingText> + <OverflowingText> + {props.embed.title ?? nameFromURL()} + </OverflowingText> diff --git a/packages/client/components/ui/components/utils/SizedContent.tsx b/packages/client/components/ui/components/utils/SizedContent.tsx index 979a9569f..d093c2125 100644 --- a/packages/client/components/ui/components/utils/SizedContent.tsx +++ b/packages/client/components/ui/components/utils/SizedContent.tsx @@ -17,71 +17,37 @@ interface Props { * The content itself */ children: JSX.Element; + + class?: string; } -const MIN_W = 160; -const MIN_H = 120; -const MAX_S = 420; +const MIN_W = 160, + MIN_H = 120, + MAX_S = 420; /** * Automatic message content sizing for images, videos and embeds */ export function SizedContent(props: Props) { - // const tall = () => props.width > props.height; - - // const desiredWidth = "max(var(--width), var(--layout-attachments-min-width))"; - // const desiredHeight = - // "max(var(--height), var(--layout-attachments-min-height))"; - - // const constrainedWidth = `calc(min(${desiredWidth}, (var(--width)/var(--height)) * min(${desiredHeight}, var(--layout-attachments-max-height))))`; - // const constrainedHeight = `calc(min(${desiredHeight}, (var(--height)/var(--width)) * min(${desiredWidth}, var(--layout-attachments-max-width))))`; - - const size = createMemo(() => { - let width = props.width, + const style = createMemo(() => { + const width = props.width, height = props.height; - // console.info("1", width, height); - - // ensure min. size - if (width < MIN_W) { - height *= MIN_W / width; - width = MIN_W; - } - // console.info("2", width, height); - - if (height < MIN_H) { - width *= MIN_H / height; - height = MIN_H; - } - // console.info("3", width, height); - - // scale down to required size - if (width > MAX_S) { - height /= width / MAX_S; - width = MAX_S; - } - // console.info("4", width, height); - - if (height > MAX_S) { - width /= height / MAX_S; - height = MAX_S; - } - // console.info("5", width, height); + const setW = + height > width + ? Math.floor( + (width * Math.min(Math.max(height, MIN_H), MAX_S)) / height, + ) //Set from height + : Math.min(Math.max(width, MIN_W), MAX_S); //Set from width return { - width, - height, + width: `min(${setW}px, 100%)`, + "aspect-ratio": `${width}/${height}`, }; }); return ( - + {props.children} ); @@ -90,34 +56,14 @@ export function SizedContent(props: Props) { const Container = styled("div", { base: { display: "grid", - height: "auto", - maxWidth: "100%", - - // // min. render size or chat width, whichever is smaller - // // minWidth: "calc(min(100%, var(--layout-attachments-min-width)))", - // minWidth: "var(--layout-attachments-min-width)", - // // max. render size or chat width, whichever is smaller - // // maxWidth: "calc(min(100%, var(--layout-attachments-max-width)))", - // maxWidth: "var(--layout-attachments-max-width)", - // // min. render size, whichever is smaller - // minHeight: "var(--layout-attachments-min-height)", - // // max. render size - // maxHeight: "calc(min(70vh, var(--layout-attachments-max-height)))", - - // width: "auto", - // height: "var(--height)", - // aspectRatio: "var(--width) / var(--height)", - overflow: "hidden", borderRadius: "var(--borderRadius-md)", - gridTemplateColumns: "1fr", gridTemplateRows: "1fr", // scale to size of container "& > *": { - // gridArea: "1/1", // top-left corner to bottom-right corner gridArea: "1 / 1 / 2 / 2", width: "100%", @@ -125,23 +71,7 @@ const Container = styled("div", { // special case for images minHeight: 0, - - // testing: objectFit: "contain", }, - // "& .Spoiler": { - // width: "100%", - // height: "100%", - // }, }, - // variants: { - // tall: { - // true: { - // // // special case for image sizing: - // // "& img": { - // // width: "unset", - // // }, - // }, - // }, - // }, }); diff --git a/packages/client/components/ui/styles.css b/packages/client/components/ui/styles.css index 5cdb943c4..a5958f352 100644 --- a/packages/client/components/ui/styles.css +++ b/packages/client/components/ui/styles.css @@ -25,6 +25,10 @@ body { background: #191919; } +/* Consistent gap between embeds */ +.message_body .embed:not(:last-child) { margin-bottom: var(--gap-md) } +.message_body .embed + br { display: none } + /* HighlightJs */ /*! Theme: GitHub Dark