mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-26 00:50:56 -05:00
Change invite rendering logic.
Handle link warnings on embeds. Remove "EDIT!!" 🙏🙏🙏
This commit is contained in:
parent
571b30243c
commit
2ccc0b7b5e
7 changed files with 219 additions and 179 deletions
|
@ -24,15 +24,7 @@ import MessageBase, {
|
||||||
import Attachment from "./attachments/Attachment";
|
import Attachment from "./attachments/Attachment";
|
||||||
import { MessageReply } from "./attachments/MessageReply";
|
import { MessageReply } from "./attachments/MessageReply";
|
||||||
import Embed from "./embed/Embed";
|
import Embed from "./embed/Embed";
|
||||||
import EmbedInvite from "./embed/EmbedInvite";
|
import InviteList from "./embed/EmbedInvite";
|
||||||
|
|
||||||
const INVITE_PATHS = [
|
|
||||||
location.hostname + "/invite",
|
|
||||||
"app.revolt.chat/invite",
|
|
||||||
"nightly.revolt.chat/invite",
|
|
||||||
"local.revolt.chat/invite",
|
|
||||||
"rvlt.gg"
|
|
||||||
]
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
attachContext?: boolean;
|
attachContext?: boolean;
|
||||||
|
@ -151,28 +143,7 @@ const Message = observer(
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{replacement ?? <Markdown content={content} />}
|
{replacement ?? <Markdown content={content} />}
|
||||||
{(() => {
|
{!queued && <InviteList message={message} />}
|
||||||
let isInvite = false;
|
|
||||||
INVITE_PATHS.forEach(path => {
|
|
||||||
if (content.includes(path)) {
|
|
||||||
isInvite = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (isInvite) {
|
|
||||||
const inviteRegex = new RegExp("(?:" + INVITE_PATHS.map((path, index) => path.split(".").join("\\.") + (index !== INVITE_PATHS.length - 1 ? "|" : "")).join("") + ")/([A-Za-z0-9]*)", "g");
|
|
||||||
if (inviteRegex.test(content)) {
|
|
||||||
let results: string[] = [];
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
inviteRegex.lastIndex = 0;
|
|
||||||
while ((match = inviteRegex.exec(content)) !== null) {
|
|
||||||
if (!results.includes(match[match.length - 1])) {
|
|
||||||
results.push(match[match.length - 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results.map(code => <EmbedInvite code={code} />);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
{queued?.error && (
|
{queued?.error && (
|
||||||
<Overline type="error" error={queued.error} />
|
<Overline type="error" error={queued.error} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -22,7 +22,7 @@ const MAX_PREVIEW_SIZE = 150;
|
||||||
export default function Embed({ embed }: Props) {
|
export default function Embed({ embed }: Props) {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
|
|
||||||
const { openScreen } = useIntermediate();
|
const { openScreen, openLink } = useIntermediate();
|
||||||
const maxWidth = Math.min(
|
const maxWidth = Math.min(
|
||||||
useContext(MessageAreaWidthContext) - CONTAINER_PADDING,
|
useContext(MessageAreaWidthContext) - CONTAINER_PADDING,
|
||||||
MAX_EMBED_WIDTH,
|
MAX_EMBED_WIDTH,
|
||||||
|
@ -111,6 +111,10 @@ export default function Embed({ embed }: Props) {
|
||||||
{embed.title && (
|
{embed.title && (
|
||||||
<span>
|
<span>
|
||||||
<a
|
<a
|
||||||
|
onClick={(e) =>
|
||||||
|
openLink(e.currentTarget.href) &&
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
href={embed.url}
|
href={embed.url}
|
||||||
target={"_blank"}
|
target={"_blank"}
|
||||||
className={styles.title}
|
className={styles.title}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import styled from "styled-components";
|
|
||||||
|
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { RetrievedInvite } from "revolt-api/types/Invites";
|
import { RetrievedInvite } from "revolt-api/types/Invites";
|
||||||
|
import { Message } from "revolt.js/dist/maps/Messages";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { useContext, useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
@ -10,8 +11,6 @@ import { defer } from "../../../../lib/defer";
|
||||||
|
|
||||||
import { dispatch } from "../../../../redux";
|
import { dispatch } from "../../../../redux";
|
||||||
|
|
||||||
import { useClient } from "../../../../context/revoltjs/RevoltClient";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AppContext,
|
AppContext,
|
||||||
ClientStatus,
|
ClientStatus,
|
||||||
|
@ -21,10 +20,8 @@ import { takeError } from "../../../../context/revoltjs/util";
|
||||||
|
|
||||||
import ServerIcon from "../../../../components/common/ServerIcon";
|
import ServerIcon from "../../../../components/common/ServerIcon";
|
||||||
import Button from "../../../../components/ui/Button";
|
import Button from "../../../../components/ui/Button";
|
||||||
import Overline from "../../../ui/Overline";
|
|
||||||
import Preloader from "../../../ui/Preloader";
|
import Preloader from "../../../ui/Preloader";
|
||||||
|
|
||||||
|
|
||||||
const EmbedInviteBase = styled.div`
|
const EmbedInviteBase = styled.div`
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
@ -35,22 +32,25 @@ const EmbedInviteBase = styled.div`
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmbedInviteDetails = styled.div`
|
const EmbedInviteDetails = styled.div`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmbedInviteName = styled.div`
|
const EmbedInviteName = styled.div`
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmbedInviteMemberCount = styled.div`
|
const EmbedInviteMemberCount = styled.div`
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
code: string
|
code: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function EmbedInvite(props: Props) {
|
export function EmbedInvite(props: Props) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const status = useContext(StatusContext);
|
const status = useContext(StatusContext);
|
||||||
|
@ -76,86 +76,116 @@ export default function EmbedInvite(props: Props) {
|
||||||
if (typeof invite === "undefined") {
|
if (typeof invite === "undefined") {
|
||||||
return error ? (
|
return error ? (
|
||||||
<EmbedInviteBase>
|
<EmbedInviteBase>
|
||||||
<ServerIcon
|
<ServerIcon size={55} />
|
||||||
size={55}
|
|
||||||
/>
|
|
||||||
<EmbedInviteDetails>
|
<EmbedInviteDetails>
|
||||||
<EmbedInviteName>
|
<EmbedInviteName>Invalid invite!</EmbedInviteName>
|
||||||
Invalid invite!
|
|
||||||
</EmbedInviteName>
|
|
||||||
</EmbedInviteDetails>
|
</EmbedInviteDetails>
|
||||||
</EmbedInviteBase>
|
</EmbedInviteBase>
|
||||||
) : (
|
) : (
|
||||||
<EmbedInviteBase>
|
<EmbedInviteBase>
|
||||||
<Preloader type="ring" />
|
<Preloader type="ring" />
|
||||||
</EmbedInviteBase>
|
</EmbedInviteBase>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <EmbedInviteBase>
|
return (
|
||||||
<ServerIcon
|
<EmbedInviteBase>
|
||||||
attachment={invite.server_icon}
|
<ServerIcon
|
||||||
server_name={invite.server_name}
|
attachment={invite.server_icon}
|
||||||
size={55}
|
server_name={invite.server_name}
|
||||||
/>
|
size={55}
|
||||||
<EmbedInviteDetails>
|
/>
|
||||||
<EmbedInviteName>
|
<EmbedInviteDetails>
|
||||||
{invite.server_name}
|
<EmbedInviteName>{invite.server_name}</EmbedInviteName>
|
||||||
</EmbedInviteName>
|
<EmbedInviteMemberCount>
|
||||||
<EmbedInviteMemberCount>
|
{invite.member_count} members
|
||||||
{invite.member_count} members
|
</EmbedInviteMemberCount>
|
||||||
</EmbedInviteMemberCount>
|
</EmbedInviteDetails>
|
||||||
</EmbedInviteDetails>
|
{processing ? (
|
||||||
{processing ? (
|
<div>
|
||||||
<div>
|
<Preloader type="ring" />
|
||||||
<Preloader type="ring" />
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<Button
|
||||||
<Button onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
|
|
||||||
if (invite.type === "Server") {
|
|
||||||
if (
|
|
||||||
client.servers.get(invite.server_id)
|
|
||||||
) {
|
|
||||||
history.push(
|
|
||||||
`/server/${invite.server_id}/channel/${invite.channel_id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispose = autorun(() => {
|
|
||||||
const server = client.servers.get(
|
|
||||||
invite.server_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
defer(() => {
|
|
||||||
if (server) {
|
|
||||||
dispatch({
|
|
||||||
type: "UNREADS_MARK_MULTIPLE_READ",
|
|
||||||
channels:
|
|
||||||
server.channel_ids,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if (invite.type === "Server") {
|
||||||
|
if (client.servers.get(invite.server_id)) {
|
||||||
history.push(
|
history.push(
|
||||||
`/server/${server._id}/channel/${invite.channel_id}`,
|
`/server/${invite.server_id}/channel/${invite.channel_id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
dispose();
|
const dispose = autorun(() => {
|
||||||
});
|
const server = client.servers.get(
|
||||||
}
|
invite.server_id,
|
||||||
|
);
|
||||||
|
|
||||||
await client.joinInvite(code);
|
defer(() => {
|
||||||
setProcessing(false);
|
if (server) {
|
||||||
} catch (err) {
|
dispatch({
|
||||||
setError(takeError(err));
|
type: "UNREADS_MARK_MULTIPLE_READ",
|
||||||
setProcessing(false);
|
channels: server.channel_ids,
|
||||||
}
|
});
|
||||||
}}>
|
|
||||||
{client.servers.get(invite.server_id) ? "Joined" : "Join"}
|
history.push(
|
||||||
</Button>
|
`/server/${server._id}/channel/${invite.channel_id}`,
|
||||||
)}
|
);
|
||||||
</EmbedInviteBase>
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.joinInvite(code);
|
||||||
|
setProcessing(false);
|
||||||
|
} catch (err) {
|
||||||
|
setError(takeError(err));
|
||||||
|
setProcessing(false);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{client.servers.get(invite.server_id) ? "Joined" : "Join"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</EmbedInviteBase>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INVITE_PATHS = [
|
||||||
|
`${location.hostname}/invite`,
|
||||||
|
"app.revolt.chat/invite",
|
||||||
|
"nightly.revolt.chat/invite",
|
||||||
|
"local.revolt.chat/invite",
|
||||||
|
"rvlt.gg",
|
||||||
|
];
|
||||||
|
|
||||||
|
const RE_INVITE = new RegExp(
|
||||||
|
`(?:${INVITE_PATHS.map((x) => x.replaceAll(".", "\\.")).join(
|
||||||
|
"|",
|
||||||
|
)})/([A-Za-z0-9]*)`,
|
||||||
|
"g",
|
||||||
|
);
|
||||||
|
|
||||||
|
export default observer(({ message }: { message: Message }) => {
|
||||||
|
if (typeof message.content !== "string") return null;
|
||||||
|
const matches = [...message.content.matchAll(RE_INVITE)];
|
||||||
|
|
||||||
|
if (matches.length > 0) {
|
||||||
|
const entries = [
|
||||||
|
...new Set(matches.slice(0, 5).map((x) => x[1])),
|
||||||
|
].slice(0, 5);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{entries.map((entry) => (
|
||||||
|
<EmbedInvite key={entry} code={entry} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
|
@ -17,6 +17,7 @@ import styles from "./Markdown.module.scss";
|
||||||
import { useCallback, useContext } from "preact/hooks";
|
import { useCallback, useContext } from "preact/hooks";
|
||||||
|
|
||||||
import { internalEmit } from "../../lib/eventEmitter";
|
import { internalEmit } from "../../lib/eventEmitter";
|
||||||
|
import { determineLink } from "../../lib/links";
|
||||||
|
|
||||||
import { getState } from "../../redux";
|
import { getState } from "../../redux";
|
||||||
|
|
||||||
|
@ -35,13 +36,6 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALLOWED_ORIGINS = [
|
|
||||||
location.hostname,
|
|
||||||
"app.revolt.chat",
|
|
||||||
"nightly.revolt.chat",
|
|
||||||
"local.revolt.chat",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Handler for code block copy.
|
// Handler for code block copy.
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
window.copycode = function (element: HTMLDivElement) {
|
window.copycode = function (element: HTMLDivElement) {
|
||||||
|
@ -100,7 +94,7 @@ const RE_CHANNELS = /<#([A-z0-9]{26})>/g;
|
||||||
|
|
||||||
export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const { openScreen } = useIntermediate();
|
const { openLink } = useIntermediate();
|
||||||
|
|
||||||
if (typeof content === "undefined") return null;
|
if (typeof content === "undefined") return null;
|
||||||
if (content.length === 0) return null;
|
if (content.length === 0) return null;
|
||||||
|
@ -142,24 +136,15 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLink = useCallback((ev: MouseEvent) => {
|
const handleLink = useCallback(
|
||||||
if (ev.currentTarget) {
|
(ev: MouseEvent) => {
|
||||||
const element = ev.currentTarget as HTMLAnchorElement;
|
if (ev.currentTarget) {
|
||||||
const url = new URL(element.href, location.href);
|
const element = ev.currentTarget as HTMLAnchorElement;
|
||||||
const pathname = url.pathname;
|
if (openLink(element.href)) ev.preventDefault();
|
||||||
|
|
||||||
if (pathname.startsWith("/@")) {
|
|
||||||
const id = pathname.substr(2);
|
|
||||||
if (/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}/.test(id)) {
|
|
||||||
ev.preventDefault();
|
|
||||||
internalEmit("Intermediate", "openProfile", id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ev.preventDefault();
|
|
||||||
internalEmit("Intermediate", "navigate", pathname);
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, []);
|
[openLink],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
|
@ -175,52 +160,23 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
el.querySelectorAll<HTMLAnchorElement>("a").forEach(
|
el.querySelectorAll<HTMLAnchorElement>("a").forEach(
|
||||||
(element) => {
|
(element) => {
|
||||||
element.removeEventListener("click", handleLink);
|
element.removeEventListener("click", handleLink);
|
||||||
|
element.addEventListener("click", handleLink);
|
||||||
element.removeAttribute("data-type");
|
element.removeAttribute("data-type");
|
||||||
element.removeAttribute("target");
|
element.removeAttribute("target");
|
||||||
|
|
||||||
let internal,
|
const link = determineLink(element.href);
|
||||||
url: URL | null = null;
|
switch (link.type) {
|
||||||
const href = element.href;
|
case "profile": {
|
||||||
if (href) {
|
element.setAttribute(
|
||||||
try {
|
"data-type",
|
||||||
url = new URL(href, location.href);
|
"mention",
|
||||||
|
);
|
||||||
if (
|
break;
|
||||||
ALLOWED_ORIGINS.includes(url.hostname)
|
}
|
||||||
) {
|
case "external": {
|
||||||
internal = true;
|
element.setAttribute("target", "_blank");
|
||||||
element.addEventListener(
|
break;
|
||||||
"click",
|
}
|
||||||
handleLink,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (url.pathname.startsWith("/@")) {
|
|
||||||
element.setAttribute(
|
|
||||||
"data-type",
|
|
||||||
"mention",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!internal) {
|
|
||||||
element.setAttribute("target", "_blank");
|
|
||||||
element.onclick = (ev) => {
|
|
||||||
const { trustedLinks } = getState();
|
|
||||||
if (
|
|
||||||
!url ||
|
|
||||||
!trustedLinks.domains?.includes(
|
|
||||||
url.hostname,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ev.preventDefault();
|
|
||||||
openScreen({
|
|
||||||
id: "external_link_prompt",
|
|
||||||
link: href,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Prompt } from "react-router";
|
import { Prompt } from "react-router";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import type { Attachment } from "revolt-api/types/Autumn";
|
import type { Attachment } from "revolt-api/types/Autumn";
|
||||||
|
import { Bot } from "revolt-api/types/Bots";
|
||||||
import type { EmbedImage } from "revolt-api/types/January";
|
import type { EmbedImage } from "revolt-api/types/January";
|
||||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||||
import { Message } from "revolt.js/dist/maps/Messages";
|
import { Message } from "revolt.js/dist/maps/Messages";
|
||||||
|
@ -11,12 +12,14 @@ import { createContext } from "preact";
|
||||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||||
|
|
||||||
import { internalSubscribe } from "../../lib/eventEmitter";
|
import { internalSubscribe } from "../../lib/eventEmitter";
|
||||||
|
import { determineLink } from "../../lib/links";
|
||||||
|
|
||||||
|
import { getState } from "../../redux";
|
||||||
|
|
||||||
import { Action } from "../../components/ui/Modal";
|
import { Action } from "../../components/ui/Modal";
|
||||||
|
|
||||||
import { Children } from "../../types/Preact";
|
import { Children } from "../../types/Preact";
|
||||||
import Modals from "./Modals";
|
import Modals from "./Modals";
|
||||||
import { Bot } from "revolt-api/types/Bots";
|
|
||||||
|
|
||||||
export type Screen =
|
export type Screen =
|
||||||
| { id: "none" }
|
| { id: "none" }
|
||||||
|
@ -103,9 +106,11 @@ export const IntermediateContext = createContext({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const IntermediateActionsContext = createContext<{
|
export const IntermediateActionsContext = createContext<{
|
||||||
|
openLink: (href?: string) => boolean;
|
||||||
openScreen: (screen: Screen) => void;
|
openScreen: (screen: Screen) => void;
|
||||||
writeClipboard: (text: string) => void;
|
writeClipboard: (text: string) => void;
|
||||||
}>({
|
}>({
|
||||||
|
openLink: null!,
|
||||||
openScreen: null!,
|
openScreen: null!,
|
||||||
writeClipboard: null!,
|
writeClipboard: null!,
|
||||||
});
|
});
|
||||||
|
@ -125,6 +130,37 @@ export default function Intermediate(props: Props) {
|
||||||
|
|
||||||
const actions = useMemo(() => {
|
const actions = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
openLink: (href?: string) => {
|
||||||
|
const link = determineLink(href);
|
||||||
|
|
||||||
|
switch (link.type) {
|
||||||
|
case "profile": {
|
||||||
|
openScreen({ id: "profile", user_id: link.id });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case "navigate": {
|
||||||
|
history.push(link.path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case "external": {
|
||||||
|
const { trustedLinks } = getState();
|
||||||
|
if (
|
||||||
|
!trustedLinks.domains?.includes(link.url.hostname)
|
||||||
|
) {
|
||||||
|
openScreen({
|
||||||
|
id: "external_link_prompt",
|
||||||
|
link: link.href,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
openScreen: (screen: Screen) => openScreen(screen),
|
openScreen: (screen: Screen) => openScreen(screen),
|
||||||
writeClipboard: (text: string) => {
|
writeClipboard: (text: string) => {
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
|
@ -134,6 +170,7 @@ export default function Intermediate(props: Props) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
43
src/lib/links.ts
Normal file
43
src/lib/links.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
type LinkType =
|
||||||
|
| { type: "profile"; id: string }
|
||||||
|
| { type: "navigate"; path: string }
|
||||||
|
| { type: "external"; href: string; url: URL }
|
||||||
|
| { type: "none" };
|
||||||
|
|
||||||
|
const ALLOWED_ORIGINS = [
|
||||||
|
location.hostname,
|
||||||
|
"app.revolt.chat",
|
||||||
|
"nightly.revolt.chat",
|
||||||
|
"local.revolt.chat",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function determineLink(href?: string): LinkType {
|
||||||
|
let internal,
|
||||||
|
url: URL | null = null;
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
try {
|
||||||
|
url = new URL(href, location.href);
|
||||||
|
|
||||||
|
if (ALLOWED_ORIGINS.includes(url.hostname)) {
|
||||||
|
const path = url.pathname;
|
||||||
|
if (path.startsWith("/@")) {
|
||||||
|
const id = path.substr(2);
|
||||||
|
if (/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}/.test(id)) {
|
||||||
|
return { type: "profile", id };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { type: "navigate", path };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal = true;
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
if (!internal && url) {
|
||||||
|
return { type: "external", href, url };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: "none" };
|
||||||
|
}
|
|
@ -73,7 +73,6 @@ export const SimpleRenderer: RendererRoutines = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
edit: async (renderer) => {
|
edit: async (renderer) => {
|
||||||
console.log("EDIT!!");
|
|
||||||
renderer.emitScroll({
|
renderer.emitScroll({
|
||||||
type: "StayAtBottom",
|
type: "StayAtBottom",
|
||||||
smooth: false,
|
smooth: false,
|
||||||
|
|
Loading…
Reference in a new issue