Skip to content

Commit 30d07bc

Browse files
authored
Add DiffKit browser redirect extension (#46)
1 parent bc122cf commit 30d07bc

14 files changed

Lines changed: 1280 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# DiffKit Extension
2+
3+
Standalone browser extension for redirecting only selected GitHub URLs to DiffKit.
4+
5+
## What it does
6+
7+
- Redirects matching GitHub URLs at page start
8+
- Lets you keep the extension globally enabled or disabled
9+
- Shows one toggle per redirect rule in the popup
10+
- Uses a configurable list of rules instead of redirecting every GitHub page
11+
- Supports both exact URL redirects and regex-based route schemas
12+
- Supports custom route remaps like GitHub PR changes pages to DiffKit review pages
13+
14+
## Default rule
15+
16+
The extension ships with these enabled rules:
17+
18+
- `https://github.com/`
19+
- `https://diff-kit.com/`
20+
- `https://github.com/pulls/*`
21+
- `https://diff-kit.com/pulls`
22+
- `https://github.com/issues/*`
23+
- `https://diff-kit.com/issues`
24+
- `https://github.com/:owner/:repo/pull/:number`
25+
- `https://diff-kit.com/:owner/:repo/pull/:number`
26+
- `https://github.com/:owner/:repo/pull/:number/changes`
27+
- `https://diff-kit.com/:owner/:repo/review/:number`
28+
- `https://github.com/:owner/:repo/issues/:number`
29+
- `https://diff-kit.com/:owner/:repo/issues/:number`
30+
31+
## Rule format
32+
33+
```json
34+
[
35+
{
36+
"id": "github-pull-details",
37+
"label": "Pull request details",
38+
"description": "Redirect GitHub pull request detail pages to DiffKit.",
39+
"enabled": true,
40+
"match": {
41+
"urlRegex": "^https://github\\.com/([^/?#]+)/([^/?#]+)/pull/(\\d+)/?$",
42+
"excludeUrlRegexes": []
43+
},
44+
"redirect": {
45+
"replacement": "https://diff-kit.com/$1/$2/pull/$3"
46+
}
47+
}
48+
]
49+
```
50+
51+
For an exact URL redirect, use:
52+
53+
```json
54+
{
55+
"id": "one-off",
56+
"label": "Specific PR page",
57+
"description": "Single URL redirect",
58+
"enabled": true,
59+
"match": {
60+
"exactUrl": "https://github.com/org/repo/pull/123"
61+
},
62+
"redirect": {
63+
"url": "https://diff-kit.com/org/repo/pull/123"
64+
}
65+
}
66+
```
67+
68+
## Install locally
69+
70+
1. Open `chrome://extensions`
71+
2. Enable Developer mode
72+
3. Click Load unpacked
73+
4. Select `extensions/diffkit-redirect`
74+
75+
## Scope
76+
77+
This version is intentionally limited to `github.com` in `manifest.json`. If you later want redirects from other source hosts, add those hosts to the extension matches and permissions.
78+
79+
## Popup behavior
80+
81+
The popup keeps all default rules enabled unless you turn specific ones off. Use the master switch to pause all redirects without losing your per-rule selections.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(async function runRedirect() {
2+
const shared = globalThis.DiffKitRedirect;
3+
if (!shared) {
4+
return;
5+
}
6+
7+
try {
8+
const config = await shared.getConfig();
9+
if (!config.enabled) {
10+
return;
11+
}
12+
13+
const redirect = shared.findRedirect(window.location.href, config.rules);
14+
if (!redirect) {
15+
return;
16+
}
17+
18+
window.location.replace(redirect.targetUrl);
19+
} catch (error) {
20+
console.error("DiffKit: failed to evaluate redirect.", error);
21+
}
22+
})();
5.79 KB
Loading
815 Bytes
Loading
1.51 KB
Loading
2.07 KB
Loading
Lines changed: 12 additions & 0 deletions
Loading
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "DiffKit",
4+
"version": "0.1.0",
5+
"description": "Redirect selected GitHub URLs to matching DiffKit routes.",
6+
"icons": {
7+
"16": "icons/icon-16.png",
8+
"32": "icons/icon-32.png",
9+
"48": "icons/icon-48.png",
10+
"128": "icons/icon-128.png"
11+
},
12+
"permissions": ["storage"],
13+
"host_permissions": ["https://github.com/*"],
14+
"action": {
15+
"default_title": "DiffKit",
16+
"default_popup": "popup.html",
17+
"default_icon": {
18+
"16": "icons/icon-16.png",
19+
"32": "icons/icon-32.png"
20+
}
21+
},
22+
"options_page": "options.html",
23+
"content_scripts": [
24+
{
25+
"matches": ["https://github.com/*"],
26+
"js": ["shared.js", "content.js"],
27+
"run_at": "document_start"
28+
}
29+
]
30+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>DiffKit Options</title>
7+
<link rel="stylesheet" href="styles.css">
8+
</head>
9+
<body>
10+
<main class="options-layout">
11+
<section class="options-panel">
12+
<div class="options-header">
13+
<div>
14+
<p class="eyebrow">DiffKit</p>
15+
<h1>Redirect rules</h1>
16+
</div>
17+
18+
<label class="toggle-row">
19+
<span>Enabled</span>
20+
<input id="enabled-toggle" type="checkbox">
21+
</label>
22+
</div>
23+
24+
<p class="muted">
25+
Add only the GitHub routes DiffKit currently supports. A rule can
26+
match a single exact URL or a regex pattern over the full URL.
27+
</p>
28+
29+
<textarea
30+
id="rules-editor"
31+
class="rules-editor"
32+
spellcheck="false"
33+
></textarea>
34+
35+
<div class="panel-actions">
36+
<button id="save-button" type="button">Save rules</button>
37+
<button id="reset-button" type="button" class="secondary">
38+
Reset defaults
39+
</button>
40+
</div>
41+
42+
<p id="save-status" class="status-copy"></p>
43+
</section>
44+
45+
<aside class="help-panel">
46+
<h2>Rule shape</h2>
47+
<pre class="code-block">{
48+
"id": "github-pull-details",
49+
"label": "Pull request details",
50+
"description": "Redirect GitHub pull request detail pages to DiffKit.",
51+
"enabled": true,
52+
"match": {
53+
"urlRegex": "^https://github\\.com/([^/?#]+)/([^/?#]+)/pull/(\\d+)/?$",
54+
"excludeUrlRegexes": []
55+
},
56+
"redirect": {
57+
"replacement": "https://diff-kit.com/$1/$2/pull/$3"
58+
}
59+
}</pre>
60+
61+
<p class="muted">
62+
<code>label</code>
63+
is optional but recommended so the popup can show a clean toggle name.
64+
For one-off routes, use <code>match.exactUrl</code> and
65+
<code>redirect.url</code>
66+
instead.
67+
</p>
68+
</aside>
69+
</main>
70+
71+
<script src="shared.js"></script>
72+
<script src="options.js"></script>
73+
</body>
74+
</html>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
(async function initOptions() {
2+
const shared = globalThis.DiffKitRedirect;
3+
const enabledToggle = document.getElementById("enabled-toggle");
4+
const rulesEditor = document.getElementById("rules-editor");
5+
const saveButton = document.getElementById("save-button");
6+
const resetButton = document.getElementById("reset-button");
7+
const saveStatus = document.getElementById("save-status");
8+
9+
function setStatus(message, tone) {
10+
saveStatus.textContent = message;
11+
saveStatus.dataset.tone = tone || "";
12+
}
13+
14+
async function loadConfig() {
15+
const config = await shared.getConfig();
16+
enabledToggle.checked = config.enabled;
17+
rulesEditor.value = JSON.stringify(config.rules, null, 2);
18+
19+
if (config.validationErrors.length > 0) {
20+
setStatus(config.validationErrors.join(" "), "error");
21+
return;
22+
}
23+
24+
setStatus("", "");
25+
}
26+
27+
async function saveConfig() {
28+
let parsedRules;
29+
try {
30+
parsedRules = JSON.parse(rulesEditor.value);
31+
} catch {
32+
setStatus("Rules must be valid JSON.", "error");
33+
return;
34+
}
35+
36+
const result = await shared.saveConfig({
37+
enabled: enabledToggle.checked,
38+
rules: parsedRules,
39+
});
40+
41+
if (!result.ok) {
42+
setStatus(result.errors.join(" "), "error");
43+
return;
44+
}
45+
46+
setStatus("Rules saved.", "success");
47+
await loadConfig();
48+
}
49+
50+
saveButton.addEventListener("click", saveConfig);
51+
52+
resetButton.addEventListener("click", () => {
53+
rulesEditor.value = JSON.stringify(shared.getDefaultRules(), null, 2);
54+
setStatus("Defaults restored in the editor. Save to apply them.", "");
55+
});
56+
57+
await loadConfig();
58+
})();

0 commit comments

Comments
 (0)