mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-25 11:42:30 +01:00
mentions
This commit is contained in:
parent
8872de4ad0
commit
711e79cdfd
@ -2,8 +2,10 @@ import * as Icons from "@mdi/js";
|
||||
import { Icon as MdiIcon } from "@mdi/react";
|
||||
import { IconProps as IconBaseProps } from "@mdi/react/dist/IconProps";
|
||||
|
||||
export type IconType = keyof typeof Icons;
|
||||
|
||||
export interface IconProps extends Omit<IconBaseProps, "path"> {
|
||||
icon: keyof typeof Icons;
|
||||
icon: IconType;
|
||||
}
|
||||
|
||||
function Icon(props: IconProps) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Container = styled.div`
|
||||
const Container = styled.span`
|
||||
background-color: hsl(var(--background-tertiary-hsl));
|
||||
padding: 0 5px;
|
||||
border-radius: 4px;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { FormattingPatterns } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import Marked, { ReactRenderer } from "marked-react";
|
||||
import React from "react";
|
||||
import reactStringReplace from "react-string-replace";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { materialDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import styled from "styled-components";
|
||||
@ -8,6 +9,7 @@ import CodeBlock from "../Codeblock";
|
||||
import Link from "../Link";
|
||||
import Spoiler from "../Spoiler";
|
||||
import { MarkdownProps } from "./Markdown";
|
||||
import Mention from "./Mention";
|
||||
import Timestamp from "./Timestamp";
|
||||
|
||||
const Container = styled.div`
|
||||
@ -125,21 +127,39 @@ const customRenderer: Partial<ReactRenderer> = {
|
||||
codespan: (content: string) => <code className="inline">{content}</code>,
|
||||
link: (href, text) => <Link href={href}>{text}</Link>,
|
||||
text: (text: string) => {
|
||||
const spoilerRe = /\|\|([\s\S]+?)\|\|/g;
|
||||
let replaced: string | React.ReactNode[] = text;
|
||||
const SpoilerRegex = /\|\|(?<content>[\s\S]+?)\|\|/;
|
||||
|
||||
if (spoilerRe.test(text)) {
|
||||
// get content inside spoiler tags
|
||||
const spoilerContent = text.match(spoilerRe)![0].slice(2, -2);
|
||||
return <Spoiler children={spoilerContent} />;
|
||||
}
|
||||
// replace the spoiler with the spoiler component
|
||||
replaced = reactStringReplace(text, SpoilerRegex, (match, i) => {
|
||||
return <Spoiler key={i} children={match} />;
|
||||
});
|
||||
|
||||
const match = text.match(FormattingPatterns.Timestamp);
|
||||
if (match) {
|
||||
const { timestamp, style } = match.groups || {};
|
||||
return <Timestamp timestamp={timestamp} style={style} />;
|
||||
}
|
||||
replaced = reactStringReplace(replaced, FormattingPatterns.DefaultStyledTimestamp, (match, i) => (
|
||||
<Timestamp key={i} timestamp={match} />
|
||||
));
|
||||
|
||||
return text;
|
||||
replaced = reactStringReplace(replaced, /<t:-?(\d{1,13}:[tTdDfFR])>/, (match, i) => {
|
||||
const parts = match.split(":");
|
||||
const timestamp = parts[0];
|
||||
const style = parts[1];
|
||||
|
||||
return <Timestamp key={i} timestamp={timestamp} style={style} />;
|
||||
});
|
||||
|
||||
replaced = reactStringReplace(replaced, FormattingPatterns.Channel, (match, i) => (
|
||||
<Mention key={i} type="channel" id={match} />
|
||||
));
|
||||
|
||||
replaced = reactStringReplace(replaced, FormattingPatterns.User, (match, i) => (
|
||||
<Mention key={i} type="user" id={match} />
|
||||
));
|
||||
|
||||
replaced = reactStringReplace(replaced, FormattingPatterns.Role, (match, i) => (
|
||||
<Mention key={i} type="role" id={match} />
|
||||
));
|
||||
|
||||
return replaced;
|
||||
},
|
||||
};
|
||||
|
||||
|
71
src/components/markdown/Mention.tsx
Normal file
71
src/components/markdown/Mention.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import React, { memo } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import User from "../../stores/objects/User";
|
||||
|
||||
const Container = styled.span`
|
||||
padding: 0 2px;
|
||||
border-radius: 4px;
|
||||
background-color: hsl(var(--primary-hsl) / 0.3);
|
||||
|
||||
&:hover {
|
||||
background-color: hsl(var(--primary-hsl) / 0.5);
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
interface MentionProps {
|
||||
id: string;
|
||||
}
|
||||
function UserMention({ id }: MentionProps) {
|
||||
const app = useAppStore();
|
||||
const [user, setUser] = React.useState<User | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const user = app.users.get(id);
|
||||
if (user) setUser(user);
|
||||
}, [id]);
|
||||
|
||||
if (!user)
|
||||
return (
|
||||
<Container>
|
||||
<span>@{id}</span>
|
||||
</Container>
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<span>@{user.username}</span>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function ChannelMention({ id }: MentionProps) {
|
||||
return (
|
||||
<Container>
|
||||
<span>#{id}</span>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function RoleMention({ id }: MentionProps) {
|
||||
return (
|
||||
<Container>
|
||||
<span>@{id}</span>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
type: "role" | "user" | "channel";
|
||||
id: string;
|
||||
}
|
||||
|
||||
function Mention({ type, id }: Props) {
|
||||
if (type === "role") return <RoleMention id={id} />;
|
||||
if (type === "user") return <UserMention id={id} />;
|
||||
if (type === "channel") return <ChannelMention id={id} />;
|
||||
return null;
|
||||
}
|
||||
|
||||
export default memo(Mention);
|
@ -12,7 +12,7 @@ const Container = styled.div`
|
||||
|
||||
interface Props {
|
||||
timestamp: string;
|
||||
style: string;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
function Timestamp({ timestamp, style }: Props) {
|
||||
|
Loading…
Reference in New Issue
Block a user