(function(root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; else root.issueAttachment = api; })(typeof self !== 'undefined' ? self : this, function() { 'use strict'; const MAX_BYTES = 2 * 1024 * 1024; const IMAGE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp']); function create(options) { const readDataUrl = options.readDataUrl; const upload = options.upload; const createOperationId = options.createOperationId || (() => { if (typeof globalThis !== 'undefined' && globalThis.crypto?.randomUUID) { return globalThis.crypto.randomUUID(); } return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2); }); let selected = null; let confirmed = null; let serialized = null; let operationId = null; function select(file) { if (!file || !IMAGE_TYPES.has(file.type)) { throw new Error('Choose a PNG, JPEG, or WebP screenshot.'); } if (!Number.isFinite(file.size) || file.size <= 0 || file.size > MAX_BYTES) { throw new Error('Choose a screenshot that is 2 MB or smaller.'); } selected = file; confirmed = null; serialized = null; operationId = createOperationId(); return state(); } function clear() { selected = null; confirmed = null; serialized = null; operationId = null; } function restore(value) { const contentType = String(value?.contentType || ''); const filename = String(value?.filename || ''); const data = String(value?.data || ''); if (!data) { clear(); throw new Error('The saved screenshot is unavailable. Retry before editing this issue.'); } const padding = (data.match(/=*$/) || [''])[0].length; const size = Math.max(1, Math.floor(data.length * 3 / 4) - padding); select({ name: filename, type: contentType, size }); serialized = { filename, contentType, data }; return state(); } function state() { return selected ? { name: selected.name, size: selected.size, uploaded: Boolean(confirmed), } : null; } async function serialize() { if (!selected) return null; if (!serialized) { const dataUrl = await readDataUrl(selected); const marker = ';base64,'; const markerAt = String(dataUrl).indexOf(marker); if (markerAt < 0) throw new Error('The screenshot could not be read. Choose it again.'); serialized = { filename: selected.name, contentType: selected.type, data: String(dataUrl).slice(markerAt + marker.length), }; } return { ...serialized }; } async function prepareComment(item, body) { const text = String(body || '').trim(); if (!selected) return text; if (!confirmed) { const attachment = await serialize(); confirmed = await upload({ repository: item.repository, number: item.number, filename: attachment.filename, content_type: attachment.contentType, data: attachment.data, operation_id: operationId, }); if (!confirmed || typeof confirmed.markdown !== 'string' || !confirmed.markdown) { confirmed = null; throw new Error('The server did not confirm the screenshot upload.'); } } return text ? text + '\n\n' + confirmed.markdown : confirmed.markdown; } return { select, restore, clear, state, serialize, prepareComment }; } function mount(options) { const controller = create(options); const clearSelection = controller.clear; let previewUrl = ''; function clearPreview() { if (previewUrl) options.revokeObjectURL(previewUrl); previewUrl = ''; options.image.src = ''; options.preview.hidden = true; options.input.value = ''; clearSelection(); } options.input.addEventListener('change', event => { const file = event.target.files && event.target.files[0]; try { controller.select(file); } catch (error) { options.status.textContent = error.message; options.input.value = ''; return; } if (previewUrl) options.revokeObjectURL(previewUrl); previewUrl = options.createObjectURL(file); options.image.src = previewUrl; options.meta.textContent = file.name + ' · ' + Math.ceil(file.size / 1024) + ' KB'; options.preview.hidden = false; options.status.textContent = options.readyMessage || 'Screenshot ready to upload with this comment.'; }); options.remove.addEventListener('click', () => { clearPreview(); options.status.textContent = options.removedMessage || 'Screenshot removed. Your comment is unchanged.'; }); function restorePreview(value) { clearPreview(); const restored = controller.restore(value); previewUrl = 'data:' + value.contentType + ';base64,' + value.data; options.image.src = previewUrl; options.meta.textContent = restored.name + ' · ' + Math.ceil(restored.size / 1024) + ' KB'; options.preview.hidden = false; options.status.textContent = options.readyMessage || 'Screenshot ready to upload with this comment.'; return restored; } return Object.assign(controller, { clear: clearPreview, restore: restorePreview }); } return { create, mount, MAX_BYTES }; });