diff --git a/extensions/web-clipper/src/app.tsx b/extensions/web-clipper/src/app.tsx index bbe34acb2..38a2952dc 100644 --- a/extensions/web-clipper/src/app.tsx +++ b/extensions/web-clipper/src/app.tsx @@ -16,20 +16,37 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import { useEffect } from "react"; import { ThemeProvider } from "./components/theme-provider"; import { useAppStore } from "./stores/app-store"; import { Login } from "./views/login"; import { Main } from "./views/main"; +import { Settings } from "./views/settings"; export function App() { const isLoggedIn = useAppStore((s) => s.isLoggedIn); const user = useAppStore((s) => s.user); + const route = useAppStore((s) => s.route); + const navigate = useAppStore((s) => s.navigate); + + useEffect(() => { + if (!isLoggedIn) { + navigate("/login"); + } else navigate("/"); + }, [isLoggedIn]); return ( {(() => { - if (!isLoggedIn) return ; - return
; + switch (route) { + case "/login": + return ; + default: + case "/": + return
; + case "/settings": + return ; + } })()} ); diff --git a/extensions/web-clipper/src/components/icons/index.ts b/extensions/web-clipper/src/components/icons/index.ts index bf39ae7b4..937eb4c4c 100644 --- a/extensions/web-clipper/src/components/icons/index.ts +++ b/extensions/web-clipper/src/components/icons/index.ts @@ -37,7 +37,8 @@ import { mdiNewspaper, mdiMagnify, mdiViewDayOutline, - mdiViewDashboardOutline + mdiViewDashboardOutline, + mdiArrowLeft } from "@mdi/js"; export const Icons = { @@ -66,7 +67,8 @@ export const Icons = { chevronUp: mdiChevronUp, chevronRight: mdiChevronRight, - none: "" + none: "", + back: mdiArrowLeft }; export type IconNames = keyof typeof Icons; diff --git a/extensions/web-clipper/src/content-scripts/all.ts b/extensions/web-clipper/src/content-scripts/all.ts index 2c77b68ee..81b68ec94 100644 --- a/extensions/web-clipper/src/content-scripts/all.ts +++ b/extensions/web-clipper/src/content-scripts/all.ts @@ -25,11 +25,13 @@ import { enterNodeSelectionMode } from "@notesnook/clipper"; import { ClipArea, ClipMode } from "../common/bridge"; +import type { Config } from "@notesnook/clipper/dist/types"; type ClipMessage = { type: "clip"; mode?: ClipMode; area?: ClipArea; + settings?: Config; }; type ViewportMessage = { @@ -62,21 +64,22 @@ browser.runtime.onMessage.addListener(async (request) => { function clip(message: ClipMessage) { try { + const config = message.settings; const isScreenshot = message.mode === "screenshot"; const withStyles = message.mode === "complete" || isScreenshot; if (isScreenshot && message.area === "full-page") { - return clipScreenshot(document.body, "jpeg"); + return clipScreenshot(document.body, "jpeg", config); } else if (message.area === "full-page") { - return clipPage(document, withStyles, false); + return clipPage(document, withStyles, false, config); } else if (message.area === "selection") { - enterNodeSelectionMode(document).then((result) => + enterNodeSelectionMode(document, config).then((result) => browser.runtime.sendMessage({ type: "manual", data: result }) ); } else if (message.area === "article") { - return clipArticle(document, withStyles); + return clipArticle(document, withStyles, config); } else if (message.area === "visible") { - return clipPage(document, withStyles, true); + return clipPage(document, withStyles, true, config); } } catch (e) { console.error(e); diff --git a/extensions/web-clipper/src/stores/app-store.tsx b/extensions/web-clipper/src/stores/app-store.tsx index d9a6bc9d6..bdeb8bbdd 100644 --- a/extensions/web-clipper/src/stores/app-store.tsx +++ b/extensions/web-clipper/src/stores/app-store.tsx @@ -27,8 +27,10 @@ interface AppStore { notes: ItemReference[]; notebooks: NotebookReference[]; tags: ItemReference[]; + route: string; login(openNew?: boolean): Promise; + navigate(route: string): void; } export const useAppStore = create((set) => ({ @@ -37,6 +39,11 @@ export const useAppStore = create((set) => ({ notebooks: [], notes: [], tags: [], + route: "/login", + + navigate(route) { + set({ route }); + }, async login(openNew = false) { set({ isLoggingIn: true }); diff --git a/extensions/web-clipper/src/views/main.tsx b/extensions/web-clipper/src/views/main.tsx index 85aba7664..d5f3c19ac 100644 --- a/extensions/web-clipper/src/views/main.tsx +++ b/extensions/web-clipper/src/views/main.tsx @@ -29,13 +29,15 @@ import { SelectedNotebook, ClipArea, ClipMode, - ClipData, + ClipData } from "../common/bridge"; import { usePersistentState } from "../hooks/use-persistent-state"; import { deleteClip, getClip } from "../utils/storage"; import { useAppStore } from "../stores/app-store"; import { connectApi } from "../api"; import { FlexScrollContainer } from "../components/scroll-container"; +import { DEFAULT_SETTINGS, SETTINGS_KEY } from "./settings"; +import type { Config } from "@notesnook/clipper/dist/types"; const clipAreas: { name: string; id: ClipArea; icon: string }[] = [ { @@ -86,6 +88,9 @@ export function Main() { // const [colorMode, setColorMode] = useColorMode(); const isPremium = useAppStore((s) => s.user?.pro); + const navigate = useAppStore((s) => s.navigate); + + const [settings] = usePersistentState(SETTINGS_KEY, DEFAULT_SETTINGS); const [title, setTitle] = useState(); const [url, setUrl] = useState(); const [clipNonce, setClipNonce] = useState(0); @@ -375,7 +380,13 @@ export function Main() { justifyContent: "flex-end" }} > - @@ -386,7 +397,8 @@ export function Main() { export async function clip( area: ClipArea, - mode: ClipMode + mode: ClipMode, + settings: Config = DEFAULT_SETTINGS ): Promise { const clipData = await getClip(); if (area === "selection" && typeof clipData === "string") { @@ -410,5 +422,10 @@ export async function clip( }; } - return await browser.tabs.sendMessage(tab.id, { type: "clip", mode, area }); + return await browser.tabs.sendMessage(tab.id, { + type: "clip", + mode, + area, + settings + }); } diff --git a/extensions/web-clipper/src/views/settings.tsx b/extensions/web-clipper/src/views/settings.tsx index b9cea263c..f260a2131 100644 --- a/extensions/web-clipper/src/views/settings.tsx +++ b/extensions/web-clipper/src/views/settings.tsx @@ -16,4 +16,80 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -export const s = ""; \ No newline at end of file + +import { Button, Flex, Input, Label, Text } from "@theme-ui/components"; +import { Icons } from "../components/icons"; +import { Icon } from "../components/icons/icon"; +import { usePersistentState } from "../hooks/use-persistent-state"; +import { useAppStore } from "../stores/app-store"; +import type { Config } from "@notesnook/clipper/dist/types"; + +export const SETTINGS_KEY = "settings"; +export const DEFAULT_SETTINGS: Config = { + corsProxy: "https://cors.notesnook.com" +}; + +export function Settings() { + const navigate = useAppStore((s) => s.navigate); + const [settings, saveSettings] = usePersistentState( + SETTINGS_KEY, + DEFAULT_SETTINGS + ); + + return ( + { + e.preventDefault(); + + const form = new FormData(e.target as HTMLFormElement); + + let corsProxy = form.get("corsProxy")?.toString(); + if (corsProxy) { + const url = new URL(corsProxy); + corsProxy = `${url.protocol}//${url.hostname}`; + } + + saveSettings({ + corsProxy + }); + }} + > + + { + navigate("/"); + }} + sx={{ mr: 2 }} + /> + Settings + + + + + ); +} diff --git a/packages/clipper/src/fetch.ts b/packages/clipper/src/fetch.ts index f86a03e31..0c66fa200 100644 --- a/packages/clipper/src/fetch.ts +++ b/packages/clipper/src/fetch.ts @@ -18,7 +18,7 @@ along with this program. If not, see . */ export type FetchOptions = { bypassCors?: boolean; - corsHost?: string; + corsHost: string; noCache?: boolean; crossOrigin?: "anonymous" | "use-credentials" | null; }; diff --git a/packages/clipper/src/index.ts b/packages/clipper/src/index.ts index 70f105a14..15be368d1 100644 --- a/packages/clipper/src/index.ts +++ b/packages/clipper/src/index.ts @@ -21,7 +21,7 @@ import { Readability } from "@mozilla/readability"; import { injectCss } from "./utils"; import { app, h, text } from "hyperapp"; import { getInlinedNode, toBlob, toJpeg, toPng } from "./domtoimage"; -import { InlineOptions } from "./types"; +import { Config, InlineOptions } from "./types"; import { FetchOptions } from "./fetch"; type ReadabilityEnhanced = Readability & { @@ -36,13 +36,6 @@ const CLASSES = { const BLACKLIST = [CLASSES.nodeSelected, CLASSES.nodeSelectionContainer]; -const fetchOptions: FetchOptions = { - bypassCors: true, - corsHost: "https://cors.notesnook.com", - crossOrigin: "anonymous", - noCache: true -}; - const inlineOptions: InlineOptions = { fonts: false, images: true, @@ -52,9 +45,15 @@ const inlineOptions: InlineOptions = { async function clipPage( document: Document, withStyles: boolean, - onlyVisible: boolean + onlyVisible: boolean, + config?: Config ): Promise { - const { body, head } = await getPage(document, withStyles, onlyVisible); + const { body, head } = await getPage( + document, + withStyles, + config, + onlyVisible + ); if (!body || !head) return null; const result = toDocument(head, body).documentElement.outerHTML; return `\n${result}`; @@ -62,9 +61,10 @@ async function clipPage( async function clipArticle( doc: Document, - withStyles: boolean + withStyles: boolean, + config?: Config ): Promise { - const { body, head } = await getPage(doc, withStyles); + const { body, head } = await getPage(doc, withStyles, config); if (!body || !head) return null; const newDoc = toDocument(head, body); @@ -99,7 +99,8 @@ async function clipScreenshot< : Blob | undefined >( target?: HTMLElement, - output: TOutputFormat = "jpeg" as TOutputFormat + output: TOutputFormat = "jpeg" as TOutputFormat, + config?: Config ): Promise { const screenshotTarget = target || document.body; @@ -109,7 +110,7 @@ async function clipScreenshot< backgroundColor: "white", width: document.body.scrollWidth, height: document.body.scrollHeight, - fetchOptions, + fetchOptions: resolveFetchOptions(config), inlineOptions: { fonts: true, images: true, @@ -181,7 +182,7 @@ function removeClickHandlers(doc: Document) { doc.body.removeEventListener("click", onMouseClick); } -function enterNodeSelectionMode(doc: Document) { +function enterNodeSelectionMode(doc: Document, config?: Config) { setTimeout(() => { registerClickListeners(doc); registerHoverListeners(doc); @@ -203,7 +204,7 @@ function enterNodeSelectionMode(doc: Document) { node.classList.remove(CLASSES.nodeSelected); const inlined = await getInlinedNode(node as HTMLElement, { raster: false, - fetchOptions, + fetchOptions: resolveFetchOptions(config), inlineOptions }); if (!inlined) continue; @@ -454,11 +455,12 @@ function cleanup() { async function getPage( document: Document, styles: boolean, + config?: Config, onlyVisible = false ) { const body = await getInlinedNode(document.body, { raster: true, - fetchOptions, + fetchOptions: resolveFetchOptions(config), inlineOptions: { fonts: false, images: true, @@ -482,3 +484,14 @@ async function getPage( head }; } + +function resolveFetchOptions(config?: Config): FetchOptions | undefined { + return config?.corsProxy + ? { + bypassCors: true, + corsHost: config.corsProxy, + crossOrigin: "anonymous", + noCache: true + } + : undefined; +} diff --git a/packages/clipper/src/types.ts b/packages/clipper/src/types.ts index 3bf59128f..cd25c2db2 100644 --- a/packages/clipper/src/types.ts +++ b/packages/clipper/src/types.ts @@ -49,3 +49,7 @@ export type Options = { inlineOptions?: InlineOptions; styles?: boolean; }; + +export type Config = { + corsProxy?: string; +};