(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, ['get', 'list'].includes(operation) ? 'readonly' : 'readwrite'); const records = transaction.objectStore(storeName); if (operation === 'put') return requestResult(records.put(value)); if (operation === 'delete') return requestResult(records.delete(key)); if (operation === 'list') return requestResult(records.getAll()); 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, updatedAt:Date.now(), 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; } async function list() { if (!transact || draftScope !== 'conversation') return []; const ownerLogin = String(getOwnerLogin() || '').trim(); if (!ownerLogin) return []; const records = await transact('list'); return (Array.isArray(records) ? records : []).flatMap(record => { if (record?.version !== 1 || record.ownerLogin !== ownerLogin || (record.scope && record.scope !== 'conversation') || !Array.isArray(record.attachments) || !record.attachments.length) return []; const updatedAt = Number(record.updatedAt || 0); if (record.kind === 'update') { const notificationId = Number(record.notificationId || 0); if (!Number.isInteger(notificationId) || notificationId < 1) return []; return [{ id:record.id, kind:'photo-reply', label:'Update photos', title:'Update #' + notificationId, photo_count:record.attachments.length, updated_at:updatedAt, route:{ kind:'update', notification_id:notificationId }, photo_store:'conversation', }]; } const repository = String(record.repository || ''); const number = Number(record.number || 0); if (!['issue', 'pull'].includes(record.kind) || !repository || !Number.isInteger(number) || number < 1) return []; const label = record.kind === 'issue' ? 'Issue photos' : 'PR photos'; return [{ id:record.id, kind:'photo-reply', label, repository, number, title:repository + '#' + number, photo_count:record.attachments.length, updated_at:updatedAt, route:{ kind:record.kind, repository, number }, photo_store:'conversation', }]; }).sort((left, right) => Number(right.updated_at) - Number(left.updated_at) || left.id.localeCompare(right.id)); } return { save, load, remove, list }; }; });