feat(capture): add minimal browser extension
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
# Jobjakt Quick Capture
|
||||||
|
|
||||||
|
The Manifest V3 extension opens the current page or selected link in Jobjakt's existing reviewed import flow. It does not scrape page content, store credentials, or submit an application automatically.
|
||||||
|
|
||||||
|
## Install locally
|
||||||
|
|
||||||
|
1. Open `chrome://extensions` (or the equivalent extensions page in Edge).
|
||||||
|
2. Enable developer mode.
|
||||||
|
3. Choose **Load unpacked** and select this `browser-extension` folder.
|
||||||
|
4. Open a vacancy and click the Jobjakt extension, or use **Save vacancy to Jobjakt** from the page/link context menu.
|
||||||
|
|
||||||
|
The Jobjakt add-job dialog opens with the vacancy URL and runs the same server-side preview, validation, duplicate checking, and user review used by normal URL imports.
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node --test browser-extension/tests/*.test.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
No build or package installation is required. Zip the folder contents for store submission after adding store artwork and completing the browser-store privacy declarations.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { buildCaptureUrl } from "./capture-url.mjs";
|
||||||
|
|
||||||
|
const menuId = "save-vacancy-to-jobjakt";
|
||||||
|
|
||||||
|
function openCapture(pageUrl) {
|
||||||
|
const captureUrl = buildCaptureUrl(pageUrl);
|
||||||
|
if (captureUrl) chrome.tabs.create({ url: captureUrl });
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
chrome.contextMenus.removeAll(() => {
|
||||||
|
chrome.contextMenus.create({
|
||||||
|
id: menuId,
|
||||||
|
title: "Save vacancy to Jobjakt",
|
||||||
|
contexts: ["page", "link"]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.action.onClicked.addListener((tab) => openCapture(tab.url));
|
||||||
|
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
|
if (info.menuItemId === menuId) openCapture(info.linkUrl || info.pageUrl || tab?.url);
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const JOBBJAKT_CAPTURE_ORIGIN = "https://jobs.cesnimda.uk";
|
||||||
|
|
||||||
|
export function buildCaptureUrl(pageUrl, origin = JOBBJAKT_CAPTURE_ORIGIN) {
|
||||||
|
let source;
|
||||||
|
try {
|
||||||
|
source = new URL(pageUrl);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (source.protocol !== "http:" && source.protocol !== "https:") return null;
|
||||||
|
|
||||||
|
const capture = new URL("/", origin);
|
||||||
|
capture.searchParams.set("add", source.href);
|
||||||
|
return capture.href;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Jobjakt Quick Capture",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Send the current vacancy to Jobjakt's reviewed import flow.",
|
||||||
|
"permissions": ["activeTab", "contextMenus"],
|
||||||
|
"action": {
|
||||||
|
"default_title": "Save vacancy to Jobjakt"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.mjs",
|
||||||
|
"type": "module"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { buildCaptureUrl } from "../capture-url.mjs";
|
||||||
|
|
||||||
|
test("builds the existing reviewed capture route", () => {
|
||||||
|
const result = new URL(buildCaptureUrl("https://example.test/jobs/42?from=search"));
|
||||||
|
assert.equal(result.origin, "https://jobs.cesnimda.uk");
|
||||||
|
assert.equal(result.pathname, "/");
|
||||||
|
assert.equal(result.searchParams.get("add"), "https://example.test/jobs/42?from=search");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects privileged and malformed URLs", () => {
|
||||||
|
assert.equal(buildCaptureUrl("chrome://settings"), null);
|
||||||
|
assert.equal(buildCaptureUrl("javascript:alert(1)"), null);
|
||||||
|
assert.equal(buildCaptureUrl("not a url"), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("supports a local application origin during development", () => {
|
||||||
|
assert.equal(
|
||||||
|
buildCaptureUrl("https://arbeidsplassen.nav.no/stillinger/stilling/abc", "http://localhost:3300"),
|
||||||
|
"http://localhost:3300/?add=https%3A%2F%2Farbeidsplassen.nav.no%2Fstillinger%2Fstilling%2Fabc"
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user