mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-25 16:40:58 -05:00
feat(modal): port LinkWarning
This commit is contained in:
parent
241b9cd27b
commit
b7be9f8c03
11 changed files with 84 additions and 51 deletions
|
@ -107,6 +107,7 @@
|
||||||
"eslint-config-preact": "^1.1.4",
|
"eslint-config-preact": "^1.1.4",
|
||||||
"eslint-plugin-jsdoc": "^39.3.2",
|
"eslint-plugin-jsdoc": "^39.3.2",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
|
"history": "4",
|
||||||
"json-stringify-deterministic": "^1.0.2",
|
"json-stringify-deterministic": "^1.0.2",
|
||||||
"localforage": "^1.9.0",
|
"localforage": "^1.9.0",
|
||||||
"lodash.defaultsdeep": "^4.6.1",
|
"lodash.defaultsdeep": "^4.6.1",
|
||||||
|
|
5
src/context/history.ts
Normal file
5
src/context/history.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { createBrowserHistory } from "history";
|
||||||
|
|
||||||
|
export const history = createBrowserHistory({
|
||||||
|
basename: import.meta.env.BASE_URL,
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import { BrowserRouter as Router, Link } from "react-router-dom";
|
import { Router, Link } from "react-router-dom";
|
||||||
|
|
||||||
import { ContextMenuTrigger } from "preact-context-menu";
|
import { ContextMenuTrigger } from "preact-context-menu";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
|
@ -10,6 +10,7 @@ import { hydrateState } from "../mobx/State";
|
||||||
|
|
||||||
import Locale from "./Locale";
|
import Locale from "./Locale";
|
||||||
import Theme from "./Theme";
|
import Theme from "./Theme";
|
||||||
|
import { history } from "./history";
|
||||||
import Intermediate from "./intermediate/Intermediate";
|
import Intermediate from "./intermediate/Intermediate";
|
||||||
import ModalRenderer from "./modals/ModalRenderer";
|
import ModalRenderer from "./modals/ModalRenderer";
|
||||||
import Client from "./revoltjs/RevoltClient";
|
import Client from "./revoltjs/RevoltClient";
|
||||||
|
@ -36,7 +37,7 @@ export default function Context({ children }: { children: Children }) {
|
||||||
if (!ready) return <Preloader type="spinner" />;
|
if (!ready) return <Preloader type="spinner" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router basename={import.meta.env.BASE_URL}>
|
<Router history={history}>
|
||||||
<UIProvider value={uiContext}>
|
<UIProvider value={uiContext}>
|
||||||
<Locale>
|
<Locale>
|
||||||
<Intermediate>
|
<Intermediate>
|
||||||
|
|
|
@ -141,35 +141,7 @@ export default function Intermediate(props: Props) {
|
||||||
const actions = useMemo(() => {
|
const actions = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
openLink: (href?: string, trusted?: boolean) => {
|
openLink: (href?: string, trusted?: boolean) => {
|
||||||
const link = determineLink(href);
|
return modalController.openLink(href, trusted);
|
||||||
|
|
||||||
switch (link.type) {
|
|
||||||
case "profile": {
|
|
||||||
openScreen({ id: "profile", user_id: link.id });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case "navigate": {
|
|
||||||
history.push(link.path);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case "external": {
|
|
||||||
if (
|
|
||||||
!trusted &&
|
|
||||||
!settings.security.isTrustedOrigin(
|
|
||||||
link.url.hostname,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
openScreen({
|
|
||||||
id: "external_link_prompt",
|
|
||||||
link: link.href,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
window.open(link.href, "_blank", "noreferrer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
openScreen: (screen: Screen) => openScreen(screen),
|
openScreen: (screen: Screen) => openScreen(screen),
|
||||||
writeClipboard: (a: string) => modalController.writeText(a),
|
writeClipboard: (a: string) => modalController.writeText(a),
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
//import { isModalClosing } from "../../components/ui/Modal";
|
//import { isModalClosing } from "../../components/ui/Modal";
|
||||||
import { Screen } from "./Intermediate";
|
import { Screen } from "./Intermediate";
|
||||||
import { ExternalLinkModal } from "./modals/ExternalLinkPrompt";
|
|
||||||
import { InputModal } from "./modals/Input";
|
import { InputModal } from "./modals/Input";
|
||||||
import { OnboardingModal } from "./modals/Onboarding";
|
import { OnboardingModal } from "./modals/Onboarding";
|
||||||
import { PromptModal } from "./modals/Prompt";
|
import { PromptModal } from "./modals/Prompt";
|
||||||
|
@ -23,8 +22,6 @@ export default function Modals({ screen, openScreen }: Props) {
|
||||||
return <InputModal onClose={onClose} {...screen} />;
|
return <InputModal onClose={onClose} {...screen} />;
|
||||||
case "onboarding":
|
case "onboarding":
|
||||||
return <OnboardingModal onClose={onClose} {...screen} />;
|
return <OnboardingModal onClose={onClose} {...screen} />;
|
||||||
case "external_link_prompt":
|
|
||||||
return <ExternalLinkModal onClose={onClose} {...screen} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -2,35 +2,32 @@ import { Text } from "preact-i18n";
|
||||||
|
|
||||||
import { Modal } from "@revoltchat/ui";
|
import { Modal } from "@revoltchat/ui";
|
||||||
|
|
||||||
|
import { noopTrue } from "../../../lib/js";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
import { useIntermediate } from "../Intermediate";
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
interface Props {
|
export default function LinkWarning({
|
||||||
onClose: () => void;
|
link,
|
||||||
link: string;
|
callback,
|
||||||
}
|
...props
|
||||||
|
}: ModalProps<"link_warning">) {
|
||||||
export function ExternalLinkModal({ onClose, link }: Props) {
|
|
||||||
const { openLink } = useIntermediate();
|
|
||||||
const settings = useApplicationState().settings;
|
const settings = useApplicationState().settings;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
onClose={onClose}
|
{...props}
|
||||||
title={<Text id={"app.special.modals.external_links.title"} />}
|
title={<Text id={"app.special.modals.external_links.title"} />}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
onClick: () => {
|
onClick: callback,
|
||||||
openLink(link, true);
|
|
||||||
onClose();
|
|
||||||
},
|
|
||||||
confirmation: true,
|
confirmation: true,
|
||||||
palette: "accent",
|
palette: "accent",
|
||||||
children: "Continue",
|
children: "Continue",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onClick: onClose,
|
onClick: noopTrue,
|
||||||
confirmation: false,
|
confirmation: false,
|
||||||
children: "Cancel",
|
children: "Cancel",
|
||||||
},
|
},
|
||||||
|
@ -41,8 +38,7 @@ export function ExternalLinkModal({ onClose, link }: Props) {
|
||||||
settings.security.addTrustedOrigin(url.hostname);
|
settings.security.addTrustedOrigin(url.hostname);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
openLink(link, true);
|
return callback();
|
||||||
onClose();
|
|
||||||
},
|
},
|
||||||
palette: "plain",
|
palette: "plain",
|
||||||
children: (
|
children: (
|
|
@ -8,9 +8,16 @@ import {
|
||||||
import type { Client, API } from "revolt.js";
|
import type { Client, API } from "revolt.js";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
|
import { determineLink } from "../../lib/links";
|
||||||
|
|
||||||
|
import { getApplicationState, useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
|
import { history } from "../history";
|
||||||
|
// import { determineLink } from "../../lib/links";
|
||||||
import Changelog from "./components/Changelog";
|
import Changelog from "./components/Changelog";
|
||||||
import Clipboard from "./components/Clipboard";
|
import Clipboard from "./components/Clipboard";
|
||||||
import Error from "./components/Error";
|
import Error from "./components/Error";
|
||||||
|
import LinkWarning from "./components/LinkWarning";
|
||||||
import MFAEnableTOTP from "./components/MFAEnableTOTP";
|
import MFAEnableTOTP from "./components/MFAEnableTOTP";
|
||||||
import MFAFlow from "./components/MFAFlow";
|
import MFAFlow from "./components/MFAFlow";
|
||||||
import MFARecovery from "./components/MFARecovery";
|
import MFARecovery from "./components/MFARecovery";
|
||||||
|
@ -156,12 +163,46 @@ class ModalControllerExtended extends ModalController<Modal> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openLink(href?: string, trusted?: boolean) {
|
||||||
|
const link = determineLink(href);
|
||||||
|
const settings = getApplicationState().settings;
|
||||||
|
|
||||||
|
switch (link.type) {
|
||||||
|
case "profile": {
|
||||||
|
// TODO: port Profile
|
||||||
|
// openScreen({ id: "profile", user_id: link.id });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "navigate": {
|
||||||
|
history.push(link.path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "external": {
|
||||||
|
if (
|
||||||
|
!trusted &&
|
||||||
|
!settings.security.isTrustedOrigin(link.url.hostname)
|
||||||
|
) {
|
||||||
|
modalController.push({
|
||||||
|
type: "link_warning",
|
||||||
|
link: link.href,
|
||||||
|
callback: () => this.openLink(href, true) as true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.open(link.href, "_blank", "noreferrer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const modalController = new ModalControllerExtended({
|
export const modalController = new ModalControllerExtended({
|
||||||
changelog: Changelog,
|
changelog: Changelog,
|
||||||
clipboard: Clipboard,
|
clipboard: Clipboard,
|
||||||
error: Error,
|
error: Error,
|
||||||
|
link_warning: LinkWarning,
|
||||||
mfa_flow: MFAFlow,
|
mfa_flow: MFAFlow,
|
||||||
mfa_recovery: MFARecovery,
|
mfa_recovery: MFARecovery,
|
||||||
mfa_enable_totp: MFAEnableTOTP,
|
mfa_enable_totp: MFAEnableTOTP,
|
||||||
|
|
|
@ -51,6 +51,11 @@ export type Modal = {
|
||||||
type: "clipboard";
|
type: "clipboard";
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: "link_warning";
|
||||||
|
link: string;
|
||||||
|
callback: () => true;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: "signed_out";
|
type: "signed_out";
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,3 +311,11 @@ export async function hydrateState() {
|
||||||
export function useApplicationState() {
|
export function useApplicationState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the application state
|
||||||
|
* @returns Application state
|
||||||
|
*/
|
||||||
|
export function getApplicationState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { makeAutoObservable, computed, action } from "mobx";
|
||||||
|
|
||||||
import Settings from "../Settings";
|
import Settings from "../Settings";
|
||||||
|
|
||||||
|
const TRUSTED_DOMAINS = ["revolt.chat", "revolt.wtf", "gifbox.me", "rvlt.gg"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for changing security options.
|
* Helper class for changing security options.
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +29,10 @@ export default class SSecurity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed isTrustedOrigin(origin: string) {
|
@computed isTrustedOrigin(origin: string) {
|
||||||
|
if (TRUSTED_DOMAINS.find((x) => origin.endsWith(x))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return this.settings.get("security:trustedOrigins")?.includes(origin);
|
return this.settings.get("security:trustedOrigins")?.includes(origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3574,6 +3574,7 @@ __metadata:
|
||||||
eslint-plugin-jsdoc: ^39.3.2
|
eslint-plugin-jsdoc: ^39.3.2
|
||||||
eventemitter3: ^4.0.7
|
eventemitter3: ^4.0.7
|
||||||
fs-extra: ^10.0.0
|
fs-extra: ^10.0.0
|
||||||
|
history: 4
|
||||||
json-stringify-deterministic: ^1.0.2
|
json-stringify-deterministic: ^1.0.2
|
||||||
klaw: ^3.0.0
|
klaw: ^3.0.0
|
||||||
localforage: ^1.9.0
|
localforage: ^1.9.0
|
||||||
|
@ -5043,7 +5044,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"history@npm:^4.9.0":
|
"history@npm:4, history@npm:^4.9.0":
|
||||||
version: 4.10.1
|
version: 4.10.1
|
||||||
resolution: "history@npm:4.10.1"
|
resolution: "history@npm:4.10.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue