Skip to content
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

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

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
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
31 changes: 31 additions & 0 deletions redesign/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// background.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']
});
});
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.

220 changes: 220 additions & 0 deletions redesign/config-tab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// 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');

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

if (response.ok) {
statusIndicator.className = 'status-indicator status-success';
statusText.textContent = '✓ Server is reachable';
statusText.className = 'text-success';
} else {
statusIndicator.className = 'status-indicator status-error';
statusText.textContent = `✗ Server error: ${response.status} ${response.statusText}`;
statusText.className = 'text-danger';
}
} catch (err) {
statusIndicator.className = 'status-indicator status-error';
statusText.textContent = `✗ Connection failed: ${err.message}`;
statusText.className = 'text-danger';
}
});

// 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