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 (
-
+
-
-
+
+
- {props.embed.title}
+
+ {props.embed.title ?? nameFromURL()}
+
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