Skip to content

Update to manifest v3, redesign UI to allow click-to-archive + tagging, add history view + options.html page for settings #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 51 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
c53560a
Create manifest.json
pirate Nov 26, 2024
d8f0fb7
Create background.js
pirate Nov 26, 2024
3c2f8e5
Create popup.css
pirate Nov 26, 2024
19a0654
Create content_script.js
pirate Nov 26, 2024
eaf1b68
Create options.html
pirate Nov 26, 2024
f9dee06
Create options.js
pirate Nov 26, 2024
e2eb706
Create popup.js
pirate Nov 26, 2024
103b722
add live testing and saving of config in extension
pirate Nov 26, 2024
575249a
add sync button, and ability to test adding from config page
pirate Nov 26, 2024
793a56b
tiny fixes
pirate Nov 26, 2024
0b70855
convert to snake case, better error messages, better live updating forms
pirate Nov 26, 2024
86148fb
change popup.js and background.js to use snake_case for variables
pirate Nov 26, 2024
3fee467
add autocomplete for tags
pirate Nov 27, 2024
dcf4da6
Merge branch 'master' into redesign
pirate Nov 27, 2024
4181966
add icons
pirate Nov 27, 2024
547d17a
dead code
pirate Nov 27, 2024
11e3415
working iframed popup
pirate Nov 27, 2024
c3a027e
switch to using chrome.storage.local and working import tab from book…
pirate Nov 27, 2024
d62f604
working autoresizing iframe popup
pirate Nov 27, 2024
ae2ca65
add new cookies import tab to options page
pirate Nov 27, 2024
8e354d5
add fields for user agent, language, timezone, etc. and autodetect in…
pirate Nov 27, 2024
b7b19d9
more UI to handle selecting all or no urls on options page
pirate Nov 27, 2024
c8d7aa0
working import export and tags editing on bulk urls
pirate Nov 27, 2024
9181465
split tabs into separate files, fix tag autocomplete, and more
pirate Nov 27, 2024
b858e86
fix export buttons
pirate Nov 27, 2024
05bd1ba
nicer tags css and fix auto-adding URLs while browsing
pirate Nov 27, 2024
3a5ad16
Make request to server from background script
benmuth Dec 1, 2024
b195ec2
Add auth to request to server in background script
benmuth Dec 13, 2024
ee41b1c
Get configuration info directly in background script
benmuth Dec 13, 2024
b2fb2e9
Merge pull request #34 from benmuth/redesign-fixes
pirate Dec 15, 2024
c6029fd
Remove dead code
benmuth Dec 13, 2024
82691fc
Improve status and error reporting.
benmuth Dec 16, 2024
89b40df
Fix bug with error message
benmuth Dec 16, 2024
b9abf65
Merge pull request #35 from benmuth/error-handling
pirate Dec 16, 2024
38f3862
Add fallback when API call fails.
benmuth Jan 18, 2025
2d9e3fc
Switch to async await.
benmuth Jan 19, 2025
eca92d7
Fix formatting, add comments and missing await.
benmuth Jan 19, 2025
cb021ed
Remove API key requirement for old versions.
benmuth Jan 23, 2025
6ada168
Make server test work with 0.7.3.
benmuth Jan 24, 2025
c53eeb6
Fix response format.
benmuth Jan 24, 2025
0a7bf52
Wrap sendMessage in Promise.
benmuth Jan 25, 2025
6e032b3
Fix fetch error not being reported.
benmuth Feb 13, 2025
bd8d5b1
Reset status fadeout animation on update.
benmuth Feb 13, 2025
4401818
Merge pull request #36 from benmuth/redesign
pirate Feb 16, 2025
a1b87f8
Merge branch 'master' into redesign
pirate Feb 17, 2025
1a7c4b1
Handle escape when popup doesn't have focus.
benmuth Feb 19, 2025
b7f3f62
Hook up delete button.
benmuth Feb 19, 2025
80db060
Move addToArchiveBox to utils.
benmuth Feb 20, 2025
fdfbbbb
Add link to view entry on server.
benmuth Feb 20, 2025
ca5b188
Only show link if server is configured.
benmuth Feb 20, 2025
060feb0
Merge pull request #37 from benmuth/bugfixes
pirate Mar 6, 2025
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
43 changes: 43 additions & 0 deletions redesign/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// background.js

import { addToArchiveBox } from "./utils.js";

chrome.runtime.onMessage.addListener(async (message) => {
const options_url = chrome.runtime.getURL('options.html') + `?search=${message.id}`;
console.log('i ArchiveBox Collector showing options.html', options_url);
if (message.action === 'openOptionsPage') {
await chrome.tabs.create({ url: options_url });
}
});

chrome.action.onClicked.addListener(async (tab) => {
const entry = {
id: crypto.randomUUID(),
url: tab.url,
timestamp: new Date().toISOString(),
tags: [],
title: tab.title,
favicon: tab.favIconUrl
};

// Save the entry first
const { entries = [] } = await chrome.storage.local.get('entries');
entries.push(entry);
await chrome.storage.local.set({ entries });

// Inject scripts - CSS now handled in popup.js
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['popup.js']
});
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'archivebox_add') {
const result = addToArchiveBox(message.body)
.then(result => {
sendResponse(result);
})
}
return true;
});
7 changes: 7 additions & 0 deletions redesign/bootstrap.bundle.min.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions redesign/bootstrap.min.css

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions redesign/config-tab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// Config tab initialization and handlers

