mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-21 18:55:36 +02:00
feat: browser capture button (Track 4)
- Add archivr-core/src/capture.rs: Source enum, all capture helpers,
fail_run (replaces process::exit fail_archive_and_exit), and
perform_capture() as the public entry point
- Slim archivr-cli/src/main.rs to 103 lines: thin adapter over
archivr_core::capture::perform_capture
- Move all source-classification and tweet-entry tests from CLI to
archivr-core/src/capture.rs (they test core logic, belong in core)
- Add POST /api/archives/:archive_id/captures route in archivr-server:
validates non-empty locator (400), resolves archive (404), delegates
to perform_capture, returns {run_uid, status}
- Add capture dialog to browser UI: dialog markup in index.html,
showModal/submit/cancel/loading/error wiring in app.js, dialog
styles in styles.css
- 77 tests pass, clean build (0 warnings)
This commit is contained in:
parent
b70ab4945e
commit
8d0352c2c4
7 changed files with 1415 additions and 1227 deletions
|
|
@ -21,6 +21,12 @@ const entryTagsEl = document.querySelector("#entry-tags");
|
|||
const assignTagForm = document.querySelector("#assign-tag-form");
|
||||
const assignTagInput = document.querySelector("#assign-tag-input");
|
||||
const assignTagBtn = document.querySelector("#assign-tag-btn");
|
||||
const captureButton = document.querySelector('.capture-button');
|
||||
const captureDialog = document.querySelector('#capture-dialog');
|
||||
const captureLocatorInput = document.querySelector('#capture-locator');
|
||||
const captureSubmitBtn = document.querySelector('#capture-submit-btn');
|
||||
const captureCancelBtn = document.querySelector('#capture-cancel-btn');
|
||||
const captureError = document.querySelector('#capture-error');
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!bytes) return "0 B";
|
||||
|
|
@ -425,6 +431,45 @@ assignTagBtn.addEventListener("click", async () => {
|
|||
}
|
||||
});
|
||||
|
||||
captureButton.addEventListener('click', () => {
|
||||
captureLocatorInput.value = '';
|
||||
captureError.hidden = true;
|
||||
captureDialog.showModal();
|
||||
});
|
||||
|
||||
captureCancelBtn.addEventListener('click', () => captureDialog.close());
|
||||
|
||||
captureSubmitBtn.addEventListener('click', async () => {
|
||||
const locator = captureLocatorInput.value.trim();
|
||||
if (!locator) {
|
||||
captureError.textContent = 'Enter a locator.';
|
||||
captureError.hidden = false;
|
||||
return;
|
||||
}
|
||||
captureSubmitBtn.disabled = true;
|
||||
captureSubmitBtn.textContent = 'Capturing\u2026';
|
||||
captureError.hidden = true;
|
||||
try {
|
||||
const res = await fetch(`/api/archives/${state.archiveId}/captures`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ locator }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const msg = await res.text();
|
||||
throw new Error(msg || `HTTP ${res.status}`);
|
||||
}
|
||||
captureDialog.close();
|
||||
await Promise.all([loadEntries(searchInput.value), loadRuns()]);
|
||||
} catch (e) {
|
||||
captureError.textContent = e.message;
|
||||
captureError.hidden = false;
|
||||
} finally {
|
||||
captureSubmitBtn.disabled = false;
|
||||
captureSubmitBtn.textContent = 'Capture';
|
||||
}
|
||||
});
|
||||
|
||||
loadArchives().catch((error) => {
|
||||
contextBody.textContent = `Failed to load archives: ${error.message}`;
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue