diff --git a/src/components/common/AgeGate.tsx b/src/components/common/AgeGate.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b78f60b94dbf641ba076a2b70f7b3aea0ae31649 --- /dev/null +++ b/src/components/common/AgeGate.tsx @@ -0,0 +1,90 @@ +import { useHistory } from "react-router-dom"; +import { useState } from "preact/hooks"; +import styled from "styled-components"; +import { dispatch, getState } from "../../redux"; +import Checkbox from "../ui/Checkbox"; +import Button from "../ui/Button"; +import { Children } from "../../types/Preact"; +import { Channel } from "revolt.js"; +import { Text } from "preact-i18n"; + +const Base = styled.div` + display: flex; + flex-grow: 1; + flex-direction: column; + align-items: center; + justify-content: center; + user-select: none; + padding: 12px; + + img { + height: 150px; + } + + .subtext { + color: var(--secondary-foreground); + margin-bottom: 12px; + font-size: 14px; + } + + .actions { + margin-top: 20px; + display: flex; + gap: 12px; + } +`; + +type Props = { + gated: boolean; + children: Children; +} & { + type: 'channel'; + channel: Channel; +} + +export default function AgeGate(props: Props) { + const history = useHistory(); + const [consent, setConsent] = useState(getState().sectionToggle['nsfw'] ?? false); + const [ageGate, setAgeGate] = useState(false); + + if (ageGate || !props.gated) { + return <>{ props.children }</>; + } else { + if (!(props.channel.channel_type === 'Group' || props.channel.channel_type === 'TextChannel')) return <>{ props.children }</>; + + return ( + <Base> + <img + src={"https://static.revolt.chat/emoji/mutant/26a0.svg"} + draggable={false} + /> + <h2>{props.channel.name}</h2> + <span className="subtext"> + <Text id={`app.main.channel.nsfw.${props.type}.marked`} />{" "} + <a href="#"><Text id={`app.main.channel.nsfw.learn_more`} /></a> + </span> + + <Checkbox checked={consent} onChange={(v) => { + setConsent(v); + if (v) { + dispatch({ type: 'SECTION_TOGGLE_SET', id: 'nsfw', state: true }); + } else { + dispatch({ type: 'SECTION_TOGGLE_UNSET', id: 'nsfw' }); + } + }}> + <Text id="app.main.channel.nsfw.confirm" /> + </Checkbox> + <div className="actions"> + <Button contrast onClick={() => history.goBack()}> + <Text id="app.special.modals.actions.back" /> + </Button> + <Button + contrast + onClick={() => consent && setAgeGate(true)}> + <Text id={`app.main.channel.nsfw.${props.type}.confirm`} /> + </Button> + </div> + </Base> + ); + } +} diff --git a/src/components/common/messaging/MessageBase.tsx b/src/components/common/messaging/MessageBase.tsx index c5e6ebb8b88e7063305cdf7a481cebf31c660882..e040168ef1043971b8ab184a41b0c1c3738561f3 100644 --- a/src/components/common/messaging/MessageBase.tsx +++ b/src/components/common/messaging/MessageBase.tsx @@ -21,7 +21,7 @@ export interface BaseMessageProps { export default styled.div<BaseMessageProps>` display: flex; - overflow-x: none; + overflow: none; padding: 0.125rem; flex-direction: row; padding-right: 16px; diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index 1b531460ac47215118eb53a2b3c3991e5578c8f7..205328ce9ff596199d6a28fb2950ed5a8a093689 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -66,16 +66,9 @@ export const md: MarkdownIt = MarkdownIt({ .use(MarkdownKatex, { throwOnError: false, maxExpand: 0, + maxSize: 10 }); -// ? Force links to open _blank. -// From: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer -const defaultRender = - md.renderer.rules.link_open || - function (tokens, idx, options, _env, self) { - return self.renderToken(tokens, idx, options); - }; - // TODO: global.d.ts file for defining globals declare global { interface Window { @@ -124,15 +117,19 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { }, []); const handleLink = useCallback((ev: MouseEvent) => { - ev.preventDefault(); if (ev.currentTarget) { const element = ev.currentTarget as HTMLAnchorElement; const url = new URL(element.href, location.href); const pathname = url.pathname; if (pathname.startsWith("/@")) { - internalEmit("Intermediate", "openProfile", pathname.substr(2)); + let id = pathname.substr(2); + if (/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}/.test(id)) { + ev.preventDefault(); + internalEmit("Intermediate", "openProfile", id); + } } else { + ev.preventDefault(); internalEmit("Intermediate", "navigate", pathname); } } diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx index f68b29a4536aebcf11f92c624cad4088f376bc62..6484bdc96efb77c11f4ca970fd04865be216d2b3 100644 --- a/src/pages/channels/Channel.tsx +++ b/src/pages/channels/Channel.tsx @@ -13,8 +13,7 @@ import { useChannel, useForceUpdate } from "../../context/revoltjs/hooks"; import MessageBox from "../../components/common/messaging/MessageBox"; import JumpToBottom from "../../components/common/messaging/bars/JumpToBottom"; import TypingIndicator from "../../components/common/messaging/bars/TypingIndicator"; -import Button from "../../components/ui/Button"; -import Checkbox from "../../components/ui/Checkbox"; +import AgeGate from "../../components/common/AgeGate"; import MemberSidebar from "../../components/navigation/right/MemberSidebar"; import ChannelHeader from "./ChannelHeader"; @@ -36,32 +35,6 @@ const ChannelContent = styled.div` flex-direction: column; `; -const AgeGate = styled.div` - display: flex; - flex-grow: 1; - flex-direction: column; - align-items: center; - justify-content: center; - user-select: none; - padding: 12px; - - img { - height: 150px; - } - - .subtext { - color: var(--secondary-foreground); - margin-bottom: 12px; - font-size: 14px; - } - - .actions { - margin-top: 20px; - display: flex; - gap: 12px; - } -`; - export function Channel({ id }: { id: string }) { const ctx = useForceUpdate(); const channel = useChannel(id, ctx); @@ -77,52 +50,18 @@ export function Channel({ id }: { id: string }) { const MEMBERS_SIDEBAR_KEY = "sidebar_members"; function TextChannel({ channel }: { channel: Channels.Channel }) { - if ( - (channel.channel_type === "TextChannel" || - channel.channel_type === "Group") && - channel.name.includes("nsfw") - ) { - const goBack = useHistory(); - const [consent, setConsent] = useState(false); - const [ageGate, setAgeGate] = useState(false); - if (!ageGate) { - return ( - <AgeGate> - <img - src={"https://static.revolt.chat/emoji/mutant/26a0.svg"} - draggable={false} - /> - <h2>{channel.name}</h2> - <span className="subtext"> - This channel is marked as NSFW.{" "} - <a href="#">Learn more</a> - </span> - - <Checkbox checked={consent} onChange={(v) => setConsent(v)}> - I confirm that I am at least 18 years old. - </Checkbox> - <div className="actions"> - <Button contrast onClick={() => goBack}> - Go back - </Button> - <Button - contrast - onClick={() => consent && setAgeGate(true)}> - Enter Channel - </Button> - </div> - </AgeGate> - ); - } - } - const [showMembers, setMembers] = useState( getState().sectionToggle[MEMBERS_SIDEBAR_KEY] ?? true, ); let id = channel._id; return ( - <> + <AgeGate + type="channel" + channel={channel} + gated={(channel.channel_type === "TextChannel" || + channel.channel_type === "Group") && + channel.name.includes("nsfw")}> <ChannelHeader channel={channel} toggleSidebar={() => { @@ -154,7 +93,7 @@ function TextChannel({ channel }: { channel: Channels.Channel }) { <MemberSidebar channel={channel} /> )} </ChannelMain> - </> + </AgeGate> ); }