Skip to content

Commit c8511f9

Browse files
committed
add import tab to import bookmarks and history
1 parent 11e3415 commit c8511f9

File tree

6 files changed

+244
-27
lines changed

6 files changed

+244
-27
lines changed

redesign/background.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ chrome.action.onClicked.addListener(async (tab) => {
1919
};
2020

2121
// Save the entry first
22-
const { entries = [] } = await chrome.storage.sync.get('entries');
22+
const { entries = [] } = await chrome.storage.local.get('entries');
2323
entries.push(entry);
24-
await chrome.storage.sync.set({ entries });
24+
await chrome.storage.local.set({ entries });
2525

2626
// Inject scripts - CSS now handled in popup.js
2727
await chrome.scripting.executeScript({

redesign/manifest.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"storage",
88
"scripting",
99
"tabs",
10-
"activeTab"
10+
"activeTab",
11+
"history",
12+
"bookmarks",
13+
"unlimitedStorage"
1114
],
1215
"host_permissions": [
1316
"<all_urls>"

redesign/options.html

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@
107107
<li class="nav-item">
108108
<a class="nav-link" id="personas-tab" data-bs-toggle="tab" href="#personas" role="tab">Personas</a>
109109
</li>
110+
<li class="nav-item">
111+
<a class="nav-link" id="import-tab" data-bs-toggle="tab" href="#import" role="tab">Import</a>
112+
</li>
110113
</ul>
111114

112115
<div class="tab-content">
@@ -229,6 +232,52 @@ <h2>All Tags</h2>
229232
</div>
230233
</div>
231234
</div>
235+
236+
<!-- Import Tab -->
237+
<div class="tab-pane fade" id="import" role="tabpanel">
238+
<div class="row mt-4">
239+
<div class="col-md-8">
240+
<h2>Import from Chrome</h2>
241+
242+
<div class="mb-4">
243+
<h4>History</h4>
244+
<div class="input-group mb-3">
245+
<input type="number" class="form-control" id="historyDays" value="7" min="1" max="90">
246+
<span class="input-group-text">days of history</span>
247+
<button class="btn btn-outline-secondary" type="button" id="loadHistory">Load History</button>
248+
</div>
249+
</div>
250+
251+
<div class="mb-4">
252+
<h4>Bookmarks</h4>
253+
<button class="btn btn-outline-secondary mb-3" type="button" id="loadBookmarks">Load Bookmarks</button>
254+
</div>
255+
256+
<div class="mb-3">
257+
<input type="search" class="form-control" id="importFilter" placeholder="Filter URLs...">
258+
</div>
259+
260+
<div class="form-check mb-3">
261+
<input class="form-check-input" type="checkbox" id="showOnlyNew">
262+
<label class="form-check-label" for="showOnlyNew">
263+
Show only URLs not already in ArchiveBox
264+
</label>
265+
</div>
266+
267+
<div id="importList" class="list-group mb-3">
268+
<!-- Items will be added here dynamically -->
269+
</div>
270+
271+
<div class="d-flex gap-2">
272+
<button class="btn btn-primary" id="importSelected" disabled>
273+
Import Selected (<span id="selectedCount">0</span>)
274+
</button>
275+
<button class="btn btn-outline-secondary" id="selectAll">Select All</button>
276+
<button class="btn btn-outline-secondary" id="deselectAll">Deselect All</button>
277+
</div>
278+
</div>
279+
</div>
280+
</div>
232281
</div>
233282

234283
<script src="bootstrap.bundle.min.js"></script>

redesign/options.js

Lines changed: 175 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function filterEntries(entries, filterText) {
1818
}
1919

2020
async function renderEntries(filterText = '', tagFilter = '') {
21-
const { entries = [] } = await chrome.storage.sync.get('entries');
21+
const { entries = [] } = await chrome.storage.local.get('entries');
2222

2323
if (!window.location.search.includes(filterText)) {
2424
window.history.pushState({}, '', `?search=${filterText}`);
@@ -111,7 +111,7 @@ function downloadJson(entries) {
111111

112112
// Add this function near the other event handlers
113113
async function handleDeleteFiltered(filterText = '') {
114-
const { entries = [] } = await chrome.storage.sync.get('entries');
114+
const { entries = [] } = await chrome.storage.local.get('entries');
115115
const filteredEntries = filterEntries(entries, filterText);
116116

117117
if (!filteredEntries.length) {
@@ -132,15 +132,15 @@ async function handleDeleteFiltered(filterText = '') {
132132
const remainingEntries = entries.filter(e => !idsToDelete.has(e.id));
133133

134134
// Save remaining entries
135-
await chrome.storage.sync.set({ entries: remainingEntries });
135+
await chrome.storage.local.set({ entries: remainingEntries });
136136

137137
// Refresh the view
138138
await renderEntries(filterText);
139139
}
140140

141141
// Add these functions near the top
142142
async function loadConfig() {
143-
const config = await chrome.storage.sync.get([
143+
const config = await chrome.storage.local.get([
144144
'archivebox_server_url',
145145
'archivebox_api_key', // Added this
146146
'match_urls',
@@ -181,7 +181,7 @@ function createAutosaveHandler() {
181181
exclude_urls: document.getElementById('exclude_urls').value || ''
182182
};
183183

184-
await chrome.storage.sync.set(config);
184+
await chrome.storage.local.set(config);
185185

186186
status_div.textContent = 'Saved.';
187187
setTimeout(() => {
@@ -319,7 +319,7 @@ async function testApiKey() {
319319

320320
// Add this function near the other utility functions
321321
async function syncToArchiveBox(entry) {
322-
const { archivebox_server_url, archivebox_api_key } = await chrome.storage.sync.get([
322+
const { archivebox_server_url, archivebox_api_key } = await chrome.storage.local.get([
323323
'archivebox_server_url',
324324
'archivebox_api_key'
325325
]);
@@ -358,7 +358,7 @@ async function syncToArchiveBox(entry) {
358358

359359
// Add this function to handle the sync operation
360360
async function handleSync(filterText = '') {
361-
const { entries = [] } = await chrome.storage.sync.get('entries');
361+
const { entries = [] } = await chrome.storage.local.get('entries');
362362
const filteredEntries = filterEntries(entries, filterText);
363363

364364
if (!filteredEntries.length) {
@@ -450,6 +450,123 @@ async function testAdding() {
450450
}
451451
}
452452

453+
// Import functionality
454+
async function loadHistoryItems(days) {
455+
const startTime = new Date();
456+
startTime.setDate(startTime.getDate() - days);
457+
458+
return new Promise((resolve) => {
459+
chrome.history.search({
460+
text: '',
461+
startTime: startTime.getTime(),
462+
maxResults: 10000
463+
}, resolve);
464+
});
465+
}
466+
467+
async function loadBookmarkItems(bookmarks = []) {
468+
const results = [];
469+
470+
function processNode(node) {
471+
if (node.url) {
472+
results.push({
473+
url: node.url,
474+
title: node.title,
475+
dateAdded: node.dateAdded
476+
});
477+
}
478+
if (node.children) {
479+
node.children.forEach(processNode);
480+
}
481+
}
482+
483+
return new Promise((resolve) => {
484+
chrome.bookmarks.getTree(tree => {
485+
tree.forEach(processNode);
486+
resolve(results);
487+
});
488+
});
489+
}
490+
491+
async function updateImportList(items, filter = '', onlyNew = false) {
492+
const { entries = [] } = await chrome.storage.local.get('entries');
493+
const existingUrls = new Set(entries.map(e => e.url));
494+
495+
// Filter and sort items
496+
const filteredItems = items
497+
.filter(item => {
498+
const matchesFilter = !filter ||
499+
item.url.toLowerCase().includes(filter.toLowerCase()) ||
500+
(item.title && item.title.toLowerCase().includes(filter.toLowerCase()));
501+
const isNew = !existingUrls.has(item.url);
502+
return matchesFilter && (!onlyNew || isNew);
503+
})
504+
.sort((a, b) => (b.lastVisited || b.dateAdded || 0) - (a.lastVisited || a.dateAdded || 0));
505+
506+
// Update the list
507+
const importList = document.getElementById('importList');
508+
importList.innerHTML = filteredItems.map(item => `
509+
<div class="list-group-item">
510+
<div class="form-check">
511+
<input class="form-check-input import-check" type="checkbox" value="${item.url}"
512+
id="check-${item.url}" ${existingUrls.has(item.url) ? 'disabled' : ''}>
513+
<label class="form-check-label" for="check-${item.url}">
514+
<div>
515+
<strong>${item.title || 'Untitled'}</strong>
516+
<small class="text-muted">
517+
(${new Date(item.lastVisited || item.dateAdded).toLocaleString()})
518+
</small>
519+
</div>
520+
<small class="text-muted">${item.url}</small>
521+
</label>
522+
</div>
523+
</div>
524+
`).join('');
525+
526+
updateSelectedCount();
527+
}
528+
529+
function updateSelectedCount() {
530+
const count = document.querySelectorAll('.import-check:checked').length;
531+
document.getElementById('selectedCount').textContent = count;
532+
document.getElementById('importSelected').disabled = count === 0;
533+
}
534+
535+
async function importSelected() {
536+
const selectedUrls = Array.from(document.querySelectorAll('.import-check:checked'))
537+
.map(checkbox => ({
538+
url: checkbox.value,
539+
title: checkbox.closest('.list-group-item').querySelector('strong').textContent
540+
}));
541+
542+
const { entries = [] } = await chrome.storage.local.get('entries');
543+
544+
// Create new entries
545+
const newEntries = selectedUrls.map(({ url, title }) => ({
546+
id: crypto.randomUUID(),
547+
url,
548+
title,
549+
timestamp: new Date().toISOString(),
550+
tags: [],
551+
notes: ''
552+
}));
553+
554+
// Add to storage
555+
await chrome.storage.local.set({
556+
entries: [...entries, ...newEntries]
557+
});
558+
559+
// Refresh the list
560+
const filter = document.getElementById('importFilter').value;
561+
const onlyNew = document.getElementById('showOnlyNew').checked;
562+
await updateImportList(window.importItems || [], filter, onlyNew);
563+
564+
// Update the main entries list if we're on that tab
565+
if (document.getElementById('urls-tab').classList.contains('active')) {
566+
await renderEntries();
567+
}
568+
}
569+
453570
// Modify the loadOptions function to include config initialization
454571
async function loadOptions() {
455572
// Initial render
@@ -471,7 +588,7 @@ async function loadOptions() {
471588
});
472589

473590
// Collect and display all unique tags
474-
const { entries = [] } = await chrome.storage.sync.get('entries');
591+
const { entries = [] } = await chrome.storage.local.get('entries');
475592
const allTags = [...new Set(entries.flatMap(entry => entry.tags))];
476593
const tagsList = document.getElementById('tagsList');
477594
tagsList.innerHTML = allTags.map(tag => `
@@ -499,14 +616,14 @@ async function loadOptions() {
499616

500617
downloadBtn.addEventListener('click', async () => {
501618
const filterInput = document.getElementById('filterInput');
502-
const { entries = [] } = await chrome.storage.sync.get('entries');
619+
const { entries = [] } = await chrome.storage.local.get('entries');
503620
const filteredEntries = filterEntries(entries, filterInput.value);
504621
downloadCsv(filteredEntries);
505622
});
506623

507624
downloadJsonBtn.addEventListener('click', async () => {
508625
const filterInput = document.getElementById('filterInput');
509-
const { entries = [] } = await chrome.storage.sync.get('entries');
626+
const { entries = [] } = await chrome.storage.local.get('entries');
510627
const filteredEntries = filterEntries(entries, filterInput.value);
511628
downloadJson(filteredEntries);
512629
});
@@ -611,6 +728,54 @@ async function loadOptions() {
611728

612729
// Add test adding button handler
613730
document.getElementById('testAdding').addEventListener('click', testAdding);
731+
732+
// Import tab handlers
733+
let importItems = [];
734+
735+
document.getElementById('loadHistory').addEventListener('click', async () => {
736+
const days = parseInt(document.getElementById('historyDays').value, 10);
737+
window.importItems = await loadHistoryItems(days);
738+
const filter = document.getElementById('importFilter').value;
739+
const onlyNew = document.getElementById('showOnlyNew').checked;
740+
await updateImportList(window.importItems, filter, onlyNew);
741+
});
742+
743+
document.getElementById('loadBookmarks').addEventListener('click', async () => {
744+
window.importItems = await loadBookmarkItems();
745+
const filter = document.getElementById('importFilter').value;
746+
const onlyNew = document.getElementById('showOnlyNew').checked;
747+
await updateImportList(window.importItems, filter, onlyNew);
748+
});
749+
750+
document.getElementById('importFilter').addEventListener('input', async (e) => {
751+
const filter = e.target.value;
752+
const onlyNew = document.getElementById('showOnlyNew').checked;
753+
await updateImportList(window.importItems || [], filter, onlyNew);
754+
});
755+
756+
document.getElementById('showOnlyNew').addEventListener('change', async (e) => {
757+
const filter = document.getElementById('importFilter').value;
758+
const onlyNew = e.target.checked;
759+
await updateImportList(window.importItems || [], filter, onlyNew);
760+
});
761+
762+
document.getElementById('importList').addEventListener('change', updateSelectedCount);
763+
764+
document.getElementById('selectAll').addEventListener('click', () => {
765+
document.querySelectorAll('.import-check:not(:disabled)').forEach(checkbox => {
766+
checkbox.checked = true;
767+
});
768+
updateSelectedCount();
769+
});
770+
771+
document.getElementById('deselectAll').addEventListener('click', () => {
772+
document.querySelectorAll('.import-check').forEach(checkbox => {
773+
checkbox.checked = false;
774+
});
775+
updateSelectedCount();
776+
});
777+
778+
document.getElementById('importSelected').addEventListener('click', importSelected);
614779
}
615780

616781
document.addEventListener('DOMContentLoaded', loadOptions);

0 commit comments

Comments
 (0)