diff --git a/frontend/create-issue-sheet.js b/frontend/create-issue-sheet.js index 6bc2662..7fa1bf1 100644 --- a/frontend/create-issue-sheet.js +++ b/frontend/create-issue-sheet.js @@ -8,8 +8,20 @@ function newIssueOperationId() { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } +function normalizeSharedContent(value = {}) { + const clean = input => String(input || '').replace(/\s+/g, ' ').trim(); + const text = String(value.text || '').trim().slice(0, 9500); + const textSummary = clean(text); + const sentence = (textSummary.match(/^.{1,80}?[.!?](?:\s|$)/)?.[0] || textSummary.slice(0, 80)).trim(); + const title = (clean(value.title) || sentence).slice(0, 240); + const url = clean(value.url).slice(0, 2000); + const body = text && url && text.includes(url) ? text : [text, url].filter(Boolean).join('\n\n'); + return { title, body }; +} + function createIssueCapture({ fetchJson, storage, createOperationId = newIssueOperationId }) { const storageKey = 'stackchain.issue-capture.v1'; + const sharedStorageKey = 'stackchain.issue-share.v1'; let pending = null; const safeLabelIds = value => Array.from(new Set( (Array.isArray(value) ? value : []).filter(id => Number.isInteger(id) && id > 0) @@ -73,6 +85,42 @@ function createIssueCapture({ fetchJson, storage, createOperationId = newIssueOp catch (_error) { /* Confirmed creation remains authoritative. */ } } + function pendingSharedContent() { + try { + const parsed = JSON.parse(storage.getItem(sharedStorageKey) || 'null'); + if (!parsed || typeof parsed !== 'object') return null; + const shared = normalizeSharedContent({title: parsed.title, text: parsed.body}); + return shared.title || shared.body ? shared : null; + } catch (_error) { + return null; + } + } + + function acceptSharedContent() { + const shared = pendingSharedContent(); + if (!shared) return loadDraft(); + const accepted = saveDraft({...loadDraft(), ...shared}); + try { storage.removeItem(sharedStorageKey); } + catch (_error) { /* Accepted content is already persisted as the issue draft. */ } + return accepted; + } + + function discardSharedContent() { + try { storage.removeItem(sharedStorageKey); } + catch (_error) { /* The existing issue draft remains authoritative. */ } + } + + function stageSharedContent(value) { + const shared = normalizeSharedContent(value); + if (!shared.title && !shared.body) return {status: 'empty'}; + try { storage.setItem(sharedStorageKey, JSON.stringify(shared)); } + catch (_error) { /* The caller can still use the in-page share payload. */ } + const existing = loadDraft(); + if (existing.title || existing.body) return {status: 'conflict'}; + acceptSharedContent(); + return {status: 'ready'}; + } + function loadLabels(repository) { const encoded = String(repository || '').split('/').map(encodeURIComponent).join('/'); const priorities = new Set(['p0', 'priority-high', 'critical']); @@ -117,7 +165,12 @@ function createIssueCapture({ fetchJson, storage, createOperationId = newIssueOp return pending; } - return { saveDraft, loadDraft, loadLabels, loadMilestones, submit }; + return { + saveDraft, loadDraft, loadLabels, loadMilestones, submit, + stageSharedContent, pendingSharedContent, acceptSharedContent, discardSharedContent, + }; } +createIssueCapture.normalizeSharedContent = normalizeSharedContent; + if (typeof module !== 'undefined' && module.exports) module.exports = createIssueCapture; diff --git a/frontend/icons/stackchain-192.png b/frontend/icons/stackchain-192.png new file mode 100644 index 0000000..3ee495e Binary files /dev/null and b/frontend/icons/stackchain-192.png differ diff --git a/frontend/icons/stackchain-512.png b/frontend/icons/stackchain-512.png new file mode 100644 index 0000000..fb86c54 Binary files /dev/null and b/frontend/icons/stackchain-512.png differ diff --git a/frontend/index.html b/frontend/index.html index 536757e..be6676e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,6 +3,8 @@
+ +