updates
This commit is contained in:
64
src/db.js
64
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) {
|
function map(table, cb) {
|
||||||
ensureLocal();
|
ensureLocal();
|
||||||
callbacks[table] = callbacks[table] || [];
|
callbacks[table] = callbacks[table] || [];
|
||||||
@@ -247,6 +309,8 @@ var DB = (function () {
|
|||||||
list,
|
list,
|
||||||
map,
|
map,
|
||||||
replicateToRemote,
|
replicateToRemote,
|
||||||
|
listAttachments,
|
||||||
|
deleteAttachment,
|
||||||
putAttachment,
|
putAttachment,
|
||||||
getAttachment,
|
getAttachment,
|
||||||
_internal: { local }
|
_internal: { local }
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ PAGES.notas = {
|
|||||||
var field_asunto = safeuuid();
|
var field_asunto = safeuuid();
|
||||||
var field_contenido = safeuuid();
|
var field_contenido = safeuuid();
|
||||||
var field_autor = safeuuid();
|
var field_autor = safeuuid();
|
||||||
|
var field_files = safeuuid();
|
||||||
|
var attachments_list = safeuuid();
|
||||||
var btn_guardar = safeuuid();
|
var btn_guardar = safeuuid();
|
||||||
var btn_borrar = safeuuid();
|
var btn_borrar = safeuuid();
|
||||||
var div_actions = safeuuid();
|
var div_actions = safeuuid();
|
||||||
@@ -30,6 +32,11 @@ PAGES.notas = {
|
|||||||
Contenido<br>
|
Contenido<br>
|
||||||
<textarea id="${field_contenido}" style="width: calc(100% - 15px); height: 400px;"></textarea><br><br>
|
<textarea id="${field_contenido}" style="width: calc(100% - 15px); height: 400px;"></textarea><br><br>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
Adjuntos (Fotos o archivos)<br>
|
||||||
|
<input type="file" id="${field_files}" multiple><br><br>
|
||||||
|
<div id="${attachments_list}"></div>
|
||||||
|
</label>
|
||||||
<hr>
|
<hr>
|
||||||
<button class="btn5" id="${btn_guardar}">Guardar</button>
|
<button class="btn5" id="${btn_guardar}">Guardar</button>
|
||||||
<button class="rojo" id="${btn_borrar}">Borrar</button>
|
<button class="rojo" id="${btn_borrar}">Borrar</button>
|
||||||
@@ -64,6 +71,17 @@ PAGES.notas = {
|
|||||||
},
|
},
|
||||||
"Autor"
|
"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") {
|
if (typeof data == "string") {
|
||||||
TS_decrypt(data, SECRET, (data) => {
|
TS_decrypt(data, SECRET, (data) => {
|
||||||
@@ -73,6 +91,50 @@ PAGES.notas = {
|
|||||||
load_data(data || {});
|
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 = () => {
|
document.getElementById(btn_guardar).onclick = () => {
|
||||||
var data = {
|
var data = {
|
||||||
Autor: document.getElementById(field_autor).value,
|
Autor: document.getElementById(field_autor).value,
|
||||||
@@ -81,12 +143,45 @@ PAGES.notas = {
|
|||||||
};
|
};
|
||||||
document.getElementById("actionStatus").style.display = "block";
|
document.getElementById("actionStatus").style.display = "block";
|
||||||
DB.put('notas', mid, data).then(() => {
|
DB.put('notas', mid, data).then(() => {
|
||||||
|
// 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!");
|
toastr.success("Guardado!");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.getElementById("actionStatus").style.display = "none";
|
document.getElementById("actionStatus").style.display = "none";
|
||||||
setUrlHash("notas");
|
setUrlHash("notas");
|
||||||
}, SAVE_WAIT);
|
}, SAVE_WAIT);
|
||||||
}).catch((e) => { console.warn('DB.put error', e); });
|
}).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 = () => {
|
document.getElementById(btn_borrar).onclick = () => {
|
||||||
if (confirm("¿Quieres borrar esta nota?") == true) {
|
if (confirm("¿Quieres borrar esta nota?") == true) {
|
||||||
|
|||||||
13
src/sw.js
13
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(
|
workbox.routing.registerRoute(
|
||||||
new RegExp("/*"),
|
({ url }) =>
|
||||||
new workbox.strategies.StaleWhileRevalidate({
|
!url.pathname.startsWith("/_couchdb/") && url.origin === self.location.origin,
|
||||||
|
new workbox.strategies.NetworkFirst({
|
||||||
cacheName: CACHE,
|
cacheName: CACHE,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
Reference in New Issue
Block a user