4 Commits

Author SHA1 Message Date
nick comer d390feeab7 whoops missed en 2024-05-02 20:06:20 -04:00
nick comer 510b98edcc mess with i18n a little bit 2024-05-02 20:03:18 -04:00
nick comer 78dbf9eaab release process 2024-05-02 19:25:26 -04:00
nick comer b826ce9c63 dark mode + a bit more polish 2024-05-01 21:54:52 -04:00
10 changed files with 241 additions and 34 deletions
+2
View File
@@ -1,3 +1,5 @@
dist dist
node_modules node_modules
*.map *.map
.release-*/
HyperTab-*.zip
+24
View File
@@ -0,0 +1,24 @@
{
"extension_name": {
"message": "HyperTab",
"description": "The display name for the extension."
},
"extension_description": {
"message": "Schnelle Suche und Wechsel von Registerkarten",
"description": "Description of what the extension does."
},
"ui_open_tabs": {
"message": "Öffnen Sie Registerkarten ($COUNT$)",
"description": "Text denoting the section in the UI of open tabs",
"placeholders": {
"count": {
"content": "$1",
"example": "82"
}
}
},
"ui_search_tabs": {
"message": "Suchregisterkarten",
"description": "Placeholder text in the tab search box"
}
}
+15 -1
View File
@@ -4,7 +4,21 @@
"description": "The display name for the extension." "description": "The display name for the extension."
}, },
"extension_description": { "extension_description": {
"message": "Quick Tab Search and Switch for Safari", "message": "Quick Tab Search and Switch",
"description": "Description of what the extension does." "description": "Description of what the extension does."
},
"ui_open_tabs": {
"message": "Open Tabs ($COUNT$)",
"description": "Text denoting the section in the UI of open tabs",
"placeholders": {
"count": {
"content": "$1",
"example": "82"
}
}
},
"ui_search_tabs": {
"message": "Search Tabs",
"description": "Placeholder text in the tab search box"
} }
} }
+3 -3
View File
@@ -2,9 +2,9 @@
"manifest_version": 2, "manifest_version": 2,
"default_locale": "en", "default_locale": "en",
"name": "HyperTab", "name": "__MSG_extension_name__",
"description": "Quick tabs search and switch", "description": "__MSG_extension_description__",
"version": "0.1", "version": "@@replaced_by_package_sh",
"icons": { "icons": {
"48": "images/icon-48.png", "48": "images/icon-48.png",
+13 -6
View File
@@ -5,16 +5,23 @@
"dev:bg": "esbuild --bundle ./src/background/main.ts --outdir=dist/bg --sourcemap --watch", "dev:bg": "esbuild --bundle ./src/background/main.ts --outdir=dist/bg --sourcemap --watch",
"dev:popup": "esbuild --bundle ./src/popup/main.tsx --outdir=dist/popup --sourcemap --watch", "dev:popup": "esbuild --bundle ./src/popup/main.tsx --outdir=dist/popup --sourcemap --watch",
"build": "npm run clean && run-p build:**", "build": "npm run clean && run-p build:**",
"build:bg": "esbuild --bundle ./src/background/main.ts --outdir=dist/bg --minify", "build:bg": "env NODE_ENV=production esbuild --bundle ./src/background/main.ts --outdir=dist/bg --minify",
"build:popup": "esbuild --bundle ./src/popup/main.tsx --outdir=dist/popup --minify" "build:popup": "env NODE_ENV=production esbuild --bundle ./src/popup/main.tsx --outdir=dist/popup --minify",
"release": "npm run build && ./package.sh"
}, },
"devDependencies": { "releaseArtifacts": [
"popup.css",
"popup.html",
"manifest.json",
"images",
"dist",
"_locales"
],
"dependencies": {
"@types/chrome": "^0.0.251", "@types/chrome": "^0.0.251",
"esbuild": "^0.19.5", "esbuild": "^0.19.5",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"typescript": "^5.2.2" "typescript": "^5.2.2",
},
"dependencies": {
"@types/lodash.uniq": "^4.5.9", "@types/lodash.uniq": "^4.5.9",
"@types/react": "^18.2.37", "@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15", "@types/react-dom": "^18.2.15",
Executable
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
set -euxo pipefail
release_version="$(git describe --tags)"
release_folder=".release-HyperTab-$release_version"
rm -rf .release-* HyperTab-*.zip
mkdir "$release_folder"
# shellcheck disable=SC2046
cp -rv $(jq -r '.releaseArtifacts[]' package.json) "$release_folder/"
jq \
--arg newVersion "$release_version" \
'.version = $newVersion' \
"$release_folder/manifest.json" > \
"$release_folder/manifest.json.tmp"
rm -vf "$release_folder/manifest.json"
mv -v "$release_folder/manifest.json.tmp" \
"$release_folder/manifest.json"
(
cd "$release_folder" &&
zip -r9 "../HyperTab-$release_version.zip" ./*
)
+29 -2
View File
@@ -140,6 +140,33 @@ body {
background-color: white; background-color: white;
} }
@media (prefers-color-scheme: dark) { .ht-tab:hover {
/* Dark Mode styles go here. */ background-color: #efefef;
}
.ht-tab-location {
color: #e9e9e9;
}
@media (prefers-color-scheme: dark) {
html {
background-color: black;
}
body {
background-color: #2b2b2b;
color: #f2f2f2;
}
.ht-search-wrapper .ht-search-input {
background-color: #2b2b2b;
}
.ht-tab-location {
color: #e9e9e9;
}
.ht-tab:hover {
background-color: #4e4e4e;
}
} }
+33
View File
@@ -0,0 +1,33 @@
Privacy Policy for HyperTab
This Privacy Policy explains how we handle your information when you use this
browser extension, HyperTab (the "Extension").
Information We Collect
We do not collect any personal information or user data when you use the
Extension. All data and information processed by the Extension remain securely
within your own browser and are not transmitted to our servers or any third
parties.
Cookies
The Extension does not use cookies or similar tracking technologies.
Third-Party Services
The Extension does not integrate with any third-party services that would
require sharing user data.
Security
We take the security of your information seriously. Since no user data is
collected or transmitted by the Extension, there are no risks associated with
data breaches or unauthorized access to user data through the Extension.
Changes to This Privacy Policy
We reserve the right to update or change our Privacy Policy at any time. Any
changes will be posted on this page with a revised effective date. Your
continued use of the Extension after any such changes constitutes your
acceptance of the new Privacy Policy.
+75 -16
View File
@@ -11,6 +11,13 @@ import ReactDOM from "react-dom/client";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
import "./scrollIntoViewIfNeededPolyfill"; import "./scrollIntoViewIfNeededPolyfill";
function t(
messageName: string,
substitutions?: string | string[] | undefined
): string {
return browser.i18n.getMessage(messageName, substitutions);
}
function hostname(url: string): string { function hostname(url: string): string {
try { try {
const u = new URL(url); const u = new URL(url);
@@ -33,7 +40,7 @@ function useBackgroundPage(): BackgroundPage {
resolve: (value: T | PromiseLike<T>) => void; resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void; reject: (reason?: any) => void;
}; };
const waiter = new Map<number, PromiseFinishers<unknown>>(); const waiter = useRef(new Map<number, PromiseFinishers<unknown>>());
useEffect(() => { useEffect(() => {
const msgListener: Parameters< const msgListener: Parameters<
typeof port.current.onMessage.addListener typeof port.current.onMessage.addListener
@@ -41,11 +48,11 @@ function useBackgroundPage(): BackgroundPage {
if (!("id" in message) || typeof message.id !== "number") { if (!("id" in message) || typeof message.id !== "number") {
return; return;
} }
const promfinishers = waiter.get(message.id); const promfinishers = waiter.current.get(message.id);
if (!promfinishers) { if (!promfinishers) {
return; return;
} }
waiter.delete(message.id); waiter.current.delete(message.id);
if (message.error) { if (message.error) {
promfinishers.reject(message.error); promfinishers.reject(message.error);
} else { } else {
@@ -64,14 +71,12 @@ function useBackgroundPage(): BackgroundPage {
listTabs() { listTabs() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = ++msgId.current; const id = ++msgId.current;
waiter.set(id, { waiter.current.set(id, {
reject, reject,
resolve(value) { resolve(value) {
console.timeEnd(`bgpage:rpc:listTabs:${id}`);
resolve(value as chrome.tabs.Tab[]); resolve(value as chrome.tabs.Tab[]);
}, },
}); });
console.time(`bgpage:rpc:listTabs:${id}`);
port.current.postMessage({ rpc: "listTabs", id }); port.current.postMessage({ rpc: "listTabs", id });
}); });
}, },
@@ -86,11 +91,14 @@ const focusTab = (tabId: number, windowId: number): void => {
const HighlightMatches: FunctionComponent<{ const HighlightMatches: FunctionComponent<{
text: string; text: string;
match?: FuseResultMatch; match?: Omit<FuseResultMatch, "key">;
}> = ({ text, match }) => { }> = ({ text, match }) => {
if (!match) { if (!match) {
return <>{text}</>; return <>{text}</>;
} }
if (text.toLowerCase().includes("spec.matrix")) {
console.log({ text, match });
}
const parts: JSX.Element[] = []; const parts: JSX.Element[] = [];
const indicies = structuredClone(match.indices) as RangeTuple[]; const indicies = structuredClone(match.indices) as RangeTuple[];
let currentPart = ""; let currentPart = "";
@@ -158,7 +166,29 @@ function faviconURL(t: chrome.tabs.Tab, size: number): string | undefined {
return url.toString(); return url.toString();
} }
const prefersDarkMode = (): boolean => {
return window.matchMedia("(prefers-color-scheme: dark)").matches;
};
const useDarkMode = (): boolean => {
const [dm, setdm] = useState(() => {
return prefersDarkMode();
});
useEffect(() => {
const mql = window.matchMedia("(prefers-color-scheme: dark)");
const onChange = (ev: MediaQueryListEvent) => {
setdm(ev.matches);
};
mql.addEventListener("change", onChange);
return () => {
mql.removeEventListener("change", onChange);
};
}, []);
return dm;
};
const Popup: FunctionComponent = () => { const Popup: FunctionComponent = () => {
const darkMode = useDarkMode();
const [tabSelector, setTabSelector] = useState(0); const [tabSelector, setTabSelector] = useState(0);
const [tabs, setTabs] = useState<chrome.tabs.Tab[]>([]); const [tabs, setTabs] = useState<chrome.tabs.Tab[]>([]);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
@@ -179,7 +209,6 @@ const Popup: FunctionComponent = () => {
if (tabs.length === 0) { if (tabs.length === 0) {
return; return;
} }
console.log({ tabs });
}, [tabs]); }, [tabs]);
useEffect(() => { useEffect(() => {
@@ -246,7 +275,6 @@ const Popup: FunctionComponent = () => {
const bgpage = useBackgroundPage(); const bgpage = useBackgroundPage();
useEffect(() => { useEffect(() => {
console.time("queryTabs");
bgpage bgpage
.listTabs() .listTabs()
.then((returnedTabs) => { .then((returnedTabs) => {
@@ -255,9 +283,7 @@ const Popup: FunctionComponent = () => {
} }
setTabs(returnedTabs); setTabs(returnedTabs);
}) })
.finally(() => { .finally(() => {});
console.timeEnd("queryTabs");
});
}, []); }, []);
const selectedTabEle = useRef<HTMLDivElement>(null); const selectedTabEle = useRef<HTMLDivElement>(null);
@@ -273,10 +299,29 @@ const Popup: FunctionComponent = () => {
return ( return (
<div> <div>
<div style={{ padding: "1em" }}> <div
style={{ padding: "1em", display: "flex" }}
className="ht-search-wrapper"
>
<div style={{ marginRight: "1em" }}>
<svg
style={{ scale: "0.85" }}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill={darkMode ? "#e9e9e9" : "#636363"}
d="M23.809 21.646l-6.205-6.205c1.167-1.605 1.857-3.579 1.857-5.711 0-5.365-4.365-9.73-9.731-9.73-5.365 0-9.73 4.365-9.73 9.73 0 5.366 4.365 9.73 9.73 9.73 2.034 0 3.923-.627 5.487-1.698l6.238 6.238 2.354-2.354zm-20.955-11.916c0-3.792 3.085-6.877 6.877-6.877s6.877 3.085 6.877 6.877-3.085 6.877-6.877 6.877c-3.793 0-6.877-3.085-6.877-6.877z"
/>
</svg>
</div>
<input <input
className="ht-search-input"
type="text" type="text"
autoFocus autoFocus
placeholder={t("ui_search_tabs")}
value={searchQuery} value={searchQuery}
style={{ style={{
width: "100%", width: "100%",
@@ -301,6 +346,9 @@ const Popup: FunctionComponent = () => {
/> />
</div> </div>
<hr style={{ opacity: "0.3", marginTop: "0px" }} /> <hr style={{ opacity: "0.3", marginTop: "0px" }} />
<div style={{ padding: "1em", fontWeight: "bold" }}>
{t("ui_open_tabs", `${tabs.length}`)}
</div>
<div <div
style={{ style={{
maxHeight: "500px", maxHeight: "500px",
@@ -332,7 +380,13 @@ const Popup: FunctionComponent = () => {
ref={i === tabSelector ? selectedTabEle : undefined} ref={i === tabSelector ? selectedTabEle : undefined}
style={{ style={{
padding: "10px", padding: "10px",
backgroundColor: i === selectedTab ? "#e9e9e9" : undefined, cursor: "pointer",
backgroundColor:
i === selectedTab
? darkMode
? "#535353"
: "#e9e9e9"
: undefined,
// favicon support // favicon support
...(enableFavicons ...(enableFavicons
@@ -344,19 +398,24 @@ const Popup: FunctionComponent = () => {
}} }}
> >
{favicURL && ( {favicURL && (
<div className="ht-tab-favicon" style={{ marginRight: "1em" }}> <div
className="ht-tab-favicon"
style={{ marginRight: "1em", padding: ".5em" }}
>
<img width={16} height={16} src={favicURL} /> <img width={16} height={16} src={favicURL} />
</div> </div>
)} )}
<div> <div className="ht-tab-right" style={{ width: "93%" }}>
<div <div
className="ht-tab-title" className="ht-tab-title"
style={{ style={{
color: darkMode ? "#e9e9e9" : "#2b2b2b",
whiteSpace: "nowrap", whiteSpace: "nowrap",
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
fontSize: "1.1em", fontSize: "1.1em",
marginBottom: "6px", marginBottom: "6px",
width: "98%",
}} }}
> >
{ {
Executable
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
set -euo pipefail
[ -n "${TRACE:-}" ] && set -x
current_tag="$(git describe --tags --abbrev=0)"
next_tag="$(datever increment "$current_tag")"
if [[ "$(git describe --exact-match --tags 2>/dev/null)" != "" ]]; then
echo "Current commit is already tagged; quiting..."
exit 1
fi
git tag "$next_tag"