mirror of
https://github.com/DustinBrett/daedalOS.git
synced 2025-12-06 00:20:05 +01:00
Improved link handing in some apps
This commit is contained in:
parent
b03f725eb8
commit
207df87f89
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
47
hooks/useLinkHandler.ts
Normal 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 |
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user