/** * new-repo.ts — New repository wizard page module. * * Handles client-side validation and tag pill input for the new-repo form. * The form is mostly server-rendered with HTMX; this module provides: * - Repo name availability check (debounced) * - Tag pill multi-input widget * * Data expected in #page-data: * { "page": "new-repo", "owner": "..." } * * Registered as: window.MusePages['new-repo'] */ export interface NewRepoData { page?: string; owner?: string; [key: string]: unknown; } function initTagInput(containerId: string, hiddenInputId: string): void { const container = document.getElementById(containerId); const hidden = document.getElementById(hiddenInputId) as HTMLInputElement | null; if (!container || !hidden) return; const textInput = container.querySelector('.tag-text-input') as HTMLInputElement | null; if (!textInput) return; let tags: string[] = hidden.value ? hidden.value.split(',').filter(Boolean) : []; function render(): void { container!.querySelectorAll('.tag-pill').forEach((p) => p.remove()); tags.forEach((tag) => { const pill = document.createElement('span'); pill.className = 'tag-pill'; pill.textContent = tag + ' '; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'tag-pill-remove'; btn.textContent = '×'; btn.addEventListener('click', () => { tags = tags.filter((t) => t !== tag); render(); }); pill.appendChild(btn); container!.insertBefore(pill, textInput); }); hidden!.value = tags.join(','); } textInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); const val = textInput.value.trim().replace(/,/g, ''); if (val && !tags.includes(val)) { tags.push(val); render(); } textInput.value = ''; } else if (e.key === 'Backspace' && textInput.value === '' && tags.length > 0) { tags.pop(); render(); } }); container.addEventListener('click', () => textInput.focus()); render(); } async function submitWizard(e: Event): Promise { e.preventDefault(); const btn = document.getElementById('submit-btn') as HTMLButtonElement | null; const errorEl = document.getElementById('submit-error') as HTMLElement | null; if (errorEl) errorEl.style.display = 'none'; const owner = (document.getElementById('f-owner') as HTMLInputElement).value.trim(); const name = (document.getElementById('f-name') as HTMLInputElement).value.trim(); const desc = (document.getElementById('f-description') as HTMLTextAreaElement).value.trim(); const license = (document.getElementById('f-license') as HTMLSelectElement).value || null; const branchEl = document.getElementById('f-branch') as HTMLInputElement | null; const branch = branchEl ? (branchEl.value.trim() || 'main') : 'main'; const init = (document.getElementById('f-initialize') as HTMLInputElement).checked; // Collect topics from the hidden input synced by initTagInput const tagsHidden = document.getElementById('tags-hidden') as HTMLInputElement | null; const topics: string[] = tagsHidden?.value ? tagsHidden.value.split(',').filter(Boolean) : []; if (btn) { btn.disabled = true; btn.textContent = 'Creating…'; } try { const res = await fetch('/new', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ owner, name, description: desc, visibility: 'public', license, topics, tags: [], initialize: init, defaultBranch: branch }), }); if (res.status === 401 || res.status === 403) { if (errorEl) { errorEl.textContent = 'Not authorized — sign in via the muse CLI to create a repository.'; errorEl.style.display = ''; } return; } const data = await res.json() as { redirect?: string; detail?: string }; if (res.status === 201) { window.location.href = data.redirect!; return; } if (errorEl) { errorEl.textContent = (data.detail || 'Failed to create repository.'); errorEl.style.display = ''; } } catch (ex) { if (errorEl) { errorEl.textContent = (ex instanceof Error ? ex.message : String(ex)); errorEl.style.display = ''; } } finally { if (btn) { btn.disabled = false; btn.textContent = 'Create repository'; } } } export function initNewRepo(_data: NewRepoData): void { initTagInput('tag-input-container', 'tags-hidden'); const form = document.getElementById('wizard-form') as HTMLFormElement | null; form?.addEventListener('submit', (e) => void submitWizard(e)); }