Refactor project name from "Voice KB" to "Faerro KB" across all files and update related configurations
This commit is contained in:
+50
-22
@@ -1,9 +1,9 @@
|
||||
const DB_NAME = 'voice-kb-local';
|
||||
const DB_NAME = 'faerro-kb-local';
|
||||
const STORE_NAME = 'recordings';
|
||||
const MAX_RECORDING_MS = 30 * 60 * 1000;
|
||||
const elements = Object.fromEntries([
|
||||
'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'capture-message',
|
||||
'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty', 'search-input',
|
||||
'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty',
|
||||
'network-state', 'date-label', 'storage-status',
|
||||
].map((id) => [id, document.getElementById(id)]));
|
||||
|
||||
@@ -13,7 +13,6 @@ let activeStream;
|
||||
let recordingChunks = [];
|
||||
let recordingStartedAt = 0;
|
||||
let recordingTimer;
|
||||
let searchTimer;
|
||||
let syncing = false;
|
||||
|
||||
function openDatabase() {
|
||||
@@ -138,6 +137,7 @@ async function syncQueue() {
|
||||
if (syncing || !navigator.onLine) return;
|
||||
syncing = true;
|
||||
elements['sync-button'].disabled = true;
|
||||
elements['queue-list'].querySelectorAll('.delete-note').forEach((button) => { button.disabled = true; });
|
||||
try {
|
||||
const recordings = await getQueued();
|
||||
for (const recording of recordings.sort((a, b) => a.createdAtMs - b.createdAtMs)) {
|
||||
@@ -159,7 +159,7 @@ async function syncQueue() {
|
||||
syncing = false;
|
||||
elements['sync-button'].disabled = false;
|
||||
await renderQueue();
|
||||
await loadArchive(elements['search-input'].value.trim());
|
||||
await loadAwaitingNotes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,18 +191,41 @@ async function renderQueue() {
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'item-meta';
|
||||
meta.textContent = `${prettyDate(recording.createdAt)} · ${formatDuration(recording.durationMs)}`;
|
||||
const remove = createDeleteButton(`Delete queued note ${recording.filename}`);
|
||||
remove.disabled = syncing;
|
||||
remove.addEventListener('click', async () => {
|
||||
if (syncing) return;
|
||||
remove.disabled = true;
|
||||
try {
|
||||
await deleteQueued(recording.id);
|
||||
await renderQueue();
|
||||
} catch {
|
||||
setMessage('Could not remove the queued recording.', true);
|
||||
remove.disabled = false;
|
||||
}
|
||||
});
|
||||
top.append(name, status);
|
||||
body.append(top, meta);
|
||||
item.append(icon, body);
|
||||
item.append(icon, body, remove);
|
||||
elements['queue-list'].append(item);
|
||||
}
|
||||
}
|
||||
|
||||
function createDeleteButton(label) {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'delete-note';
|
||||
button.type = 'button';
|
||||
button.textContent = '×';
|
||||
button.title = label;
|
||||
button.setAttribute('aria-label', label);
|
||||
return button;
|
||||
}
|
||||
|
||||
function renderArchive(notes) {
|
||||
elements['archive-list'].replaceChildren();
|
||||
elements['archive-empty'].hidden = notes.length > 0;
|
||||
if (!navigator.onLine) elements['archive-empty'].textContent = 'Reconnect to browse or search stored notes.';
|
||||
else elements['archive-empty'].textContent = elements['search-input'].value.trim() ? 'No matching transcripts.' : 'Your stored notes will appear here.';
|
||||
if (!navigator.onLine) elements['archive-empty'].textContent = 'Reconnect to view notes awaiting transcription.';
|
||||
else elements['archive-empty'].textContent = 'No notes awaiting transcription.';
|
||||
for (const note of notes) {
|
||||
const item = document.createElement('article');
|
||||
item.className = 'archive-item';
|
||||
@@ -230,8 +253,7 @@ function renderArchive(notes) {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transcript: transcript.value }),
|
||||
});
|
||||
if (!response.ok) throw new Error('Save failed');
|
||||
save.textContent = 'SAVED';
|
||||
await loadArchive(elements['search-input'].value.trim());
|
||||
await loadAwaitingNotes();
|
||||
} catch {
|
||||
save.textContent = 'RETRY SAVE';
|
||||
} finally {
|
||||
@@ -240,22 +262,32 @@ function renderArchive(notes) {
|
||||
});
|
||||
const status = document.createElement('div');
|
||||
status.className = 'archive-status';
|
||||
status.textContent = note.status.replaceAll('_', ' ').toUpperCase();
|
||||
status.textContent = 'AWAITING TRANSCRIPTION';
|
||||
const remove = createDeleteButton(`Delete note ${note.filename}`);
|
||||
remove.addEventListener('click', async () => {
|
||||
remove.disabled = true;
|
||||
try {
|
||||
const response = await fetch(`/api/notes/${encodeURIComponent(note.id)}`, { method: 'DELETE' });
|
||||
if (!response.ok) throw new Error('Delete failed');
|
||||
await loadAwaitingNotes();
|
||||
} catch {
|
||||
remove.disabled = false;
|
||||
setMessage('Could not delete this note. Please try again.', true);
|
||||
}
|
||||
});
|
||||
body.append(name, transcript, save, status);
|
||||
item.append(date, body);
|
||||
item.append(date, body, remove);
|
||||
elements['archive-list'].append(item);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadArchive(query = '') {
|
||||
async function loadAwaitingNotes() {
|
||||
if (!navigator.onLine) {
|
||||
renderArchive([]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const url = new URL('/api/notes', window.location.origin);
|
||||
if (query) url.searchParams.set('q', query);
|
||||
const response = await fetch(url);
|
||||
const response = await fetch('/api/notes?status=awaiting_transcription');
|
||||
if (!response.ok) throw new Error('Archive unavailable');
|
||||
renderArchive(await response.json());
|
||||
} catch {
|
||||
@@ -267,17 +299,13 @@ async function loadArchive(query = '') {
|
||||
|
||||
elements['record-button'].addEventListener('click', () => mediaRecorder?.state === 'recording' ? stopRecording() : startRecording());
|
||||
elements['sync-button'].addEventListener('click', syncQueue);
|
||||
elements['search-input'].addEventListener('input', () => {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => loadArchive(elements['search-input'].value.trim()), 250);
|
||||
});
|
||||
window.addEventListener('online', () => { updateNetwork(); syncQueue(); loadArchive(elements['search-input'].value.trim()); });
|
||||
window.addEventListener('online', () => { updateNetwork(); syncQueue(); loadAwaitingNotes(); });
|
||||
window.addEventListener('offline', updateNetwork);
|
||||
window.addEventListener('focus', () => { updateNetwork(); syncQueue(); loadArchive(elements['search-input'].value.trim()); });
|
||||
window.addEventListener('focus', () => { updateNetwork(); syncQueue(); loadAwaitingNotes(); });
|
||||
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') syncQueue(); });
|
||||
|
||||
elements['date-label'].textContent = new Intl.DateTimeFormat(undefined, { weekday: 'short', month: 'short', day: 'numeric' }).format(new Date()).toUpperCase();
|
||||
updateNetwork();
|
||||
renderQueue().then(syncQueue).catch(() => setMessage('Browser storage is unavailable. Check Safari storage settings.', true));
|
||||
loadArchive();
|
||||
loadAwaitingNotes();
|
||||
if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js').catch(() => {});
|
||||
Reference in New Issue
Block a user