128 lines
5.9 KiB
JavaScript
128 lines
5.9 KiB
JavaScript
(function (root, factory) {
|
|
const createConversationReplyDraftStore = factory();
|
|
if (typeof module === 'object' && module.exports) module.exports = createConversationReplyDraftStore;
|
|
else root.createConversationReplyDraftStore = createConversationReplyDraftStore;
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
'use strict';
|
|
|
|
const dbName = 'stackchain-conversation-reply-drafts-v1';
|
|
const storeName = 'drafts';
|
|
|
|
function requestResult(request) {
|
|
return new Promise((resolve, reject) => {
|
|
request.onsuccess = () => resolve(request.result ?? null);
|
|
request.onerror = () => reject(request.error || new Error('Conversation photo draft storage failed.'));
|
|
});
|
|
}
|
|
|
|
function createTransaction(indexedDB) {
|
|
if (!indexedDB) return null;
|
|
let databasePromise;
|
|
function database() {
|
|
if (!databasePromise) databasePromise = new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(dbName, 1);
|
|
request.onupgradeneeded = () => {
|
|
if (!request.result.objectStoreNames.contains(storeName)) {
|
|
request.result.createObjectStore(storeName, { keyPath:'id' });
|
|
}
|
|
};
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onerror = () => reject(request.error || new Error('Conversation photo draft storage is unavailable.'));
|
|
});
|
|
return databasePromise;
|
|
}
|
|
return async (operation, key, value) => {
|
|
const db = await database();
|
|
const transaction = db.transaction(storeName, operation === 'get' ? 'readonly' : 'readwrite');
|
|
const records = transaction.objectStore(storeName);
|
|
if (operation === 'put') return requestResult(records.put(value));
|
|
if (operation === 'delete') return requestResult(records.delete(key));
|
|
return requestResult(records.get(key));
|
|
};
|
|
}
|
|
|
|
function normalizedTarget(target) {
|
|
const kind = String(target?.kind || '');
|
|
if (kind === 'update') {
|
|
const notificationId = Number(target?.notificationId || target?.notification_id || 0);
|
|
if (!Number.isInteger(notificationId) || notificationId < 1) {
|
|
throw new Error('Open an unread update before saving photo evidence.');
|
|
}
|
|
return { kind, notificationId };
|
|
}
|
|
const repository = String(target?.repository || '');
|
|
const number = Number(target?.number || 0);
|
|
if (!['issue', 'pull'].includes(kind) || !repository || !Number.isInteger(number) || number < 1) {
|
|
throw new Error('Open a conversation before saving photo evidence.');
|
|
}
|
|
return { kind, repository, number };
|
|
}
|
|
|
|
function attachment(value) {
|
|
const blob = value?.blob;
|
|
const data = String(value?.data || '');
|
|
if (!blob && !data) throw new Error('A saved conversation photo is unavailable.');
|
|
const note = String(value?.note || '').replace(/\s+/g, ' ').trim().slice(0, 240);
|
|
const operationId = String(value?.operationId || '').slice(0, 128);
|
|
const markdown = String(value?.confirmed?.markdown || '');
|
|
return {
|
|
filename:String(value?.filename || ''), contentType:String(value?.contentType || ''),
|
|
...(blob ? { blob } : { data }), ...(note ? { note } : {}),
|
|
...(operationId ? { operationId } : {}), ...(markdown ? { confirmed:{ markdown } } : {}),
|
|
};
|
|
}
|
|
|
|
return function createConversationReplyDraftStore({
|
|
indexedDB = globalThis.indexedDB, transaction, getOwnerLogin = () => '', scope = 'conversation',
|
|
} = {}) {
|
|
const transact = transaction || createTransaction(indexedDB);
|
|
const draftScope = String(scope || 'conversation').trim().slice(0, 64) || 'conversation';
|
|
|
|
function identity(target) {
|
|
const ownerLogin = String(getOwnerLogin() || '').trim();
|
|
if (!ownerLogin) throw new Error('Confirm your Gitea account before saving conversation photos.');
|
|
const normalized = normalizedTarget(target);
|
|
const targetKey = normalized.kind === 'update' ? normalized.notificationId :
|
|
normalized.repository + ':' + normalized.number;
|
|
const identityParts = draftScope === 'conversation' ? [ownerLogin, normalized.kind, targetKey] :
|
|
[ownerLogin, draftScope, normalized.kind, targetKey];
|
|
return {
|
|
ownerLogin, scope:draftScope, target:normalized,
|
|
id:identityParts.map(value => encodeURIComponent(String(value))).join(':'),
|
|
};
|
|
}
|
|
|
|
async function save(target, values) {
|
|
if (!transact) throw new Error('Photo draft storage needs IndexedDB. Your current photos are still here.');
|
|
const { id, ownerLogin, scope:recordScope, target:normalized } = identity(target);
|
|
const list = (Array.isArray(values) ? values : [values]).filter(Boolean).slice(0, 5).map(attachment);
|
|
if (!list.length) { await transact('delete', id); return null; }
|
|
await transact('put', id, { id, version:1, ownerLogin, scope:recordScope, ...normalized, attachments:list });
|
|
return list;
|
|
}
|
|
|
|
async function load(target) {
|
|
if (!transact) return null;
|
|
const { id, ownerLogin, scope:recordScope, target:normalized } = identity(target);
|
|
const record = await transact('get', id);
|
|
const scopeMatches = recordScope === 'conversation' ? (!record?.scope || record.scope === recordScope) :
|
|
record?.scope === recordScope;
|
|
const same = record?.version === 1 && record.ownerLogin === ownerLogin && scopeMatches &&
|
|
record.kind === normalized.kind && (normalized.kind === 'update' ?
|
|
Number(record.notificationId) === normalized.notificationId :
|
|
record.repository === normalized.repository && Number(record.number) === normalized.number);
|
|
if (!same || !Array.isArray(record.attachments) || !record.attachments.length) return null;
|
|
return record.attachments.slice(0, 5).map(attachment);
|
|
}
|
|
|
|
async function remove(target) {
|
|
if (!transact) return false;
|
|
const { id } = identity(target);
|
|
await transact('delete', id);
|
|
return true;
|
|
}
|
|
|
|
return { save, load, remove };
|
|
};
|
|
});
|