107 lines
4.6 KiB
JavaScript
107 lines
4.6 KiB
JavaScript
function createUnfiledCaptures({
|
|
storage,
|
|
attachmentStore = null,
|
|
getCaptureLogin = () => '',
|
|
getCurrentLogin = () => '',
|
|
createId = () => globalThis.crypto?.randomUUID?.() || String(Date.now()) + '-' + Math.random().toString(16).slice(2),
|
|
now = () => Date.now(),
|
|
maxItems = 20,
|
|
}) {
|
|
const storageKey = 'stackchain.unfiled-issues.v1';
|
|
|
|
function read() {
|
|
try {
|
|
const record = JSON.parse(storage?.getItem(storageKey) || 'null');
|
|
if (record?.version !== 1 || !Array.isArray(record.items)) return [];
|
|
return record.items.filter(item =>
|
|
item && typeof item.id === 'string' && typeof item.ownerLogin === 'string' &&
|
|
typeof item.title === 'string' && item.title.trim() && typeof item.body === 'string'
|
|
);
|
|
} catch (_error) { return []; }
|
|
}
|
|
|
|
function write(items) {
|
|
storage?.setItem(storageKey, JSON.stringify({version:1, items}));
|
|
}
|
|
|
|
function list() {
|
|
const currentLogin = String(getCurrentLogin() || '').trim();
|
|
return read().slice().sort((left, right) => Number(right.savedAt) - Number(left.savedAt))
|
|
.map(item => ({...item, quarantined: !currentLogin || currentLogin !== item.ownerLogin}));
|
|
}
|
|
|
|
function save(note) {
|
|
const title = String(note?.title || '').trim().slice(0, 255);
|
|
const body = String(note?.body || '').trim().slice(0, 10000);
|
|
if (!title) throw new Error('Add a title before saving.');
|
|
const ownerLogin = String(getCaptureLogin() || '').trim();
|
|
if (!ownerLogin) throw new Error('Offline identity is unavailable. Reconnect once before saving private work.');
|
|
const attachment = note?.attachment;
|
|
const hasAttachment = Boolean(attachment?.blob && attachment?.filename &&
|
|
['image/png', 'image/jpeg', 'image/webp'].includes(String(attachment?.contentType || '')));
|
|
if (attachment && !hasAttachment) throw new Error('The screenshot is unavailable. Choose it again before saving.');
|
|
if (hasAttachment && !attachmentStore) {
|
|
throw new Error('Screenshot storage is unavailable. Your capture is still open; retry after reloading.');
|
|
}
|
|
const item = {
|
|
id:String(createId()), ownerLogin, title, body, savedAt:Number(now()),
|
|
...(hasAttachment ? {hasAttachment:true} : {}),
|
|
};
|
|
const existing = read().filter(candidate => candidate.id !== item.id);
|
|
const items = [item, ...existing].slice(0, maxItems);
|
|
const pruned = existing.filter(candidate => !items.some(retained => retained.id === candidate.id));
|
|
if (!hasAttachment) {
|
|
write(items);
|
|
pruned.filter(candidate => candidate.hasAttachment).forEach(candidate =>
|
|
Promise.resolve(attachmentStore?.delete(candidate.id)).catch(() => {}));
|
|
return item;
|
|
}
|
|
return Promise.resolve(attachmentStore.put(item.id, {
|
|
filename:String(attachment.filename).slice(0, 255),
|
|
contentType:String(attachment.contentType), blob:attachment.blob,
|
|
})).then(() => {
|
|
try { write(items); }
|
|
catch (error) {
|
|
Promise.resolve(attachmentStore.delete(item.id)).catch(() => {});
|
|
throw error;
|
|
}
|
|
pruned.filter(candidate => candidate.hasAttachment).forEach(candidate =>
|
|
Promise.resolve(attachmentStore.delete(candidate.id)).catch(() => {}));
|
|
return item;
|
|
});
|
|
}
|
|
|
|
function discard(id) {
|
|
const items = read();
|
|
const remaining = items.filter(item => item.id !== id);
|
|
if (remaining.length === items.length) return false;
|
|
write(remaining);
|
|
const removed = items.find(item => item.id === id);
|
|
if (!removed?.hasAttachment) return true;
|
|
return Promise.resolve(attachmentStore?.delete(id)).then(() => true);
|
|
}
|
|
|
|
function resume(id, confirmedLogin) {
|
|
const item = read().find(candidate => candidate.id === id);
|
|
if (!item) throw new Error('This capture is no longer available.');
|
|
if (!confirmedLogin || String(confirmedLogin).trim() !== item.ownerLogin) {
|
|
throw new Error('Reconnect with the account that saved this capture.');
|
|
}
|
|
const draft = {repository:'', title:item.title, body:item.body, labelIds:[]};
|
|
if (!item.hasAttachment) return draft;
|
|
if (!attachmentStore) throw new Error('The saved screenshot is unavailable. Retry after reloading.');
|
|
return Promise.resolve(attachmentStore.get(id)).then(attachment => {
|
|
if (!attachment?.blob || !attachment?.filename || !attachment?.contentType) {
|
|
throw new Error('The saved screenshot is unavailable. Keep this Draft and retry.');
|
|
}
|
|
return {...draft, attachment};
|
|
});
|
|
}
|
|
|
|
function completeResume(id) { return discard(id); }
|
|
|
|
return {list, save, discard, resume, completeResume};
|
|
}
|
|
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = createUnfiledCaptures;
|