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[] = [
{
icon: FAVICON_BASE_PATH,
name: "daedalOS",
url: "https://dustinbrett.com/",
},
{
icon: "/System/Icons/Favicons/google.webp",
name: "Google",
@ -22,11 +27,6 @@ export const bookmarks: Bookmark[] = [
name: "Internet Archive",
url: "https://archive.org/",
},
{
icon: FAVICON_BASE_PATH,
name: "daedalOS",
url: "https://dustinbrett.com/",
},
{
icon: "/System/Icons/Favicons/win96.webp",
name: "Windows 96",
@ -37,11 +37,6 @@ export const bookmarks: Bookmark[] = [
name: "AaronOS",
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";

View file

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

View file

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

View file

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

View file

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

View file

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