aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkamtschatka <simon.schatka@gmx.at>2024-08-18 20:52:51 +0200
committerGitHub <noreply@github.com>2024-08-18 21:52:51 +0300
commit0710147ab1a3274b517acefffdaa17ece7cdd744 (patch)
tree127ecb3521e4df8a2ee849dc9a466a8c8857e3e1
parentc7f9febf04541ac9b68a4cb6cb48251e41b51d05 (diff)
downloadkarakeep-0710147ab1a3274b517acefffdaa17ece7cdd744.tar.zst
[extension] Add context menu item in the browser extension. Fixes #155 (#278)
* Add context menu item in the browser extension #155 Added a context menu entry to add links directly to hoarder * Formalize protocol between extension and service worker, add support for text/images beside links * fix build --------- Co-authored-by: MohamedBassem <me@mbassem.com>
-rw-r--r--apps/browser-extension/package.json1
-rw-r--r--apps/browser-extension/src/SavePage.tsx50
-rw-r--r--apps/browser-extension/src/background/background.ts50
-rw-r--r--apps/browser-extension/src/background/protocol.ts1
-rw-r--r--package.json2
-rw-r--r--pnpm-lock.yaml28
6 files changed, 101 insertions, 31 deletions
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<ZNewBookmarkRequest | null> {
+ 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)':