export async function initializeConfigTab() {
const configForm = document.getElementById('configForm');
const serverUrl = document.getElementById('archivebox_server_url');
const apiKey = document.getElementById('archivebox_api_key');
const matchUrls = document.getElementById('match_urls');

// Load saved values
const savedConfig = await chrome.storage.local.get([
'archivebox_server_url',
'archivebox_api_key',
'match_urls'
]);

serverUrl.value = savedConfig.archivebox_server_url || '';
apiKey.value = savedConfig.archivebox_api_key || '';
matchUrls.value = savedConfig.match_urls || '.*';

// Server test button handler
document.getElementById('testServer').addEventListener('click', async () => {
const statusIndicator = document.getElementById('serverStatus');
const statusText = document.getElementById('serverStatusText');

const updateStatus = (success, message) => {
statusIndicator.className = success ? 'status-indicator status-success' : 'status-indicator status-error';
statusText.textContent = message;
statusText.className = success ? 'text-success' : 'text-danger';
};

try {
let response = await fetch(`${serverUrl.value}/api/`, {
method: 'GET',
mode: 'cors',
credentials: 'omit'
});

// fall back to pre-v0.8.0 endpoint for backwards compatibility
if (response.status === 404) {
response = await fetch(`${serverUrl.value}`, {
method: 'GET',
mode: 'cors',
credentials: 'omit'
});
}

if (response.ok) {
updateStatus(true, '✓ Server is reachable');
} else {
updateStatus(false, `✗ Server error: ${response.status} ${response.statusText}`);
}
} catch (err) {
updateStatus(false, `✗ Connection failed: ${err.message}`);
}
});

// API key test button handler
document.getElementById('testApiKey').addEventListener('click', async () => {
const statusIndicator = document.getElementById('apiKeyStatus');
const statusText = document.getElementById('apiKeyStatusText');

try {
const response = await fetch(`${serverUrl.value}/api/v1/auth/check_api_token`, {
method: 'POST',
mode: 'cors',
credentials: 'omit',
body: JSON.stringify({
token: apiKey.value,
})
});
const data = await response.json();

if (data.user_id) {
statusIndicator.className = 'status-indicator status-success';
statusText.textContent = `✓ API key is valid: user_id = ${data.user_id}`;
statusText.className = 'text-success';
} else {
statusIndicator.className = 'status-indicator status-error';
statusText.textContent = `✗ API key error: ${response.status} ${response.statusText} ${JSON.stringify(data)}`;
statusText.className = 'text-danger';
}
} catch (err) {
statusIndicator.className = 'status-indicator status-error';
statusText.textContent = `✗ API test failed: ${err.message}`;
statusText.className = 'text-danger';
}
});

// Generate API key button handler
document.getElementById('generateApiKey').addEventListener('click', () => {
const key = Array.from(crypto.getRandomValues(new Uint8Array(16)))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
apiKey.value = key;
});

// Login server button handler
document.getElementById('loginServer').addEventListener('click', () => {
if (serverUrl.value) {
window.open(`${serverUrl.value}/admin`, '_blank');
}
});

// Save changes when inputs change
[serverUrl, apiKey, matchUrls].forEach(input => {
input.addEventListener('change', async () => {
await chrome.storage.local.set({
archivebox_server_url: serverUrl.value,
archivebox_api_key: apiKey.value,
match_urls: matchUrls.value
});
});
});

// Test URL functionality
const testUrlInput = document.getElementById('testUrl');
const testButton = document.getElementById('testAdding');
const testStatus = document.getElementById('testStatus');

testButton.addEventListener('click', async () => {
const url = testUrlInput.value.trim();
if (!url) {
testStatus.innerHTML = `
<span class="status-indicator status-error"></span>
Please enter a URL to test
`;
return;
}

// Show loading state
testButton.disabled = true;
testStatus.innerHTML = `
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Testing...
`;

try {
const testEntry = {
url,
title: 'Test Entry',
timestamp: new Date().toISOString(),
tags: ['test']
};

const result = await syncToArchiveBox(testEntry);

if (result.ok) {
testStatus.innerHTML = `
<span class="status-indicator status-success"></span>
Success! URL was added to ArchiveBox
`;
// Clear the input on success
testUrlInput.value = '';
} else {
testStatus.innerHTML = `
<span class="status-indicator status-error"></span>
Error: ${result.status}
`;
}
} catch (error) {
testStatus.innerHTML = `
<span class="status-indicator status-error"></span>
Error: ${error.message}
`;
} finally {
testButton.disabled = false;
}
});

// Add Enter key support for test URL input
testUrlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
testButton.click();
}
});
}

async function syncToArchiveBox(entry) {
const { archivebox_server_url, archivebox_api_key } = await chrome.storage.local.get([
'archivebox_server_url',
'archivebox_api_key'
]);

if (!archivebox_server_url || !archivebox_api_key) {
return {
ok: false,
status: 'Server URL and API key must be configured and saved first'
};
}

try {
const response = await fetch(`${archivebox_server_url}/api/v1/cli/add`, {
method: 'POST',
mode: 'cors',
credentials: 'omit',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
api_key: archivebox_api_key,
urls: [entry.url],
tag: entry.tags.join(','),
depth: 0,
update: false,
update_all: false,
}),
});

if (!response.ok) {
const text = await response.text();
return {
ok: false,
status: `Server returned ${response.status}: ${text}`
};
}

return {
ok: true,
status: 'Success'
};
} catch (err) {
return {
ok: false,
status: `Connection failed: ${err.message}`
};
}
}
Loading