Skip to content
Open
105 changes: 43 additions & 62 deletions packages/client/components/app/interface/channels/text/Message.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -135,7 +115,11 @@ export function Message(props: Props) {
/>
</div>
}
contextMenu={() => <MessageContextMenu message={props.message} />}
contextMenu={
props.editing
? undefined
: () => <MessageContextMenu message={props.message} />
}
timestamp={props.message.createdAt}
edited={props.message.editedAt}
mentioned={props.message.mentioned}
Expand All @@ -144,31 +128,29 @@ export function Message(props: Props) {
isLink={props.isLink}
tail={props.tail || state.settings.getValue("appearance:compact_mode")}
header={
<Show when={props.message.replyIds}>
<For each={props.message.replyIds}>
{(reply_id) => {
/**
* Signal the actual message
*/
const message = () => client().messages.get(reply_id);
<For each={props.message.replyIds}>
{(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 (
<MessageReply
mention={props.message.mentionIds?.includes(
message()!.authorId!,
)}
message={message()}
/>
);
}}
</For>
</Show>
return (
<MessageReply
mention={props.message.mentionIds?.includes(
message()!.authorId!,
)}
message={message()}
/>
);
}}
</For>
}
info={
<Switch fallback={<div />}>
Expand Down Expand Up @@ -272,28 +254,27 @@ export function Message(props: Props) {
isServer={!!props.message.server}
/>
</Show>
<Switch>
<Switch
fallback={
<For each={props.message.embeds}>{(em) => <Embed embed={em} />}</For>
}
>
<Match when={props.editing}>
<EditMessage message={props.message} />
</Match>
<Match when={props.message.content && !isOnlyGIF()}>
<BreakText>
<Markdown content={props.message.content!} />
</BreakText>
<Match when={props.message.content}>
<Markdown
message={props.message}
content={props.message.content}
container={(md) => <BreakText class="message_body">{md}</BreakText>}
/>
</Match>
</Switch>
<Show when={props.message.attachments}>
<For each={props.message.attachments}>
{(attachment) => (
<Attachment message={props.message} file={attachment} />
)}
</For>
</Show>
<Show when={props.message.embeds}>
<For each={props.message.embeds}>
{(embed) => <Embed embed={embed} />}
</For>
</Show>
<For each={props.message.attachments}>
{(attachment) => (
<Attachment message={props.message} file={attachment} />
)}
</For>
<Reactions
reactions={props.message.reactions as never as Map<string, Set<string>>}
interactions={props.message.interactions}
Expand Down
25 changes: 17 additions & 8 deletions packages/client/components/markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -283,7 +290,6 @@ export function renderSimpleMarkdown(content: string) {
components: replyComponents(),
},
schema: html,
listDepth: 0,
},
hastNode,
);
Expand All @@ -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);

Expand All @@ -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 },
),
);
Expand Down
Loading
Loading