88 lines
3.4 KiB
JavaScript
88 lines
3.4 KiB
JavaScript
function createUpdateFollowUp({ storage = null, getLogin = () => '' } = {}) {
|
|
const sourceKey = 'stackchain.update-follow-up-source.v1';
|
|
const clean = (value, limit) => String(value || '').replace(/\s+/g, ' ').trim().slice(0, limit);
|
|
const safeRepository = value => {
|
|
const candidate = String(value || '');
|
|
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(candidate) &&
|
|
candidate.split('/').every(part => part !== '.' && part !== '..') ? candidate : '';
|
|
};
|
|
const safeUrl = value => {
|
|
try {
|
|
const parsed = new URL(String(value || ''));
|
|
return parsed.protocol === 'https:' || parsed.protocol === 'http:' ? parsed.href : '';
|
|
} catch (_error) { return ''; }
|
|
};
|
|
|
|
function draft(detail = {}) {
|
|
const source = safeUrl(detail.url || detail.latest_comment?.url);
|
|
const context = clean(detail.latest_comment?.body || detail.subject_body, 9000);
|
|
const title = clean(detail.title, 228);
|
|
const sections = [];
|
|
if (source) sections.push('Source: ' + source);
|
|
if (context) sections.push('Latest context:\n> ' + context.replace(/\n/g, '\n> '));
|
|
return {
|
|
repository: safeRepository(detail.repository),
|
|
title: ('Follow up: ' + (title || 'Unread update')).slice(0, 240),
|
|
body: sections.join('\n\n').slice(0, 9500),
|
|
};
|
|
}
|
|
|
|
function readSource() {
|
|
const login = String(getLogin() || '').trim();
|
|
if (!login) return null;
|
|
try {
|
|
const record = JSON.parse(storage?.getItem(sourceKey) || 'null');
|
|
return record?.version === 1 && record.ownerLogin === login &&
|
|
Number.isInteger(record.source?.notification_id) && record.source.notification_id > 0 ? record : null;
|
|
} catch (_error) { return null; }
|
|
}
|
|
|
|
function writeSource(record) {
|
|
storage?.setItem(sourceKey, JSON.stringify(record));
|
|
}
|
|
|
|
function stageSource(item = {}) {
|
|
const ownerLogin = String(getLogin() || '').trim();
|
|
const notificationId = Number(item.notification_id);
|
|
if (!ownerLogin) throw new Error('Confirm your Gitea account before continuing this follow-up.');
|
|
if (!Number.isInteger(notificationId) || notificationId <= 0) throw new Error('Choose a valid source update.');
|
|
const source = {
|
|
notification_id: notificationId,
|
|
repository: safeRepository(item.repository),
|
|
number: Number.isInteger(item.number) && item.number > 0 ? item.number : null,
|
|
title: clean(item.title, 240), kind: 'update', has_update: true,
|
|
};
|
|
writeSource({ version:1, ownerLogin, source, admission:null });
|
|
return { ...source };
|
|
}
|
|
|
|
function source() {
|
|
const record = readSource();
|
|
return record ? { ...record.source } : null;
|
|
}
|
|
|
|
function discardSource() {
|
|
if (!readSource()) return false;
|
|
storage?.removeItem(sourceKey);
|
|
return true;
|
|
}
|
|
|
|
async function complete({ admit, queueRead, advance }) {
|
|
const record = readSource();
|
|
if (!record) throw new Error('Reopen the source update before continuing.');
|
|
let admission = record.admission;
|
|
if (!admission) {
|
|
admission = await admit();
|
|
writeSource({ ...record, admission });
|
|
}
|
|
await queueRead(record.source.notification_id);
|
|
const advanced = await advance({ ...record.source });
|
|
storage?.removeItem(sourceKey);
|
|
return { admission, advanced:Boolean(advanced) };
|
|
}
|
|
|
|
return { draft, stageSource, source, discardSource, complete };
|
|
}
|
|
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = createUpdateFollowUp;
|