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