mirror of
https://github.com/nkcmr/HyperTab.git
synced 2026-04-29 12:17:40 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
510b98edcc | ||
|
|
78dbf9eaab | ||
|
|
b826ce9c63 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
dist
|
||||
node_modules
|
||||
*.map
|
||||
.release-*/
|
||||
HyperTab-*.zip
|
||||
|
||||
24
_locales/de/messages.json
Normal file
24
_locales/de/messages.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
"manifest_version": 2,
|
||||
"default_locale": "en",
|
||||
|
||||
"name": "HyperTab",
|
||||
"description": "Quick tabs search and switch",
|
||||
"version": "0.1",
|
||||
"name": "__MSG_extension_name__",
|
||||
"description": "__MSG_extension_description__",
|
||||
"version": "@@replaced_by_package_sh",
|
||||
|
||||
"icons": {
|
||||
"48": "images/icon-48.png",
|
||||
|
||||
19
package.json
19
package.json
@@ -5,16 +5,23 @@
|
||||
"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",
|
||||
"build": "npm run clean && run-p build:**",
|
||||
"build:bg": "esbuild --bundle ./src/background/main.ts --outdir=dist/bg --minify",
|
||||
"build:popup": "esbuild --bundle ./src/popup/main.tsx --outdir=dist/popup --minify"
|
||||
"build:bg": "env NODE_ENV=production esbuild --bundle ./src/background/main.ts --outdir=dist/bg --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",
|
||||
"esbuild": "^0.19.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": "^5.2.2",
|
||||
"@types/lodash.uniq": "^4.5.9",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
|
||||
27
package.sh
Executable file
27
package.sh
Executable 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" ./*
|
||||
)
|
||||
31
popup.css
31
popup.css
@@ -140,6 +140,33 @@ body {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* Dark Mode styles go here. */
|
||||
.ht-tab:hover {
|
||||
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
privacy-policy.txt
Normal file
33
privacy-policy.txt
Normal 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.
|
||||
@@ -11,6 +11,13 @@ import ReactDOM from "react-dom/client";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import "./scrollIntoViewIfNeededPolyfill";
|
||||
|
||||
function t(
|
||||
messageName: string,
|
||||
substitutions?: string | string[] | undefined
|
||||
): string {
|
||||
return browser.i18n.getMessage(messageName, substitutions);
|
||||
}
|
||||
|
||||
function hostname(url: string): string {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
@@ -33,7 +40,7 @@ function useBackgroundPage(): BackgroundPage {
|
||||
resolve: (value: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: any) => void;
|
||||
};
|
||||
const waiter = new Map<number, PromiseFinishers<unknown>>();
|
||||
const waiter = useRef(new Map<number, PromiseFinishers<unknown>>());
|
||||
useEffect(() => {
|
||||
const msgListener: Parameters<
|
||||
typeof port.current.onMessage.addListener
|
||||
@@ -41,11 +48,11 @@ function useBackgroundPage(): BackgroundPage {
|
||||
if (!("id" in message) || typeof message.id !== "number") {
|
||||
return;
|
||||
}
|
||||
const promfinishers = waiter.get(message.id);
|
||||
const promfinishers = waiter.current.get(message.id);
|
||||
if (!promfinishers) {
|
||||
return;
|
||||
}
|
||||
waiter.delete(message.id);
|
||||
waiter.current.delete(message.id);
|
||||
if (message.error) {
|
||||
promfinishers.reject(message.error);
|
||||
} else {
|
||||
@@ -64,14 +71,12 @@ function useBackgroundPage(): BackgroundPage {
|
||||
listTabs() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = ++msgId.current;
|
||||
waiter.set(id, {
|
||||
waiter.current.set(id, {
|
||||
reject,
|
||||
resolve(value) {
|
||||
console.timeEnd(`bgpage:rpc:listTabs:${id}`);
|
||||
resolve(value as chrome.tabs.Tab[]);
|
||||
},
|
||||
});
|
||||
console.time(`bgpage:rpc:listTabs:${id}`);
|
||||
port.current.postMessage({ rpc: "listTabs", id });
|
||||
});
|
||||
},
|
||||
@@ -86,11 +91,14 @@ const focusTab = (tabId: number, windowId: number): void => {
|
||||
|
||||
const HighlightMatches: FunctionComponent<{
|
||||
text: string;
|
||||
match?: FuseResultMatch;
|
||||
match?: Omit<FuseResultMatch, "key">;
|
||||
}> = ({ text, match }) => {
|
||||
if (!match) {
|
||||
return <>{text}</>;
|
||||
}
|
||||
if (text.toLowerCase().includes("spec.matrix")) {
|
||||
console.log({ text, match });
|
||||
}
|
||||
const parts: JSX.Element[] = [];
|
||||
const indicies = structuredClone(match.indices) as RangeTuple[];
|
||||
let currentPart = "";
|
||||
@@ -158,7 +166,29 @@ function faviconURL(t: chrome.tabs.Tab, size: number): string | undefined {
|
||||
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 darkMode = useDarkMode();
|
||||
const [tabSelector, setTabSelector] = useState(0);
|
||||
const [tabs, setTabs] = useState<chrome.tabs.Tab[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
@@ -179,7 +209,6 @@ const Popup: FunctionComponent = () => {
|
||||
if (tabs.length === 0) {
|
||||
return;
|
||||
}
|
||||
console.log({ tabs });
|
||||
}, [tabs]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -246,7 +275,6 @@ const Popup: FunctionComponent = () => {
|
||||
|
||||
const bgpage = useBackgroundPage();
|
||||
useEffect(() => {
|
||||
console.time("queryTabs");
|
||||
bgpage
|
||||
.listTabs()
|
||||
.then((returnedTabs) => {
|
||||
@@ -255,9 +283,7 @@ const Popup: FunctionComponent = () => {
|
||||
}
|
||||
setTabs(returnedTabs);
|
||||
})
|
||||
.finally(() => {
|
||||
console.timeEnd("queryTabs");
|
||||
});
|
||||
.finally(() => {});
|
||||
}, []);
|
||||
|
||||
const selectedTabEle = useRef<HTMLDivElement>(null);
|
||||
@@ -273,10 +299,29 @@ const Popup: FunctionComponent = () => {
|
||||
|
||||
return (
|
||||
<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
|
||||
className="ht-search-input"
|
||||
type="text"
|
||||
autoFocus
|
||||
placeholder={t("ui_search_tabs")}
|
||||
value={searchQuery}
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -301,6 +346,9 @@ const Popup: FunctionComponent = () => {
|
||||
/>
|
||||
</div>
|
||||
<hr style={{ opacity: "0.3", marginTop: "0px" }} />
|
||||
<div style={{ padding: "1em", fontWeight: "bold" }}>
|
||||
{t("ui_open_tabs", `${tabs.length}`)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
maxHeight: "500px",
|
||||
@@ -332,7 +380,13 @@ const Popup: FunctionComponent = () => {
|
||||
ref={i === tabSelector ? selectedTabEle : undefined}
|
||||
style={{
|
||||
padding: "10px",
|
||||
backgroundColor: i === selectedTab ? "#e9e9e9" : undefined,
|
||||
cursor: "pointer",
|
||||
backgroundColor:
|
||||
i === selectedTab
|
||||
? darkMode
|
||||
? "#535353"
|
||||
: "#e9e9e9"
|
||||
: undefined,
|
||||
|
||||
// favicon support
|
||||
...(enableFavicons
|
||||
@@ -344,19 +398,24 @@ const Popup: FunctionComponent = () => {
|
||||
}}
|
||||
>
|
||||
{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} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="ht-tab-right" style={{ width: "93%" }}>
|
||||
<div
|
||||
className="ht-tab-title"
|
||||
style={{
|
||||
color: darkMode ? "#e9e9e9" : "#2b2b2b",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
fontSize: "1.1em",
|
||||
marginBottom: "6px",
|
||||
width: "98%",
|
||||
}}
|
||||
>
|
||||
{
|
||||
|
||||
14
tag.sh
Executable file
14
tag.sh
Executable 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"
|
||||
Reference in New Issue
Block a user