(function(root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; else root.createIssueFilingReview = api; })(typeof self !== 'undefined' ? self : this, function() { 'use strict'; const INTENT_LABELS = { 'create-and-assign': 'Create & assign to me', 'create-and-start': 'Create & start', 'follow-up-and-next': 'Create follow-up & next', }; function clone(value) { if (typeof structuredClone === 'function') return structuredClone(value); return JSON.parse(JSON.stringify(value)); } function create(options) { let reviewed = null; let trigger = null; let busy = false; let evidenceItems = []; const generatedUrls = new Set(); function releaseEvidenceUrls() { generatedUrls.forEach(url => options.revokeObjectURL?.(url)); generatedUrls.clear(); evidenceItems = []; } function evidenceUrl(attachment) { if (attachment?.blob) { const url = options.createObjectURL(attachment.blob); generatedUrls.add(url); return url; } if (attachment?.data) { return 'data:' + String(attachment.contentType || 'application/octet-stream') + ';base64,' + String(attachment.data); } return ''; } function selectEvidence(index) { const selected = evidenceItems[index]; if (!selected) return; evidenceItems.forEach((item, position) => item.button.setAttribute('aria-pressed', position === index ? 'true' : 'false')); const filename = selected.attachment.filename || 'Screenshot'; options.evidencePreview.hidden = false; options.evidenceImage.src = selected.url; options.evidenceImage.alt = 'Evidence ' + (index + 1) + ' of ' + evidenceItems.length + ': ' + filename; options.evidencePosition.textContent = 'Evidence ' + (index + 1) + ' of ' + evidenceItems.length; options.evidenceFilename.textContent = filename; options.evidenceNote.textContent = selected.attachment.note || 'No evidence note.'; } function renderVisualEvidence(attachments) { releaseEvidenceUrls(); options.evidenceEmpty.hidden = attachments.length > 0; options.evidencePreview.hidden = true; options.evidenceImage.src = ''; evidenceItems = attachments.map((attachment, index) => { const url = evidenceUrl(attachment); const item = options.document.createElement('li'); const button = options.document.createElement('button'); const image = options.document.createElement('img'); const filename = attachment.filename || 'Screenshot'; button.setAttribute('type', 'button'); button.className = 'issue-filing-evidence-selector'; button.setAttribute('aria-label', 'Preview evidence ' + (index + 1) + ' of ' + attachments.length + ': ' + filename); button.setAttribute('aria-pressed', 'false'); image.src = url; image.alt = ''; button.appendChild(image); item.appendChild(button); return { attachment, button, item, url }; }); evidenceItems.forEach((entry, index) => entry.button.addEventListener('click', () => selectEvidence(index))); options.evidenceList.replaceChildren(...evidenceItems.map(entry => entry.item)); if (evidenceItems.length) selectEvidence(0); } function close() { options.sheet.hidden = true; const previousTrigger = trigger; trigger = null; reviewed = null; busy = false; releaseEvidenceUrls(); options.confirmButton.disabled = false; previousTrigger?.focus(); } function render(payload) { const draft = payload.draft; options.repository.textContent = draft.repository || 'No repository'; options.intent.textContent = draft.unassigned === true ? 'Create unassigned' : (INTENT_LABELS[payload.intent] || payload.intent); if (options.issueType) options.issueType.textContent = draft.templateName || 'Blank issue'; options.title.textContent = draft.title; options.body.textContent = draft.body || 'No note provided.'; const labels = (draft.labels || []).map(label => typeof label === 'string' ? label : label.name); const milestone = draft.milestone?.title || draft.milestoneTitle || 'No milestone'; const dueDate = draft.dueDate || draft.due_date || 'No due date'; const owner = draft.unassigned === true ? 'Owner: No owner' : (draft.assignee ? 'Owner: ' + (draft.assigneeName || draft.assignee) + ' (@' + draft.assignee + ')' : 'Assigned to you'); const planning = []; if (Number.isInteger(draft.estimateMinutes)) { planning.push('Estimate: ' + draft.estimateMinutes + ' min'); const projected = draft.todayCapacity?.projectedMinutes; if (Number.isInteger(projected)) planning.push(projected >= 0 ? 'Today after start: ' + projected + ' min free' : 'Today after start: ' + Math.abs(projected) + ' min over capacity'); } options.metadata.textContent = [ labels.length ? 'Labels: ' + labels.join(', ') : 'No labels', 'Milestone: ' + milestone, 'Due: ' + dueDate, owner, ...planning, ].join(' · '); if (options.blockerList) { options.blockerList.replaceChildren(...(draft.blockers || []).map(blocker => { const item = options.document.createElement('li'); item.textContent = blocker.repository + ' #' + blocker.number + ' — ' + blocker.title; return item; })); } const attachments = (draft.attachments || (draft.attachment ? [draft.attachment] : [])).filter(Boolean); if (options.evidencePreview) { renderVisualEvidence(attachments); return; } options.evidenceList.replaceChildren(...attachments.map((attachment, index) => { const item = options.document.createElement('li'); item.textContent = (index + 1) + '. ' + (attachment.filename || 'Screenshot') + (attachment.note ? ' — ' + attachment.note : ''); return item; })); } function open(payload, opener) { reviewed = clone(payload); trigger = opener || null; busy = false; options.confirmButton.disabled = false; render(reviewed); options.sheet.hidden = false; options.backButton.focus(); } options.backButton.addEventListener('click', close); options.confirmButton.addEventListener('click', async () => { if (!reviewed || busy) return; busy = true; options.confirmButton.disabled = true; try { await options.onConfirm(reviewed); close(); } catch (error) { busy = false; options.confirmButton.disabled = false; if (options.status) options.status.textContent = error.message; options.confirmButton.focus(); } }); options.document.addEventListener('keydown', event => { if (!options.sheet.hidden && event.key === 'Escape' && !busy) { event.preventDefault(); close(); } }); return { close, open }; } return create; });