Improved link handing in some apps

This commit is contained in:
Dustin Brett 2023-11-25 14:43:24 -08:00
parent b03f725eb8
commit 207df87f89
11 changed files with 113 additions and 56 deletions

View File

@ -7,6 +7,11 @@ type Bookmark = {
}; };
export const bookmarks: Bookmark[] = [ export const bookmarks: Bookmark[] = [
{
icon: FAVICON_BASE_PATH,
name: "daedalOS",
url: "https://dustinbrett.com/",
},
{ {
icon: "/System/Icons/Favicons/google.webp", icon: "/System/Icons/Favicons/google.webp",
name: "Google", name: "Google",
@ -22,11 +27,6 @@ export const bookmarks: Bookmark[] = [
name: "Internet Archive", name: "Internet Archive",
url: "https://archive.org/", url: "https://archive.org/",
}, },
{
icon: FAVICON_BASE_PATH,
name: "daedalOS",
url: "https://dustinbrett.com/",
},
{ {
icon: "/System/Icons/Favicons/win96.webp", icon: "/System/Icons/Favicons/win96.webp",
name: "Windows 96", name: "Windows 96",
@ -37,11 +37,6 @@ export const bookmarks: Bookmark[] = [
name: "AaronOS", name: "AaronOS",
url: "https://aaronos.dev/", url: "https://aaronos.dev/",
}, },
{
icon: "/System/Icons/Favicons/diablo.webp",
name: "Diablo",
url: "https://d07riv.github.io/diabloweb/",
},
]; ];
export const HOME_PAGE = "https://www.google.com/webhp?igu=1"; export const HOME_PAGE = "https://www.google.com/webhp?igu=1";

View File

@ -30,7 +30,7 @@ const Browser: FC<ComponentProcessProps> = ({ id }) => {
processes: { [id]: process }, processes: { [id]: process },
} = useProcesses(); } = useProcesses();
const { prependFileToTitle } = useTitle(id); const { prependFileToTitle } = useTitle(id);
const { url = "" } = process || {}; const { initialTitle = "", url = "" } = process || {};
const initialUrl = url || HOME_PAGE; const initialUrl = url || HOME_PAGE;
const { canGoBack, canGoForward, history, moveHistory, position } = const { canGoBack, canGoForward, history, moveHistory, position } =
useHistory(initialUrl, id); useHistory(initialUrl, id);
@ -67,7 +67,7 @@ const Browser: FC<ComponentProcessProps> = ({ id }) => {
if (addressUrl.startsWith(GOOGLE_SEARCH_QUERY)) { if (addressUrl.startsWith(GOOGLE_SEARCH_QUERY)) {
prependFileToTitle(`${addressInput} - Google Search`); prependFileToTitle(`${addressInput} - Google Search`);
} else { } else {
const { name = "" } = const { name = initialTitle } =
bookmarks?.find( bookmarks?.find(
({ url: bookmarkUrl }) => bookmarkUrl === addressInput ({ url: bookmarkUrl }) => bookmarkUrl === addressInput
) || {}; ) || {};
@ -76,7 +76,7 @@ const Browser: FC<ComponentProcessProps> = ({ id }) => {
} }
if (addressInput.startsWith("ipfs://")) { if (addressInput.startsWith("ipfs://")) {
setIcon(id, "/System/Icons/Favicons/ipfs.webp"); setIcon(id, "/System/Icons/Favicons/ipfs.png");
} else { } else {
const favicon = new Image(); const favicon = new Image();
const faviconUrl = `${ const faviconUrl = `${
@ -105,7 +105,7 @@ const Browser: FC<ComponentProcessProps> = ({ id }) => {
} }
} }
}, },
[exists, id, prependFileToTitle, readFile, setIcon] [exists, id, initialTitle, prependFileToTitle, readFile, setIcon]
); );
useEffect(() => { useEffect(() => {

View File

@ -4,7 +4,8 @@ import { type ContainerHookProps } from "components/system/Apps/AppContainer";
import useTitle from "components/system/Window/useTitle"; import useTitle from "components/system/Window/useTitle";
import { useFileSystem } from "contexts/fileSystem"; import { useFileSystem } from "contexts/fileSystem";
import { useProcesses } from "contexts/process"; import { useProcesses } from "contexts/process";
import { haltEvent, isYouTubeUrl, loadFiles } from "utils/functions"; import { loadFiles } from "utils/functions";
import { useLinkHandler } from "hooks/useLinkHandler";
type MarkedOptions = { type MarkedOptions = {
headerIds?: boolean; headerIds?: boolean;
@ -30,7 +31,8 @@ const useMarked = ({
}: ContainerHookProps): void => { }: ContainerHookProps): void => {
const { readFile } = useFileSystem(); const { readFile } = useFileSystem();
const { prependFileToTitle } = useTitle(id); const { prependFileToTitle } = useTitle(id);
const { open, processes: { [id]: { libs = [] } = {} } = {} } = useProcesses(); const { processes: { [id]: { libs = [] } = {} } = {} } = useProcesses();
const openLink = useLinkHandler();
const loadFile = useCallback(async () => { const loadFile = useCallback(async () => {
const markdownFile = await readFile(url); const markdownFile = await readFile(url);
const container = containerRef.current?.querySelector( const container = containerRef.current?.querySelector(
@ -43,22 +45,23 @@ const useMarked = ({
headerIds: false, headerIds: false,
}) })
); );
container.querySelectorAll("a").forEach((link) => container
link.addEventListener("click", (event) => { .querySelectorAll("a")
haltEvent(event); .forEach((link) =>
link.addEventListener("click", (event) =>
if (isYouTubeUrl(link.href)) { openLink(
open("VideoPlayer", { url: link.href }); event,
} else { link.href || "",
window.open(link.href, "_blank", "noopener, noreferrer"); link.pathname,
} link.textContent || ""
}) )
); )
);
container.scrollTop = 0; container.scrollTop = 0;
} }
prependFileToTitle(basename(url)); prependFileToTitle(basename(url));
}, [containerRef, open, prependFileToTitle, readFile, url]); }, [containerRef, openLink, prependFileToTitle, readFile, url]);
useEffect(() => { useEffect(() => {
if (loading) { if (loading) {

View File

@ -20,3 +20,18 @@ export const setReadOnlyMode = (editor: Editor): void => {
editor.mode.set("readonly"); editor.mode.set("readonly");
}; };
const allowedCorsDomains = new Set(["wikipedia.org", "archive.org"]);
export const isCorsUrl = (url?: string): boolean => {
if (!url) return false;
try {
const { hostname } = new URL(url);
const [, domain, tld] = hostname.split(".");
return allowedCorsDomains.has(`${domain}.${tld}`);
} catch {
return false;
}
};

View File

@ -1,4 +1,4 @@
import { basename, dirname, extname, relative } from "path"; import { basename, dirname, extname } from "path";
import { type Editor, type NotificationSpec } from "tinymce"; import { type Editor, type NotificationSpec } from "tinymce";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { DEFAULT_SAVE_PATH, config } from "components/apps/TinyMCE/config"; import { DEFAULT_SAVE_PATH, config } from "components/apps/TinyMCE/config";
@ -8,17 +8,15 @@ import {
} from "components/apps/TinyMCE/functions"; } from "components/apps/TinyMCE/functions";
import { type IRTFJS } from "components/apps/TinyMCE/types"; import { type IRTFJS } from "components/apps/TinyMCE/types";
import { type ContainerHookProps } from "components/system/Apps/AppContainer"; import { type ContainerHookProps } from "components/system/Apps/AppContainer";
import { import { getModifiedTime } from "components/system/Files/FileEntry/functions";
getModifiedTime,
getProcessByFileExtension,
} from "components/system/Files/FileEntry/functions";
import useFileDrop from "components/system/Files/FileManager/useFileDrop"; import useFileDrop from "components/system/Files/FileManager/useFileDrop";
import useTitle from "components/system/Window/useTitle"; import useTitle from "components/system/Window/useTitle";
import { useFileSystem } from "contexts/fileSystem"; import { useFileSystem } from "contexts/fileSystem";
import { useProcesses } from "contexts/process"; import { useProcesses } from "contexts/process";
import { useSession } from "contexts/session"; import { useSession } from "contexts/session";
import { DEFAULT_LOCALE } from "utils/constants"; import { DEFAULT_LOCALE } from "utils/constants";
import { getExtension, haltEvent, loadFiles } from "utils/functions"; import { getExtension, loadFiles } from "utils/functions";
import { useLinkHandler } from "hooks/useLinkHandler";
type OptionSetter = <K, T>(name: K, value: T) => void; type OptionSetter = <K, T>(name: K, value: T) => void;
@ -28,11 +26,8 @@ const useTinyMCE = ({
setLoading, setLoading,
url, url,
}: ContainerHookProps): void => { }: ContainerHookProps): void => {
const { const { processes: { [id]: { libs = [] } = {} } = {}, url: setUrl } =
open, useProcesses();
processes: { [id]: { libs = [] } = {} } = {},
url: setUrl,
} = useProcesses();
const [editor, setEditor] = useState<Editor>(); const [editor, setEditor] = useState<Editor>();
const { prependFileToTitle } = useTitle(id); const { prependFileToTitle } = useTitle(id);
const { readFile, stat, updateFolder, writeFile } = useFileSystem(); const { readFile, stat, updateFolder, writeFile } = useFileSystem();
@ -53,31 +48,25 @@ const useTinyMCE = ({
}, },
[prependFileToTitle, stat] [prependFileToTitle, stat]
); );
const openLink = useLinkHandler();
const linksToProcesses = useCallback(() => { const linksToProcesses = useCallback(() => {
const iframe = containerRef.current?.querySelector("iframe"); const iframe = containerRef.current?.querySelector("iframe");
if (iframe?.contentWindow) { if (iframe?.contentWindow) {
[...iframe.contentWindow.document.links].forEach((link) => [...iframe.contentWindow.document.links].forEach((link) =>
link.addEventListener("click", (event) => { link.addEventListener("click", (event) => {
const mceHref = link.dataset.mceHref || ""; if (editor?.mode.isReadOnly()) {
const isRelative = openLink(
relative( event,
mceHref.startsWith("/") ? mceHref : `/${mceHref}`, link.dataset.mceHref || "",
link.pathname link.pathname,
) === ""; link.textContent || ""
if (isRelative && editor?.mode.isReadOnly()) {
haltEvent(event);
const defaultProcess = getProcessByFileExtension(
getExtension(link.pathname)
); );
if (defaultProcess) open(defaultProcess, { url: link.pathname });
} }
}) })
); );
} }
}, [containerRef, editor?.mode, open]); }, [containerRef, editor?.mode, openLink]);
const loadFile = useCallback(async () => { const loadFile = useCallback(async () => {
if (editor) { if (editor) {
setReadOnlyMode(editor); setReadOnlyMode(editor);

View File

@ -26,8 +26,8 @@ const directory: Processes = {
Component: dynamic(() => import("components/apps/Browser")), Component: dynamic(() => import("components/apps/Browser")),
backgroundColor: "#FFF", backgroundColor: "#FFF",
defaultSize: { defaultSize: {
height: 480, height: 500,
width: 640, width: 600,
}, },
icon: "/System/Icons/chromium.webp", icon: "/System/Icons/chromium.webp",
title: "Browser", title: "Browser",

View File

@ -6,6 +6,10 @@ import {
} from "components/system/Dialogs/Transfer/useTransferDialog"; } from "components/system/Dialogs/Transfer/useTransferDialog";
import { type Size } from "components/system/Window/RndWindow/useResizable"; import { type Size } from "components/system/Window/RndWindow/useResizable";
type BrowserProcessArguments = {
initialTitle?: string;
};
type DialogProcessArguments = { type DialogProcessArguments = {
fileReaders?: FileReaders | ObjectReaders; fileReaders?: FileReaders | ObjectReaders;
progress?: number; progress?: number;
@ -47,6 +51,7 @@ type BaseProcessArguments = {
}; };
export type ProcessArguments = BaseProcessArguments & export type ProcessArguments = BaseProcessArguments &
BrowserProcessArguments &
DialogProcessArguments & DialogProcessArguments &
MonacoProcessArguments & MonacoProcessArguments &
PdfProcessArguments; PdfProcessArguments;

47
hooks/useLinkHandler.ts Normal file
View File

@ -0,0 +1,47 @@
import { relative } from "path";
import { useCallback } from "react";
import { isCorsUrl } from "components/apps/TinyMCE/functions";
import { getProcessByFileExtension } from "components/system/Files/FileEntry/functions";
import { useProcesses } from "contexts/process";
import { haltEvent, isYouTubeUrl, getExtension } from "utils/functions";
type LinkHandler = (
event: Event,
url: string,
pathName: string,
title?: string
) => void;
export const useLinkHandler = (): LinkHandler => {
const { open } = useProcesses();
return useCallback(
(event: Event, url: string, pathName: string, title?: string) => {
haltEvent(event);
if (isYouTubeUrl(url)) open("VideoPlayer", { url });
else if (isCorsUrl(url)) open("Browser", { initialTitle: title, url });
else if (
!pathName ||
relative(
decodeURI(
(url.startsWith("/") ? url : `/${url}`).replace(
window.location.origin,
""
)
),
decodeURI(pathName)
) === ""
) {
const defaultProcess = getProcessByFileExtension(
getExtension(pathName)
);
if (defaultProcess) open(defaultProcess, { url: decodeURI(pathName) });
} else {
window.open(url, "_blank", "noopener, noreferrer");
}
},
[open]
);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

View File

@ -760,7 +760,10 @@ export const label = (value: string): React.HTMLAttributes<HTMLElement> => ({
}); });
export const isYouTubeUrl = (url: string): boolean => export const isYouTubeUrl = (url: string): boolean =>
url.includes("youtube.com/") || url.includes("youtu.be/"); (url.includes("youtube.com/") || url.includes("youtu.be/")) &&
!url.includes("youtube.com/@") &&
!url.includes("/channel/") &&
!url.includes("/c/");
export const getYouTubeUrlId = (url: string): string => { export const getYouTubeUrlId = (url: string): string => {
try { try {