147 lines
6.2 KiB
JavaScript
147 lines
6.2 KiB
JavaScript
(function (root, factory) {
|
|
const createSearchReplyDraftStore = factory();
|
|
if (typeof module === 'object' && module.exports) module.exports = createSearchReplyDraftStore;
|
|
else root.createSearchReplyDraftStore = createSearchReplyDraftStore;
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
'use strict';
|
|
|
|
const dbName = 'stackchain-search-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('Search reply 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('Search reply 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 || '');
|
|
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('Choose a Search result 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 Search 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 createSearchReplyDraftStore({
|
|
indexedDB = globalThis.indexedDB,
|
|
transaction,
|
|
getOwnerLogin = () => '',
|
|
} = {}) {
|
|
const transact = transaction || createTransaction(indexedDB);
|
|
|
|
function identity(target) {
|
|
const ownerLogin = String(getOwnerLogin() || '').trim();
|
|
if (!ownerLogin) throw new Error('Confirm your Gitea account before saving Search photos.');
|
|
const normalized = normalizedTarget(target);
|
|
return {
|
|
ownerLogin,
|
|
target:normalized,
|
|
id:[ownerLogin, normalized.kind, normalized.repository, normalized.number]
|
|
.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, 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, ...normalized, updatedAt:Date.now(), attachments:list,
|
|
});
|
|
return list;
|
|
}
|
|
|
|
async function load(target) {
|
|
if (!transact) return null;
|
|
const { id, ownerLogin, target:normalized } = identity(target);
|
|
const record = await transact('get', id);
|
|
if (record?.version !== 1 || record.ownerLogin !== ownerLogin || record.kind !== normalized.kind ||
|
|
record.repository !== normalized.repository || Number(record.number) !== normalized.number ||
|
|
!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) return [];
|
|
const ownerLogin = String(getOwnerLogin() || '').trim();
|
|
if (!ownerLogin) return [];
|
|
const records = await transact('list');
|
|
return (Array.isArray(records) ? records : []).flatMap(record => {
|
|
const repository = String(record?.repository || '');
|
|
const number = Number(record?.number || 0);
|
|
if (record?.version !== 1 || record.ownerLogin !== ownerLogin ||
|
|
!['issue', 'pull'].includes(record.kind) || !repository ||
|
|
!Number.isInteger(number) || number < 1 ||
|
|
!Array.isArray(record.attachments) || !record.attachments.length) return [];
|
|
return [{
|
|
id:record.id, kind:'photo-reply',
|
|
label:'Search ' + (record.kind === 'pull' ? 'PR' : 'issue') + ' photos',
|
|
repository, number, title:repository + '#' + number,
|
|
photo_count:record.attachments.length, updated_at:Number(record.updatedAt || 0),
|
|
route:{ kind:'search', target_kind:record.kind, repository, number }, photo_store:'search',
|
|
}];
|
|
}).sort((left, right) => Number(right.updated_at) - Number(left.updated_at) || left.id.localeCompare(right.id));
|
|
}
|
|
|
|
return { save, load, remove, list };
|
|
};
|
|
});
|