From cb70222c04cd889e087dcedfc13462c4674ce411 Mon Sep 17 00:00:00 2001 From: naielv <109038805+naielv@users.noreply.github.com> Date: Fri, 26 Dec 2025 01:32:48 +0100 Subject: [PATCH] updates --- src/db.js | 64 +++++++++++++++++++++++++++ src/page/notas.js | 107 +++++++++++++++++++++++++++++++++++++++++++--- src/sw.js | 15 +++++-- 3 files changed, 177 insertions(+), 9 deletions(-) diff --git a/src/db.js b/src/db.js index 41be54d..e6c4efb 100644 --- a/src/db.js +++ b/src/db.js @@ -227,6 +227,68 @@ var DB = (function () { } } + // List all attachments for a document returning array of { name, dataUrl, content_type } + async function listAttachments(table, id) { + ensureLocal(); + const _id = makeId(table, id); + try { + const doc = await local.get(_id, { attachments: true }); + if (!doc || !doc._attachments) return []; + const out = []; + for (const name of Object.keys(doc._attachments)) { + try { + const att = doc._attachments[name]; + if (att && att.data) { + const content_type = att.content_type || 'application/octet-stream'; + const durl = 'data:' + content_type + ';base64,' + att.data; + out.push({ name: name, dataUrl: durl, content_type: content_type }); + continue; + } + } catch (e) {} + // fallback: convert blob to dataURL using getAttachment + try { + const durl = await getAttachment(table, id, name); + out.push({ name: name, dataUrl: durl, content_type: null }); + } catch (e) { + out.push({ name: name, dataUrl: null, content_type: null }); + } + } + return out; + } catch (e) { + // if attachments:true not supported or error, try to get doc without attachments and then getAttachment for names + try { + const doc = await local.get(_id).catch(() => null); + if (!doc || !doc._attachments) return []; + const out = []; + for (const name of Object.keys(doc._attachments)) { + try { + const durl = await getAttachment(table, id, name); + out.push({ name: name, dataUrl: durl, content_type: null }); + } catch (e) { out.push({ name: name, dataUrl: null, content_type: null }); } + } + return out; + } catch (e2) { + return []; + } + } + } + + // Delete attachment metadata from the document (removes _attachments entry) + async function deleteAttachment(table, id, name) { + ensureLocal(); + const _id = makeId(table, id); + try { + const doc = await local.get(_id); + if (!doc || !doc._attachments || !doc._attachments[name]) return false; + delete doc._attachments[name]; + await local.put(doc); + return true; + } catch (e) { + console.error('deleteAttachment error', e); + return false; + } + } + function map(table, cb) { ensureLocal(); callbacks[table] = callbacks[table] || []; @@ -247,6 +309,8 @@ var DB = (function () { list, map, replicateToRemote, + listAttachments, + deleteAttachment, putAttachment, getAttachment, _internal: { local } diff --git a/src/page/notas.js b/src/page/notas.js index b2f9513..72bd511 100644 --- a/src/page/notas.js +++ b/src/page/notas.js @@ -11,6 +11,8 @@ PAGES.notas = { var field_asunto = safeuuid(); var field_contenido = safeuuid(); var field_autor = safeuuid(); + var field_files = safeuuid(); + var attachments_list = safeuuid(); var btn_guardar = safeuuid(); var btn_borrar = safeuuid(); var div_actions = safeuuid(); @@ -30,6 +32,11 @@ PAGES.notas = { Contenido


