Skip to content

Commit 13d9e62

Browse files
authored
Merge pull request #31 from ArchiveBox/redesign
Update to manifest v3, redesign UI to allow click-to-archive + tagging, add history view + options.html page for settings
2 parents cb3f0e6 + 060feb0 commit 13d9e62

16 files changed

+2952
-4
lines changed

redesign/background.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// background.js
2+
3+
import { addToArchiveBox } from "./utils.js";
4+
5+
chrome.runtime.onMessage.addListener(async (message) => {
6+
const options_url = chrome.runtime.getURL('options.html') + `?search=${message.id}`;
7+
console.log('i ArchiveBox Collector showing options.html', options_url);
8+
if (message.action === 'openOptionsPage') {
9+
await chrome.tabs.create({ url: options_url });
10+
}
11+
});
12+
13+
chrome.action.onClicked.addListener(async (tab) => {
14+
const entry = {
15+
id: crypto.randomUUID(),
16+
url: tab.url,
17+
timestamp: new Date().toISOString(),
18+
tags: [],
19+
title: tab.title,
20+
favicon: tab.favIconUrl
21+
};
22+
23+
// Save the entry first
24+
const { entries = [] } = await chrome.storage.local.get('entries');
25+
entries.push(entry);
26+
await chrome.storage.local.set({ entries });
27+
28+
// Inject scripts - CSS now handled in popup.js
29+
await chrome.scripting.executeScript({
30+
target: { tabId: tab.id },
31+
files: ['popup.js']
32+
});
33+
});
34+
35+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
36+
if (message.type === 'archivebox_add') {
37+
const result = addToArchiveBox(message.body)
38+
.then(result => {
39+
sendResponse(result);
40+
})
41+
}
42+
return true;
43+
});

redesign/bootstrap.bundle.min.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

redesign/bootstrap.min.css

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

