feat: project infrastructure and MV3 extension setup

TypeScript + esbuild build system, Manifest V3 with background script,
content script for iframe overlay injection, and typed message contracts
between extension contexts.

Assisted-by: Claude Code
Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
This commit is contained in:
2026-04-20 17:14:35 +05:30
parent 912f5c9ca8
commit eae5309843
9 changed files with 962 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import * as esbuild from "esbuild";
const watch = process.argv.includes("--watch");
const options = {
entryPoints: [
"src/background.ts",
"src/content.ts",
"src/sciezka.ts",
],
bundle: true,
outdir: "dist",
format: "iife",
target: "es2020",
sourcemap: watch,
};
if (watch) {
const ctx = await esbuild.context(options);
await ctx.watch();
console.log("Watching for changes...");
} else {
await esbuild.build(options);
console.log("Build complete.");
}
+44
View File
@@ -0,0 +1,44 @@
{
"manifest_version": 3,
"name": "Sciezka",
"version": "0.1.0",
"description": "A browser extension to fuzzy search tabs, history and bookmarks.",
"icons": {
"48": "icons/border-48.png"
},
"permissions": [
"tabs",
"history",
"bookmarks",
"sessions",
"storage"
],
"background": {
"scripts": ["dist/background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["dist/content.js"],
"run_at": "document_idle"
}
],
"commands": {
"toggle-sciezka": {
"suggested_key": {
"default": "Alt+K"
},
"description": "Toggle Sciezka search"
}
},
"options_ui": {
"page": "options/options.html",
"open_in_tab": true
},
"web_accessible_resources": [
{
"resources": ["sciezka.html", "sciezka.css", "dist/sciezka.js"],
"matches": ["<all_urls>"]
}
]
}
+486
View File
@@ -0,0 +1,486 @@
{
"name": "sciezka",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sciezka",
"version": "0.1.0",
"devDependencies": {
"esbuild": "^0.25.0",
"typescript": "^5.8.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "sciezka",
"version": "0.1.0",
"private": true,
"description": "A browser extension for fuzzy searching tabs, history and bookmarks.",
"scripts": {
"build": "node build.mjs",
"watch": "node build.mjs --watch"
},
"devDependencies": {
"esbuild": "^0.25.0",
"typescript": "^5.8.0"
}
}
+141
View File
@@ -0,0 +1,141 @@
import type { Message, SearchRequest, ActionRequest, SearchItem } from "./types";
async function getOpenTabs(): Promise<SearchItem[]> {
const tabs = await chrome.tabs.query({});
return tabs.map((tab) => ({
id: `tab-${tab.id}`,
title: tab.title ?? "",
url: tab.url ?? "",
type: "tabs" as const,
favIconUrl: tab.favIconUrl,
}));
}
async function getHistory(query: string): Promise<SearchItem[]> {
const results = await chrome.history.search({
text: query,
maxResults: 50,
startTime: 0,
});
return results.map((item) => ({
id: `history-${item.id}`,
title: item.title ?? "",
url: item.url ?? "",
type: "history" as const,
}));
}
async function getBookmarks(query: string): Promise<SearchItem[]> {
const results = await chrome.bookmarks.search(query || " ");
return results
.filter((b) => b.url)
.map((item) => ({
id: `bookmark-${item.id}`,
title: item.title ?? "",
url: item.url!,
type: "bookmarks" as const,
}));
}
async function getRecentlyClosed(): Promise<SearchItem[]> {
const sessions = await chrome.sessions.getRecentlyClosed({ maxResults: 25 });
return sessions
.filter((s) => s.tab)
.map((session) => ({
id: `closed-${session.tab!.sessionId}`,
title: session.tab!.title ?? "",
url: session.tab!.url ?? "",
type: "closed" as const,
favIconUrl: session.tab!.favIconUrl,
}));
}
async function handleSearch(request: SearchRequest): Promise<SearchItem[]> {
const { query, mode } = request;
switch (mode) {
case "tabs":
return getOpenTabs();
case "history":
return getHistory(query);
case "bookmarks":
return getBookmarks(query);
case "closed":
return getRecentlyClosed();
case "all": {
const [tabs, history, bookmarks, closed] = await Promise.all([
getOpenTabs(),
getHistory(query),
getBookmarks(query),
getRecentlyClosed(),
]);
return [...tabs, ...history, ...bookmarks, ...closed];
}
}
}
async function handleAction(request: ActionRequest): Promise<void> {
const rawId = request.id.replace(/^(tab|history|bookmark|closed)-/, "");
switch (request.action) {
case "switch": {
const tabId = parseInt(rawId, 10);
await chrome.tabs.update(tabId, { active: true });
const tab = await chrome.tabs.get(tabId);
if (tab.windowId != null) {
await chrome.windows.update(tab.windowId, { focused: true });
}
break;
}
case "open": {
if (request.newTab) {
await chrome.tabs.create({ url: request.id });
} else {
const [activeTab] = await chrome.tabs.query({
active: true,
currentWindow: true,
});
if (activeTab?.id != null) {
await chrome.tabs.update(activeTab.id, { url: request.id });
}
}
break;
}
case "close": {
const tabId = parseInt(rawId, 10);
await chrome.tabs.remove(tabId);
break;
}
case "restore": {
await chrome.sessions.restore(rawId);
break;
}
}
}
chrome.commands.onCommand.addListener(async (command) => {
if (command === "toggle-sciezka") {
const [tab] = await chrome.tabs.query({
active: true,
currentWindow: true,
});
if (tab?.id != null) {
chrome.tabs.sendMessage(tab.id, { type: "toggle" } satisfies Message);
}
}
});
chrome.runtime.onMessage.addListener(
(msg: unknown, _sender, sendResponse) => {
const message = msg as Message;
if (message.type === "search") {
handleSearch(message).then((items) => {
sendResponse({ type: "searchResults", results: items });
});
return true;
}
if (message.type === "action") {
handleAction(message).then(() => sendResponse({ ok: true }));
return true;
}
}
);
+78
View File
@@ -0,0 +1,78 @@
declare namespace chrome {
namespace tabs {
interface Tab {
id?: number;
windowId?: number;
title?: string;
url?: string;
favIconUrl?: string;
active?: boolean;
}
function query(queryInfo: Record<string, unknown>): Promise<Tab[]>;
function get(tabId: number): Promise<Tab>;
function create(createProperties: { url?: string; active?: boolean }): Promise<Tab>;
function update(tabId: number, updateProperties: { active?: boolean; url?: string }): Promise<Tab>;
function remove(tabId: number | number[]): Promise<void>;
function sendMessage(tabId: number, message: unknown): void;
}
namespace windows {
function update(windowId: number, updateInfo: { focused?: boolean }): Promise<void>;
}
namespace history {
interface HistoryItem {
id: string;
title?: string;
url?: string;
lastVisitTime?: number;
}
function search(query: { text: string; maxResults?: number; startTime?: number }): Promise<HistoryItem[]>;
}
namespace bookmarks {
interface BookmarkTreeNode {
id: string;
title: string;
url?: string;
children?: BookmarkTreeNode[];
}
function search(query: string): Promise<BookmarkTreeNode[]>;
}
namespace sessions {
interface Session {
tab?: tabs.Tab & { sessionId?: string };
window?: { sessionId?: string };
}
function getRecentlyClosed(filter?: { maxResults?: number }): Promise<Session[]>;
function restore(sessionId: string): Promise<Session>;
}
namespace commands {
const onCommand: {
addListener(callback: (command: string) => void): void;
};
}
namespace runtime {
function getURL(path: string): string;
const onMessage: {
addListener(
callback: (
message: unknown,
sender: { tab?: tabs.Tab; id?: string },
sendResponse: (response?: unknown) => void
) => boolean | void
): void;
};
function sendMessage(message: unknown, responseCallback?: (response: unknown) => void): void;
}
namespace storage {
const sync: {
get(keys: string | string[]): Promise<Record<string, unknown>>;
set(items: Record<string, unknown>): Promise<void>;
};
}
}
+101
View File
@@ -0,0 +1,101 @@
import type { Message } from "./types";
let overlay: HTMLDivElement | null = null;
let iframe: HTMLIFrameElement | null = null;
function createOverlay(): void {
overlay = document.createElement("div");
overlay.id = "sciezka-overlay";
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0);
z-index: 2147483647;
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 12vh;
transition: background 0.15s ease;
`;
iframe = document.createElement("iframe");
iframe.src = chrome.runtime.getURL("sciezka.html");
iframe.style.cssText = `
width: 620px;
height: 60px;
border: none;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
background: transparent;
opacity: 0;
transform: translateY(-8px);
transition: opacity 0.15s ease, transform 0.15s ease, height 0.1s ease;
`;
overlay.appendChild(iframe);
document.body.appendChild(overlay);
requestAnimationFrame(() => {
overlay!.style.background = "rgba(0, 0, 0, 0.4)";
iframe!.style.opacity = "1";
iframe!.style.transform = "translateY(0)";
});
overlay.addEventListener("click", (e) => {
if (e.target === overlay) {
removeOverlay();
}
});
}
function removeOverlay(): void {
if (overlay) {
overlay.remove();
overlay = null;
iframe = null;
}
}
function toggle(): void {
if (overlay) {
removeOverlay();
} else {
createOverlay();
}
}
chrome.runtime.onMessage.addListener((msg: unknown) => {
const message = msg as Message;
if (message.type === "toggle") {
toggle();
}
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && overlay) {
removeOverlay();
}
});
window.addEventListener("message", (event) => {
if (event.source !== iframe?.contentWindow) return;
const data = event.data as Message;
if (data.type === "closeSaka") {
removeOverlay();
return;
}
if (data.type === "resize") {
if (iframe) {
iframe.style.height = `${(data as { height: number }).height}px`;
}
return;
}
if (data.type === "search" || data.type === "action") {
chrome.runtime.sendMessage(data, (response) => {
iframe?.contentWindow?.postMessage(response, "*");
});
}
});
+56
View File
@@ -0,0 +1,56 @@
export type SearchMode = "tabs" | "history" | "bookmarks" | "closed" | "all";
export type SearchMethod = "fuzzy" | "fulltext" | "prefix";
export interface SearchItem {
id: string;
title: string;
url: string;
type: SearchMode;
favIconUrl?: string;
}
export interface SearchResult {
item: SearchItem;
score: number;
positions: number[];
}
export interface SearchRequest {
type: "search";
query: string;
mode: SearchMode;
method: SearchMethod;
}
export interface SearchResponse {
type: "searchResults";
results: SearchResult[];
}
export interface ActionRequest {
type: "action";
action: "switch" | "open" | "close" | "restore";
id: string;
newTab?: boolean;
}
export interface ToggleMessage {
type: "toggle";
}
export interface CloseMessage {
type: "closeSaka";
}
export interface ResizeMessage {
type: "resize";
height: number;
}
export type Message =
| SearchRequest
| SearchResponse
| ActionRequest
| ToggleMessage
| CloseMessage
| ResizeMessage;
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"isolatedModules": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"lib": ["ES2020", "DOM", "DOM.Iterable"]
},
"include": ["src/**/*.ts"]
}