+
@@ -64,6 +71,17 @@ PAGES.notas = { }, "Autor" ); + // Mostrar adjuntos existentes (si los hay). + // No confiar en `data._attachments` porque `DB.get` devuelve solo `doc.data`. + const attachContainer = document.getElementById(attachments_list); + attachContainer.innerHTML = ""; + // Usar API de DB para listar attachments (no acceder a internals desde la UI) + DB.listAttachments('notas', mid).then((list) => { + if (!list || !Array.isArray(list)) return; + list.forEach((att) => { + addAttachmentRow(att.name, att.dataUrl); + }); + }).catch((e) => { console.warn('listAttachments error', e); }); } if (typeof data == "string") { TS_decrypt(data, SECRET, (data) => { @@ -73,6 +91,50 @@ PAGES.notas = { load_data(data || {}); } }); + // gestión de archivos seleccionados antes de guardar + const attachmentsToUpload = []; + function addAttachmentRow(name, url) { + const attachContainer = document.getElementById(attachments_list); + const idRow = safeuuid(); + const isImage = url && url.indexOf('data:image') === 0; + const preview = isImage ? `` : `${name}`; + const html = ` +
+
${preview}${name}
+
+
`; + attachContainer.insertAdjacentHTML('beforeend', html); + attachContainer.querySelectorAll(`button[data-name="${name}"]`).forEach((btn) => { + btn.onclick = () => { + if (!confirm('¿Borrar este adjunto?')) return; + // Usar API pública en DB para borrar metadata del attachment + DB.deleteAttachment('notas', mid, name).then((ok) => { + if (ok) { + document.getElementById(idRow).remove(); + toastr.error('Adjunto borrado'); + } else { + toastr.error('No se pudo borrar el adjunto'); + } + }).catch((e) => { console.warn('deleteAttachment error', e); toastr.error('Error borrando adjunto'); }); + }; + }); + } + + document.getElementById(field_files).addEventListener('change', function (e) { + const files = Array.from(e.target.files || []); + files.forEach((file) => { + const reader = new FileReader(); + reader.onload = function (ev) { + const dataUrl = ev.target.result; + attachmentsToUpload.push({ name: file.name, data: dataUrl, type: file.type || 'application/octet-stream' }); + // mostrar preview temporal + addAttachmentRow(file.name, dataUrl); + }; + reader.readAsDataURL(file); + }); + // limpiar input para permitir re-subidas del mismo archivo + e.target.value = ''; + }); document.getElementById(btn_guardar).onclick = () => { var data = { Autor: document.getElementById(field_autor).value, @@ -81,12 +143,45 @@ PAGES.notas = { }; document.getElementById("actionStatus").style.display = "block"; DB.put('notas', mid, data).then(() => { - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("notas"); - }, SAVE_WAIT); - }).catch((e) => { console.warn('DB.put error', e); }); + // subir attachments si los hay + const uploadPromises = []; + attachmentsToUpload.forEach((att) => { + if (DB.putAttachment) { + uploadPromises.push(DB.putAttachment('notas', mid, att.name, att.data, att.type).catch((e) => { console.warn('putAttachment error', e); })); + } + }); + Promise.all(uploadPromises).then(() => { + // limpiar lista temporal y recargar attachments + attachmentsToUpload.length = 0; + try { // recargar lista actual sin salir + const pouchId = 'notas:' + mid; + if (DB && DB._internal && DB._internal.local) { + DB._internal.local.get(pouchId, { attachments: true }).then((doc) => { + const attachContainer = document.getElementById(attachments_list); + attachContainer.innerHTML = ''; + if (doc && doc._attachments) { + Object.keys(doc._attachments).forEach((name) => { + try { + const att = doc._attachments[name]; + if (att && att.data) { + const durl = 'data:' + (att.content_type || 'application/octet-stream') + ';base64,' + att.data; + addAttachmentRow(name, durl); + return; + } + } catch (e) {} + DB.getAttachment('notas', mid, name).then((durl) => { addAttachmentRow(name, durl); }).catch(() => {}); + }); + } + }).catch(() => { /* ignore reload errors */ }); + } + } catch (e) {} + toastr.success("Guardado!"); + setTimeout(() => { + document.getElementById("actionStatus").style.display = "none"; + setUrlHash("notas"); + }, SAVE_WAIT); + }).catch((e) => { console.warn('Attachment upload error', e); document.getElementById("actionStatus").style.display = "none"; }); + }).catch((e) => { console.warn('DB.put error', e); document.getElementById("actionStatus").style.display = "none"; }); }; document.getElementById(btn_borrar).onclick = () => { if (confirm("¿Quieres borrar esta nota?") == true) { diff --git a/src/sw.js b/src/sw.js index 5cec659..1b1fe99 100644 --- a/src/sw.js +++ b/src/sw.js @@ -9,9 +9,18 @@ self.addEventListener("message", (event) => { } }); +// workbox.routing.registerRoute( +// new RegExp("/*"), +// new workbox.strategies.StaleWhileRevalidate({ +// cacheName: CACHE, +// }) +// ); + +// All but couchdb workbox.routing.registerRoute( - new RegExp("/*"), - new workbox.strategies.StaleWhileRevalidate({ + ({ url }) => + !url.pathname.startsWith("/_couchdb/") && url.origin === self.location.origin, + new workbox.strategies.NetworkFirst({ cacheName: CACHE, }) -); +); \ No newline at end of file