224 lines
5.6 KiB
TypeScript
224 lines
5.6 KiB
TypeScript
import { Match, Switch } from "solid-js";
|
|
|
|
import { Handler } from "mdast-util-to-hast";
|
|
import { cva } from "styled-system/css";
|
|
import { styled } from "styled-system/jsx";
|
|
import { Plugin } from "unified";
|
|
import { visit } from "unist-util-visit";
|
|
|
|
import { UserContextMenu } from "@revolt/app";
|
|
import { useClient } from "@revolt/client";
|
|
import { useSmartParams } from "@revolt/routing";
|
|
import { Avatar, ColouredText, iconSize } from "@revolt/ui";
|
|
|
|
import MdAt from "@material-design-icons/svg/filled/alternate_email.svg?component-solid";
|
|
|
|
import { useUser } from "../users";
|
|
|
|
export function RenderMention(props: {
|
|
mentions?: string;
|
|
disabled?: boolean;
|
|
}) {
|
|
return (
|
|
<Switch fallback={<span>Invalid Mention Element</span>}>
|
|
<Match when={props.mentions?.startsWith("user:")}>
|
|
<UserMention
|
|
userId={props.mentions!.substring(5)}
|
|
disabled={props.disabled}
|
|
/>
|
|
</Match>
|
|
<Match when={props.mentions === "everyone"}>
|
|
<span class={mention()}>
|
|
<MdAt {...iconSize(16)} />
|
|
everyone
|
|
</span>
|
|
</Match>
|
|
<Match when={props.mentions === "online"}>
|
|
<span class={mention()}>
|
|
<MdAt {...iconSize(16)} />
|
|
online
|
|
</span>
|
|
</Match>
|
|
<Match when={props.mentions?.startsWith("role:")}>
|
|
<RoleMention roleId={props.mentions!.substring(5)} />
|
|
</Match>
|
|
</Switch>
|
|
);
|
|
}
|
|
|
|
export function UserMention(props: { userId: string; disabled?: boolean }) {
|
|
const user = useUser(() => props.userId);
|
|
|
|
return (
|
|
<Switch
|
|
fallback={<span class={mention({ valid: false })}>Unknown User</span>}
|
|
>
|
|
<Match when={user().user}>
|
|
<div
|
|
class={mention({ isLink: true })}
|
|
use:floating={
|
|
props.disabled
|
|
? undefined
|
|
: {
|
|
userCard: user().user
|
|
? {
|
|
user: user().user!,
|
|
member: user().member,
|
|
}
|
|
: undefined,
|
|
contextMenu: () => (
|
|
<UserContextMenu
|
|
user={user().user!}
|
|
member={user().member}
|
|
/>
|
|
),
|
|
}
|
|
}
|
|
>
|
|
<Avatar size={16} src={user().avatar} fallback={user().username} />
|
|
<ColouredText colour={user().colour!}>{user().username}</ColouredText>
|
|
</div>
|
|
</Match>
|
|
</Switch>
|
|
);
|
|
}
|
|
|
|
export function RoleMention(props: { roleId: string }) {
|
|
// some jank involved...
|
|
const client = useClient();
|
|
const params = useSmartParams();
|
|
const role = () =>
|
|
client().servers.get(params().serverId!)?.roles.get(props.roleId);
|
|
|
|
return (
|
|
<Switch
|
|
fallback={<span class={mention({ valid: false })}>Unknown Role</span>}
|
|
>
|
|
<Match when={role()}>
|
|
<div class={mention()}>
|
|
<RoleIcon
|
|
style={{
|
|
background:
|
|
role()!.colour ?? "var(--md-sys-color-outline-variant)",
|
|
}}
|
|
/>
|
|
<ColouredText colour={role()!.colour!}>{role()!.name}</ColouredText>
|
|
</div>
|
|
</Match>
|
|
</Switch>
|
|
);
|
|
}
|
|
|
|
const RoleIcon = styled("div", {
|
|
base: {
|
|
margin: "1px",
|
|
width: "14px",
|
|
height: "14px",
|
|
aspectRatio: "1/1",
|
|
borderRadius: "100%",
|
|
},
|
|
});
|
|
|
|
const RE_MENTION =
|
|
/(<@[0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}>|@everyone|@online|<%[0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}>)/;
|
|
|
|
export const remarkMentions: Plugin = () => (tree) => {
|
|
visit(
|
|
tree,
|
|
"text",
|
|
(
|
|
node: { type: "text"; value: string },
|
|
idx,
|
|
parent: { children: unknown[] },
|
|
) => {
|
|
const elements = node.value.split(RE_MENTION);
|
|
if (elements.length === 1) return; // no matches
|
|
|
|
const newNodes = elements.map((value, index) =>
|
|
index % 2
|
|
? value.startsWith("<@")
|
|
? {
|
|
type: "mention",
|
|
mentions: "user:" + value.substring(2, value.length - 1),
|
|
}
|
|
: value === "@everyone"
|
|
? {
|
|
type: "mention",
|
|
mentions: "everyone",
|
|
}
|
|
: value === "@online"
|
|
? {
|
|
type: "mention",
|
|
mentions: "online",
|
|
}
|
|
: {
|
|
type: "mention",
|
|
mentions: "role:" + value.substring(2, value.length - 1),
|
|
}
|
|
: {
|
|
type: "text",
|
|
value,
|
|
},
|
|
);
|
|
|
|
parent.children.splice(idx, 1, ...newNodes);
|
|
return idx + newNodes.length;
|
|
},
|
|
);
|
|
};
|
|
|
|
export const mentionHandler: Handler = (h, node) => {
|
|
return {
|
|
type: "element" as const,
|
|
tagName: "mention",
|
|
children: [],
|
|
properties: {
|
|
mentions: node.mentions,
|
|
},
|
|
};
|
|
};
|
|
|
|
export const mention = cva({
|
|
base: {
|
|
verticalAlign: "bottom",
|
|
|
|
gap: "4px",
|
|
paddingLeft: "2px",
|
|
paddingRight: "6px",
|
|
alignItems: "center",
|
|
display: "inline-flex",
|
|
|
|
fontWeight: 600,
|
|
borderRadius: "var(--borderRadius-lg)",
|
|
|
|
color: "var(--md-sys-color-on-primary-container)",
|
|
background: "var(--md-sys-color-primary-container)",
|
|
},
|
|
variants: {
|
|
isLink: {
|
|
true: {
|
|
cursor: "pointer",
|
|
},
|
|
},
|
|
valid: {
|
|
false: {
|
|
paddingLeft: "6px",
|
|
cursor: "not-allowed",
|
|
},
|
|
},
|
|
inEditor: {
|
|
true: {
|
|
color: "var(--md-sys-color-on-primary)",
|
|
background: "var(--md-sys-color-primary)",
|
|
|
|
"& img": {
|
|
width: "16px",
|
|
height: "16px",
|
|
objectFit: "cover",
|
|
borderRadius: "var(--borderRadius-full)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|