diff --git a/apps/browser-extension/package.json b/apps/browser-extension/package.json index 6c40d372..7ede2c05 100644 --- a/apps/browser-extension/package.json +++ b/apps/browser-extension/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@hoarder/shared-react": "workspace:^0.1.0", + "@hoarder/shared": "workspace:^0.1.0", "@hoarder/trpc": "workspace:^0.1.0", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-popover": "^1.0.7", diff --git a/apps/browser-extension/src/SavePage.tsx b/apps/browser-extension/src/SavePage.tsx index c6f85b3b..06530f9d 100644 --- a/apps/browser-extension/src/SavePage.tsx +++ b/apps/browser-extension/src/SavePage.tsx @@ -1,7 +1,13 @@ import { useEffect, useState } from "react"; import { Navigate } from "react-router-dom"; -import { BookmarkTypes } from "../../../packages/shared/types/bookmarks"; +import { + BookmarkTypes, + ZNewBookmarkRequest, + zNewBookmarkRequestSchema, +} from "@hoarder/shared/types/bookmarks"; + +import { NEW_BOOKMARK_REQUEST_KEY_NAME } from "./background/protocol"; import Spinner from "./Spinner"; import { api } from "./utils/trpc"; @@ -19,23 +25,37 @@ export default function SavePage() { }); useEffect(() => { + async function getNewBookmarkRequestFromBackgroundScriptIfAny(): Promise { + const { [NEW_BOOKMARK_REQUEST_KEY_NAME]: req } = + await chrome.storage.session.get(NEW_BOOKMARK_REQUEST_KEY_NAME); + if (!req) { + return null; + } + // Delete the request immediately to avoid issues with lingering values + await chrome.storage.session.remove(NEW_BOOKMARK_REQUEST_KEY_NAME); + return zNewBookmarkRequestSchema.parse(req); + } + async function runSave() { - let currentUrl; - const [currentTab] = await chrome.tabs.query({ - active: true, - lastFocusedWindow: true, - }); - if (currentTab?.url) { - currentUrl = currentTab.url; - } else { - setError("Couldn't find the URL of the current tab"); - return; + let newBookmarkRequest = + await getNewBookmarkRequestFromBackgroundScriptIfAny(); + if (!newBookmarkRequest) { + const [currentTab] = await chrome.tabs.query({ + active: true, + lastFocusedWindow: true, + }); + if (currentTab?.url) { + newBookmarkRequest = { + type: BookmarkTypes.LINK, + url: currentTab.url, + }; + } else { + setError("Couldn't find the URL of the current tab"); + return; + } } - createBookmark({ - type: BookmarkTypes.LINK, - url: currentUrl, - }); + createBookmark(newBookmarkRequest); } runSave(); }, [createBookmark]); diff --git a/apps/browser-extension/src/background/background.ts b/apps/browser-extension/src/background/background.ts index 9c4604af..cab58aa9 100644 --- a/apps/browser-extension/src/background/background.ts +++ b/apps/browser-extension/src/background/background.ts @@ -1,40 +1,79 @@ +import { + BookmarkTypes, + ZNewBookmarkRequest, +} from "@hoarder/shared/types/bookmarks.ts"; + import { getPluginSettings, Settings, subscribeToSettingsChanges, } from "../utils/settings.ts"; +import { NEW_BOOKMARK_REQUEST_KEY_NAME } from "./protocol.ts"; const OPEN_HOARDER_ID = "open-hoarder"; +const ADD_LINK_TO_HOARDER_ID = "add-link"; function checkSettingsState(settings: Settings) { if (settings?.address) { - registerContextMenu(); + registerContextMenus(); } else { - chrome.contextMenus.remove(OPEN_HOARDER_ID); + removeContextMenus(); } } +function removeContextMenus() { + chrome.contextMenus.remove(OPEN_HOARDER_ID); + chrome.contextMenus.remove(ADD_LINK_TO_HOARDER_ID); +} + /** - * Registers a context menu button to open a tab with the currently configured hoarder instance + * Registers + * * a context menu button to open a tab with the currently configured hoarder instance + * * a context menu button to add a link to hoarder without loading the page */ -function registerContextMenu() { +function registerContextMenus() { chrome.contextMenus.create({ id: OPEN_HOARDER_ID, title: "Open Hoarder", contexts: ["action"], }); + chrome.contextMenus.create({ + id: ADD_LINK_TO_HOARDER_ID, + title: "Add to Hoarder", + contexts: ["link", "page", "selection", "image"], + }); } /** * Reads the current settings and opens a new tab with hoarder * @param info the information about the click in the context menu */ -function handleContextMenuClick(info: chrome.contextMenus.OnClickData) { +async function handleContextMenuClick(info: chrome.contextMenus.OnClickData) { const { menuItemId } = info; if (menuItemId === OPEN_HOARDER_ID) { getPluginSettings().then((settings: Settings) => { chrome.tabs.create({ url: settings.address, active: true }); }); + } else if (menuItemId === ADD_LINK_TO_HOARDER_ID) { + let newBookmark: ZNewBookmarkRequest | null = null; + if (info.selectionText) { + newBookmark = { + type: BookmarkTypes.TEXT, + text: info.selectionText, + // TODO: Include a source url in the snippet + }; + } else if (info.srcUrl ?? info.linkUrl ?? info.pageUrl) { + newBookmark = { + type: BookmarkTypes.LINK, + url: info.srcUrl ?? info.linkUrl ?? info.pageUrl, + }; + } + if (newBookmark) { + await chrome.storage.session.set({ + [NEW_BOOKMARK_REQUEST_KEY_NAME]: newBookmark, + }); + await chrome.action.openPopup(); + } } } @@ -46,4 +85,5 @@ subscribeToSettingsChanges((settings) => { checkSettingsState(settings); }); +// eslint-disable-next-line @typescript-eslint/no-misused-promises -- Manifest V3 allows async functions for all callbacks chrome.contextMenus.onClicked.addListener(handleContextMenuClick); diff --git a/apps/browser-extension/src/background/protocol.ts b/apps/browser-extension/src/background/protocol.ts new file mode 100644 index 00000000..f4bcbcd8 --- /dev/null +++ b/apps/browser-extension/src/background/protocol.ts @@ -0,0 +1 @@ +export const NEW_BOOKMARK_REQUEST_KEY_NAME = "hoarder-new-bookmark"; diff --git a/package.json b/package.json index cd1b1906..cdf3ffc1 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "eslint": "^8.57.0", "husky": "^9.0.11", "install": "^0.13.0", - "prettier": "3.2.5", + "prettier": "^3.2.5", "turbo": "^2.0.9" }, "prettier": "@hoarder/prettier-config", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7bd2f50..140ee5e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,7 +35,7 @@ importers: specifier: ^0.13.0 version: 0.13.0 prettier: - specifier: 3.2.5 + specifier: ^3.2.5 version: 3.2.5 turbo: specifier: ^2.0.9 @@ -43,6 +43,9 @@ importers: apps/browser-extension: dependencies: + '@hoarder/shared': + specifier: workspace:^0.1.0 + version: link:../../packages/shared '@hoarder/shared-react': specifier: workspace:^0.1.0 version: link:../../packages/shared-react @@ -9364,6 +9367,7 @@ packages: osenv@0.1.5: resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==} + deprecated: This package is no longer supported. p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} @@ -10719,18 +10723,22 @@ packages: rimraf@2.4.5: resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup-plugin-terser@7.0.2: @@ -13088,7 +13096,7 @@ snapshots: '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.0)': @@ -13262,7 +13270,7 @@ snapshots: '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.0)': @@ -13286,7 +13294,7 @@ snapshots: '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.0)': @@ -13310,7 +13318,7 @@ snapshots: '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.0)': @@ -13739,7 +13747,7 @@ snapshots: dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.24.0)': @@ -13884,7 +13892,7 @@ snapshots: '@babel/core': 7.23.9 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-create-class-features-plugin': 7.24.0(@babel/core@7.23.9) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.9) dev: false @@ -13985,7 +13993,7 @@ snapshots: dependencies: '@babel/core': 7.23.9 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-transform-react-pure-annotations@7.23.3(@babel/core@7.24.0)': @@ -14076,7 +14084,7 @@ snapshots: '@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.24.0)': @@ -14157,7 +14165,7 @@ snapshots: dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.0 dev: false '@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.24.0)':