This commit is contained in:
naielv
2025-12-26 01:32:48 +01:00
parent dfcd22fadf
commit cb70222c04
3 changed files with 177 additions and 9 deletions

View File

@@ -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 }

View File

@@ -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<br>
<textarea id="${field_contenido}" style="width: calc(100% - 15px); height: 400px;"></textarea><br><br>
</label>
<label>
Adjuntos (Fotos o archivos)<br>
<input type="file" id="${field_files}" multiple><br><br>
<div id="${attachments_list}"></div>
</label>
<hr>
<button class="btn5" id="${btn_guardar}">Guardar</button>
<button class="rojo" id="${btn_borrar}">Borrar</button>
@@ -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 ? `<img src="${url}" height="80" style="margin-right:8px;">` : `<a href="${url}" target="_blank">${name}</a>`;
const html = `
<div id="${idRow}" style="display:flex;align-items:center;margin:6px 0;border:1px solid #ddd;padding:6px;border-radius:6px;">
<div style="flex:1">${preview}<strong style="margin-left:8px">${name}</strong></div>
<div><button type="button" class="rojo" data-name="${name}">Borrar</button></div>
</div>`;
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) {

View File

@@ -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,
})
);