Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 apps/dashboard/src/lib/extension-store-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const CHROME_EXTENSION_STORE_URL =
"https://chromewebstore.google.com/detail/celjddfjncnnkgfgldobcahfiimlebll/" as const;

export const FIREFOX_EXTENSION_STORE_URL =
"https://addons.mozilla.org/en-US/firefox/addon/diffkit/" as const;
"https://addons.mozilla.org/addon/diffkit/" as const;

function isFirefoxFamilyUserAgent(ua: string): boolean {
// Desktop Firefox: "Firefox/123"; Firefox iOS: "FxiOS/123"; avoid relying on siteConfig.
Expand Down
76 changes: 75 additions & 1 deletion apps/dashboard/src/routes/_protected/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { MoonIcon, SunIcon, SystemIcon } from "@diffkit/icons";
import {
CheckIcon,
DownloadIcon,
MoonIcon,
SunIcon,
SystemIcon,
} from "@diffkit/icons";
import { Button } from "@diffkit/ui/components/button";
import { Logo } from "@diffkit/ui/components/logo";
import { cn } from "@diffkit/ui/lib/utils";
import { createFileRoute, Link } from "@tanstack/react-router";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { signOutToLogin } from "#/lib/auth-actions";
import { isDiffKitExtensionPresent } from "#/lib/diffkit-extension-detect";
import { getExtensionStoreInstallUrl } from "#/lib/extension-store-url";
import { useHasMounted } from "#/lib/use-has-mounted";

const themeOptions = [
Expand Down Expand Up @@ -40,6 +50,13 @@ function GeneralSettingsPage() {
<ThemePicker />
</SettingsSection>

<SettingsSection
title="Browser extension"
description="Automatically redirect GitHub pages to DiffKit."
>
<ExtensionCard />
</SettingsSection>

<SettingsSection
title="Repository access"
description="Manage which GitHub organizations and repositories DiffKit can access."
Expand Down Expand Up @@ -229,3 +246,60 @@ function SystemThemePreview() {
</div>
);
}

function ExtensionCard() {
const hasMounted = useHasMounted();
const [installed, setInstalled] = useState(false);

useEffect(() => {
if (!hasMounted) return;

setInstalled(isDiffKitExtensionPresent());

if (isDiffKitExtensionPresent()) return;

const el = document.documentElement;
const observer = new MutationObserver(() => {
if (isDiffKitExtensionPresent()) {
setInstalled(true);
observer.disconnect();
}
});
observer.observe(el, {
attributes: true,
attributeFilter: ["data-diffkit-extension"],
});
return () => observer.disconnect();
}, [hasMounted]);

const installHref = getExtensionStoreInstallUrl();

return (
<div className="flex items-center justify-between gap-4 rounded-xl border border-border/70 px-4 py-3.5">
<div className="flex min-w-0 items-center gap-3">
<Logo className="size-5 shrink-0" aria-hidden />
<div className="min-w-0">
<p className="text-sm font-medium">DiffKit Extension</p>
<p className="text-sm text-muted-foreground">
{installed
? "The extension is installed and active."
: "Redirect GitHub PRs, issues, and matching pages to DiffKit."}
</p>
</div>
</div>
{installed ? (
<span className="flex shrink-0 items-center gap-1.5 text-sm text-emerald-600 dark:text-emerald-400">
<CheckIcon size={14} strokeWidth={2} />
Installed
</span>
) : (
<Button asChild variant="outline" size="sm" className="shrink-0">
<a href={installHref} target="_blank" rel="noopener noreferrer">
<DownloadIcon size={14} strokeWidth={2} />
Install
</a>
</Button>
)}
</div>
);
}
12 changes: 11 additions & 1 deletion extensions/diffkit-redirect/dashboard-presence.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@
* Runs on DiffKit web app origins so the site can detect the extension via DOM
* (`data-diffkit-extension` on <html>). Content scripts cannot share `window`
* with the page, but DOM attributes are visible to the app.
*
* Guard: Firefox may inject content scripts beyond the manifest `matches` when
* the user grants broader host permissions, so we verify the origin explicitly.
*/
(function markDiffKitExtensionPresent() {
try {
document.documentElement.dataset.diffkitExtension = "1";
const origin = location.origin;
if (
origin === "https://diff-kit.com" ||
origin === "http://localhost:3000" ||
origin === "http://127.0.0.1:3000"
) {
document.documentElement.dataset.diffkitExtension = "1";
}
} catch {
// ignore
}
Expand Down
Loading