Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9730377
feat: paginate disabled inline sites
hoangsvit Jul 23, 2026
81621a8
feat: add disabled-site filtering
hoangsvit Jul 23, 2026
e83862d
test: cover disabled-site filtering
hoangsvit Jul 23, 2026
beb45d8
chore: set extension version to 1.3.2
hoangsvit Jul 23, 2026
8149632
chore: apply extension changelog update
hoangsvit Jul 23, 2026
40fe714
chore: trigger extension update workflow
hoangsvit Jul 23, 2026
fbe503f
chore: rerun extension update workflow
hoangsvit Jul 23, 2026
b8ca5f5
chore: run extension update from pull request
hoangsvit Jul 23, 2026
2d2deff
chore: apply pending extension UI updates
hoangsvit Jul 23, 2026
6f8931e
fix: make extension update patch indentation-safe
hoangsvit Jul 23, 2026
99a5b32
feat: update changelog and disabled-sites UI
eplus-bot Jul 23, 2026
12f8122
ci: capture failing test output
hoangsvit Jul 23, 2026
ff92cf9
fix: reuse localized alias search label
hoangsvit Jul 23, 2026
928d0e6
ci: restore standard test workflow
hoangsvit Jul 23, 2026
09ebc7f
fix: clarify disabled-site search states
hoangsvit Jul 23, 2026
32c3f0b
refactor: centralize application version metadata
hoangsvit Jul 23, 2026
ade38e7
test: prevent user-facing version hardcoding
hoangsvit Jul 23, 2026
cf60a11
chore: apply automatic version source update
hoangsvit Jul 23, 2026
0c35b8a
refactor: source displayed version from package metadata
eplus-bot Jul 23, 2026
2294c69
chore: capture version test diagnostics
hoangsvit Jul 23, 2026
71dfa36
fix: resolve version test paths from repository root
hoangsvit Jul 23, 2026
fd551dd
chore: restore standard CI workflow
hoangsvit Jul 23, 2026
3874758
docs: make issue version placeholder release-neutral
hoangsvit Jul 23, 2026
e435ec1
feat: resolve web version from latest GitHub release
hoangsvit Jul 23, 2026
588cc18
feat: allow web builds to inject release version
hoangsvit Jul 23, 2026
6554efc
feat: inject latest GitHub release into web build
hoangsvit Jul 23, 2026
76c17bb
test: cover GitHub release version resolution
hoangsvit Jul 23, 2026
83838b1
ci: authenticate GitHub release lookup for Pages
hoangsvit Jul 23, 2026
f2ba703
ci: authenticate release lookup for web previews
hoangsvit Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ body:
id: extension-version
attributes:
label: Extension version
placeholder: "Example: v1.2.0"
placeholder: "Example: vX.Y.Z"

- type: dropdown
id: mode
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
build:
name: Build website
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ github.token }}

steps:
- name: Checkout
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
name: Build preview site
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ github.token }}

steps:
- name: Checkout production site
Expand Down
162 changes: 162 additions & 0 deletions entrypoints/popup/components/DisabledInlineSitesPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { useEffect, useState } from "react";
import { t } from "../../../lib/i18n";
import { filterDisabledInlineSites } from "src/utils/inlineSiteSettings";
import Button from "./Button";
import Input from "./Input";

const INLINE_SITES_PAGE_SIZE = 5;

interface DisabledInlineSitesPanelProps {
sites: string[];
onEnable: (site: string) => Promise<void>;
}