redesign/config-tab.js

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// Config tab initialization and handlers
2+
3+
export async function initializeConfigTab() {
4+
const configForm = document.getElementById('configForm');
5+
const serverUrl = document.getElementById('archivebox_server_url');
6+
const apiKey = document.getElementById('archivebox_api_key');
7+
const matchUrls = document.getElementById('match_urls');
8+
9+
// Load saved values
10+
const savedConfig = await chrome.storage.local.get([
11+
'archivebox_server_url',
12+
'archivebox_api_key',
13+
'match_urls'
14+
]);
15+
16+
serverUrl.value = savedConfig.archivebox_server_url || '';
17+
apiKey.value = savedConfig.archivebox_api_key || '';
18+
matchUrls.value = savedConfig.match_urls || '.*';
19+
20+
// Server test button handler
21+
document.getElementById('testServer').addEventListener('click', async () => {
22+
const statusIndicator = document.getElementById('serverStatus');
23+
const statusText = document.getElementById('serverStatusText');
24+
25+
const updateStatus = (success, message) => {
26+
statusIndicator.className = success ? 'status-indicator status-success' : 'status-indicator status-error';
27+
statusText.textContent = message;
28+
statusText.className = success ? 'text-success' : 'text-danger';
29+
};
30+
31+
try {
32+
let response = await fetch(`${serverUrl.value}/api/`, {
33+
method: 'GET',
34+
mode: 'cors',
35+
credentials: 'omit'
36+
});
37+
38+
// fall back to pre-v0.8.0 endpoint for backwards compatibility
39+
if (response.status === 404) {
40+
response = await fetch(`${serverUrl.value}`, {
41+
method: 'GET',
42+
mode: 'cors',
43+
credentials: 'omit'
44+
});
45+
}
46+
47+
if (response.ok) {
48+
updateStatus(true, '✓ Server is reachable');
49+
} else {
50+
updateStatus(false, `✗ Server error: ${response.status} ${response.statusText}`);
51+
}
52+
} catch (err) {
53+
updateStatus(false, `✗ Connection failed: ${err.message}`);
54+
}
55+
});
56+
57+
// API key test button handler
58+
document.getElementById('testApiKey').addEventListener('click', async () => {
59+
const statusIndicator = document.getElementById('apiKeyStatus');
60+
const statusText = document.getElementById('apiKeyStatusText');
61+
62+
try {
63+
const response = await fetch(`${serverUrl.value}/api/v1/auth/check_api_token`, {
64+
method: 'POST',
65+
mode: 'cors',
66+
credentials: 'omit',
67+
body: JSON.stringify({
68+
token: apiKey.value,
69+
})
70+
});
71+
const data = await response.json();
72+
73+
if (data.user_id) {
74+
statusIndicator.className = 'status-indicator status-success';
75+
statusText.textContent = `✓ API key is valid: user_id = ${data.user_id}`;
76+
statusText.className = 'text-success';
77+
} else {
78+
statusIndicator.className = 'status-indicator status-error';
79+
statusText.textContent = `✗ API key error: ${response.status} ${response.statusText} ${JSON.stringify(data)}`;
80+
statusText.className = 'text-danger';
81+
}
82+
} catch (err) {
83+
statusIndicator.className = 'status-indicator status-error';
84+
statusText.textContent = `✗ API test failed: ${err.message}`;
85+
statusText.className = 'text-danger';
86+
}
87+
});
88+
89+
// Generate API key button handler
90+
document.getElementById('generateApiKey').addEventListener('click', () => {
91+
const key = Array.from(crypto.getRandomValues(new Uint8Array(16)))
92+
.map(b => b.toString(16).padStart(2, '0'))
93+
.join('');
94+
apiKey.value = key;
95+
});
96+
97+
// Login server button handler
98+
document.getElementById('loginServer').addEventListener('click', () => {
99+
if (serverUrl.value) {
100+
window.open(`${serverUrl.value}/admin`, '_blank');
101+
}
102+
});
103+
104+
// Save changes when inputs change
105+
[serverUrl, apiKey, matchUrls].forEach(input => {
106+
input.addEventListener('change', async () => {
107+
await chrome.storage.local.set({
108+
archivebox_server_url: serverUrl.value,
109+
archivebox_api_key: apiKey.value,
110+
match_urls: matchUrls.value
111+
});
112+
});
113+
});
114+
115+
// Test URL functionality
116+
const testUrlInput = document.getElementById('testUrl');
117+
const testButton = document.getElementById('testAdding');
118+
const testStatus = document.getElementById('testStatus');
119+
120+
testButton.addEventListener('click', async () => {
121+
const url = testUrlInput.value.trim();
122+
if (!url) {
123+
testStatus.innerHTML = `
124+
<span class="status-indicator status-error"></span>
125+
Please enter a URL to test
126+
`;
127+
return;
128+
}
129+
130+
// Show loading state
131+
testButton.disabled = true;
132+
testStatus.innerHTML = `
133+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
134+
Testing...
135+
`;
136+
137+
try {
138+
const testEntry = {
139+
url,
140+
title: 'Test Entry',
141+
timestamp: new Date().toISOString(),
142+
tags: ['test']
143+
};
144+
145+
const result = await syncToArchiveBox(testEntry);
146+
147+
if (result.ok) {
148+
testStatus.innerHTML = `
149+
<span class="status-indicator status-success"></span>
150+
Success! URL was added to ArchiveBox
151+
`;
152+
// Clear the input on success
153+
testUrlInput.value = '';
154+
} else {
155+
testStatus.innerHTML = `
156+
<span class="status-indicator status-error"></span>
157+
Error: ${result.status}
158+
`;
159+
}
160+
} catch (error) {
161+
testStatus.innerHTML = `
162+
<span class="status-indicator status-error"></span>
163+
Error: ${error.message}
164+
`;
165+
} finally {
166+
testButton.disabled = false;
167+
}
168+
});
169+
170+
// Add Enter key support for test URL input
171+
testUrlInput.addEventListener('keypress', (e) => {
172+
if (e.key === 'Enter') {
173+
e.preventDefault();
174+
testButton.click();
175+
}
176+
});
177+
}
178+
179+
async function syncToArchiveBox(entry) {
180+
const { archivebox_server_url, archivebox_api_key } = await chrome.storage.local.get([
181+
'archivebox_server_url',
182+
'archivebox_api_key'
183+
]);
184+
185+
if (!archivebox_server_url || !archivebox_api_key) {
186+
return {
187+
ok: false,
188+
status: 'Server URL and API key must be configured and saved first'
189+
};
190+
}
191+
192+
try {
193+
const response = await fetch(`${archivebox_server_url}/api/v1/cli/add`, {
194+
method: 'POST',
195+
mode: 'cors',
196+
credentials: 'omit',
197+
headers: {
198+
'Accept': 'application/json',
199+
'Content-Type': 'application/json',
200+
},
201+
body: JSON.stringify({
202+
api_key: archivebox_api_key,
203+
urls: [entry.url],
204+
tag: entry.tags.join(','),
205+
depth: 0,
206+
update: false,
207+
update_all: false,
208+
}),
209+
});
210+
211+
if (!response.ok) {
212+
const text = await response.text();
213+
return {
214+
ok: false,
215+
status: `Server returned ${response.status}: ${text}`
216+
};
217+
}
218+
219+
return {
220+
ok: true,
221+
status: 'Success'
222+
};
223+
} catch (err) {
224+
return {
225+
ok: false,
226+
status: `Connection failed: ${err.message}`
227+
};
228+
}
229+
}

0 commit comments

Comments
 (0)