/** Renders searchable, paginated controls for websites with the inline helper disabled. */
export default function DisabledInlineSitesPanel({
sites,
onEnable,
}: DisabledInlineSitesPanelProps) {
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const filteredSites = filterDisabledInlineSites(sites, query);
const pageCount = Math.max(
1,
Math.ceil(filteredSites.length / INLINE_SITES_PAGE_SIZE),
);
const currentPage = Math.min(page, pageCount);
const startIndex = (currentPage - 1) * INLINE_SITES_PAGE_SIZE;
const visibleSites = filteredSites.slice(
startIndex,
startIndex + INLINE_SITES_PAGE_SIZE,
);

useEffect(() => {
if (page > pageCount) setPage(pageCount);
}, [page, pageCount]);

/** Updates the search query and returns to the first page. */
const handleQueryChange = (value: string) => {
setQuery(value);
setPage(1);
};

/** Shows the previous disabled-sites page. */
const showPreviousPage = () => setPage((current) => Math.max(1, current - 1));

/** Shows the next disabled-sites page. */
const showNextPage = () =>
setPage((current) => Math.min(pageCount, current + 1));

if (sites.length === 0) {
return (
<p className="rounded-xl border border-dashed border-border px-3 py-3 text-center text-xs text-muted-foreground">
{t("noDisabledSites")}
</p>
);
}

return (
<div className="space-y-2.5">
{sites.length > INLINE_SITES_PAGE_SIZE && (
<Input
type="search"
value={query}
onChange={handleQueryChange}
placeholder={t("inlineDisabledSites")}
className="w-full"
/>
)}

{visibleSites.length === 0 ? (
<p className="rounded-xl border border-dashed border-border px-3 py-3 text-center text-xs text-muted-foreground">
{t("noResultsFound")}
</p>
) : (
<div className="space-y-1.5">
{visibleSites.map((site) => (
<div
key={site}
className="flex items-center justify-between gap-2 rounded-xl border border-border bg-muted/45 px-3 py-2"
>
<span
className="min-w-0 truncate font-mono text-xs text-foreground"
title={site}
>
{site}
</span>
<Button
variant="ghost"
size="sm"
className="shrink-0 rounded-lg px-2 text-xs text-primary hover:bg-primary/10"
onClick={() => void onEnable(site)}
aria-label={`${t("enableInlineForSite")}: ${site}`}
>
{t("enableInlineForSite")}
</Button>
</div>
))}
</div>
)}

{pageCount > 1 && (
<div className="flex items-center justify-between border-t border-border/70 pt-2">
<Button
variant="ghost"
size="sm"
className="h-8 w-8 rounded-lg p-0"
onClick={showPreviousPage}
disabled={currentPage === 1}
aria-label={t("pageLabel", String(Math.max(1, currentPage - 1)))}
>
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</Button>
<span
className="text-xs font-medium text-muted-foreground"
aria-live="polite"
>
{currentPage} / {pageCount}
</span>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 rounded-lg p-0"
onClick={showNextPage}
disabled={currentPage === pageCount}
aria-label={t(
"pageLabel",
String(Math.min(pageCount, currentPage + 1)),
)}
>
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</Button>
</div>
)}
</div>
);
}
88 changes: 60 additions & 28 deletions entrypoints/popup/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useState, useEffect, useCallback } from "react";
import Toggle from "./Toggle";
import Button from "./Button";
import Input from "./Input";
import DisabledInlineSitesPanel from "./DisabledInlineSitesPanel";
import {
Select,
SelectContent,
Expand All @@ -16,6 +17,7 @@ import { BouncyAccordion } from "src/components/motion/bouncy-accordion";
import { AnimatedBadge } from "src/components/motion/animated-badge";
import { getAccountStorageKey, validateEmail } from "../utils";
import { t } from "../../../lib/i18n";
import { APP_VERSION } from "src/version";
import {
INLINE_DISABLED_SITES_KEY,
parseDisabledInlineSites,
Expand Down Expand Up @@ -80,6 +82,59 @@ const DEFAULT_SETTINGS: AppSettings = {
const TOAST_DURATION = 2000;

const CHANGELOG: ChangelogEntry[] = [
{
version: "1.3.2",
date: "2026-07-24",
changes: [
{
type: "Added",
items: [
"Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions",
],
},
{
type: "Changed",
items: [
"Aligned WXT development and production URL-scope handling",
"Restricted development builds to the configured site allowlist while preserving all-site production support",
],
},
{
type: "Fixed",
items: [
"Restored inline popup availability in production builds",
"Prevented unrelated content scripts and broad development URL patterns from passing manifest validation",
],
},
],
},
{
version: "1.3.1",
date: "2026-07-20",
changes: [
{
type: "Added",
items: [
"Added Edge and Opera release packages with multi-browser installation guidance",
"Added an interactive product tour and richer landing-page previews",
],
},
{
type: "Changed",
items: [
"Improved active email handling across the popup, inline helper, and context menus",
"Optimized inline popup behavior and displayed the active base email",
],
},
{
type: "Fixed",
items: [
"Improved user feedback when disabling the inline helper or saving aliases",
"Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration",
],
},
],
},
{
version: "1.3.0",
date: "2026-07-13",
Expand Down Expand Up @@ -670,7 +725,7 @@ export default function Settings({
const [editingAccountId, setEditingAccountId] = useState<string | null>(null);
const [editingLabel, setEditingLabel] = useState("");
const [editingEmail, setEditingEmail] = useState("");
const [version, setVersion] = useState("1.3.0");
const [version, setVersion] = useState(APP_VERSION);
const [showAddAccount, setShowAddAccount] = useState(false);
const [newAccountEmail, setNewAccountEmail] = useState("");
const [newAccountLabel, setNewAccountLabel] = useState("");
Expand Down Expand Up @@ -1293,33 +1348,10 @@ export default function Settings({
<p className="text-xs text-muted-foreground">
{t("inlineDisabledSitesDescription")}
</p>
{disabledInlineSites.length === 0 ? (
<p className="rounded-xl border border-dashed border-border px-3 py-3 text-center text-xs text-muted-foreground">
{t("noDisabledSites")}
</p>
) : (
<div className="max-h-36 space-y-1.5 overflow-y-auto">
{disabledInlineSites.map((site) => (
<div
key={site}
className="flex items-center justify-between gap-2 rounded-xl border border-border bg-muted/45 px-3 py-2"
>
<span className="min-w-0 truncate font-mono text-xs text-foreground">
{site}
</span>
<Button
variant="ghost"
size="sm"
className="shrink-0 rounded-lg px-2 text-xs text-primary hover:bg-primary/10"
onClick={() => enableInlineForSite(site)}
aria-label={`${t("enableInlineForSite")}: ${site}`}
>
{t("enableInlineForSite")}
</Button>
</div>
))}
</div>
)}
<DisabledInlineSitesPanel
sites={disabledInlineSites}
onEnable={enableInlineForSite}
/>
</div>
),
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "gmail-alias-toolkit",
"description": "Generate and manage Gmail aliases with plus addressing and presets",
"private": true,
"version": "1.3.1",
"version": "1.3.2",
"author": "dev@eplus.dev",
"license": "MIT",
"homepage_url": "https://eplus.dev",
Expand Down
11 changes: 11 additions & 0 deletions src/utils/inlineSiteSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,14 @@ export function parseDisabledInlineSites(value: unknown): string[] {
),
).sort((a, b) => a.localeCompare(b));
}

/** Filters disabled-site hostnames using the same normalization as stored values. */
export function filterDisabledInlineSites(
sites: string[],
query: string,
): string[] {
const normalizedQuery = normalizeSiteHostname(query);
if (!normalizedQuery) return sites;

return sites.filter((site) => site.includes(normalizedQuery));
}
6 changes: 6 additions & 0 deletions src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import packageMetadata from "../package.json";

const injectedVersion = import.meta.env.VITE_APP_VERSION?.trim();

/** Application version from the web build or root package metadata. */
export const APP_VERSION = injectedVersion || packageMetadata.version;
16 changes: 16 additions & 0 deletions tests/utils/inlineSiteSettings.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import {
filterDisabledInlineSites,
normalizeSiteHostname,
parseDisabledInlineSites,
} from "../../src/utils/inlineSiteSettings";
Expand All @@ -24,4 +25,19 @@ describe("inline site settings", () => {
it("handles invalid legacy storage values", () => {
expect(parseDisabledInlineSites({ site: "voidzero.dev" })).toEqual([]);
});

it("filters disabled sites with normalized partial hostnames", () => {
const sites = ["accounts.google.com", "github.com", "mail.google.com"];

expect(filterDisabledInlineSites(sites, " WWW.GOOGLE. ")).toEqual([
"accounts.google.com",
"mail.google.com",
]);
});

it("returns all disabled sites for an empty search", () => {
const sites = ["github.com", "typeform.com"];

expect(filterDisabledInlineSites(sites, " ")).toBe(sites);
});
});
Loading