/** * Notes — Surface Entry Point (v0.1.0) * * Markdown notes surface using the SDK: * sw.api.ext('notes') — scoped API client * sw.ui.* — primitive components * sw.shell.Topbar — navigation bar */ (async function () { 'use strict'; var mount = document.getElementById('extension-mount'); if (!mount) return; var base = window.__BASE__ || ''; var ver = window.__VERSION__ || '0'; // ── Boot SDK ─────────────────────────────── try { if (!window.preact) { var { h, render } = await import(base + '/js/sw/vendor/preact.module.js'); var hooksModule = await import(base + '/js/sw/vendor/hooks.module.js'); var htmModule = await import(base + '/js/sw/vendor/htm.module.js'); window.preact = { h, render }; window.hooks = hooksModule; window.html = htmModule.default.bind(h); } var sdk = await import(base + '/js/sw/sdk/index.js?v=' + ver); await sdk.boot(); } catch (e) { mount.innerHTML = '

SDK boot failed: ' + e.message + '

'; return; } var { html } = window; var { useState, useEffect, useCallback, useRef, useMemo } = hooks; var { render } = preact; // ── SDK modules ──────────────────────────── var api = sw.api.ext('notes'); var { Button, Spinner } = sw.ui; var Topbar = sw.shell.Topbar; // ── Debounce helper ──────────────────────── var _timers = {}; function debounce(key, fn, ms) { clearTimeout(_timers[key]); _timers[key] = setTimeout(fn, ms); } // ═══════════════════════════════════════════ // Markdown renderer (inline, no deps) // ═══════════════════════════════════════════ function renderMarkdown(src) { if (!src) return ''; var lines = src.split('\n'); var out = []; var inCode = false; var inList = false; var listTag = ''; for (var i = 0; i < lines.length; i++) { var line = lines[i]; // fenced code blocks if (line.trim().startsWith('```')) { if (inCode) { out.push(''); inCode = false; } else { if (inList) { out.push(''); inList = false; } out.push('
');
          inCode = true;
        }
        continue;
      }
      if (inCode) {
        out.push(escHtml(line) + '\n');
        continue;
      }

      // blank line
      if (!line.trim()) {
        if (inList) { out.push(''); inList = false; }
        continue;
      }

      // headings
      if (line.startsWith('### ')) { closeList(); out.push('

' + inline(line.slice(4)) + '

'); continue; } if (line.startsWith('## ')) { closeList(); out.push('

' + inline(line.slice(3)) + '

'); continue; } if (line.startsWith('# ')) { closeList(); out.push('

' + inline(line.slice(2)) + '

'); continue; } // hr if (/^[-*_]{3,}\s*$/.test(line)) { closeList(); out.push('
'); continue; } // blockquote if (line.startsWith('> ')) { closeList(); out.push('

' + inline(line.slice(2)) + '

'); continue; } // unordered list if (/^[\-*+]\s/.test(line)) { if (!inList || listTag !== 'ul') { closeList(); out.push('
'); if (inList) out.push(''); return out.join('\n'); function closeList() { if (inList) { out.push(''); inList = false; } } } function escHtml(s) { return s.replace(/&/g, '&').replace(//g, '>'); } function inline(s) { s = escHtml(s); // inline code s = s.replace(/`([^`]+)`/g, '$1'); // bold s = s.replace(/\*\*(.+?)\*\*/g, '$1'); s = s.replace(/__(.+?)__/g, '$1'); // italic s = s.replace(/\*(.+?)\*/g, '$1'); s = s.replace(/_(.+?)_/g, '$1'); // links s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); return s; } // ═══════════════════════════════════════════ // NoteCard — sidebar list item // ═══════════════════════════════════════════ function NoteCard({ note, active, onClick }) { var dateStr = note.updated_at || note.created_at || ''; if (dateStr) { try { dateStr = new Date(dateStr).toLocaleDateString(); } catch(e) { /* keep raw */ } } return html`
${note.pinned ? html`📌` : null} ${note.title || 'Untitled'}
${note.snippet && html`
${note.snippet}
`} ${dateStr && html`
${dateStr}
`}
`; } // ═══════════════════════════════════════════ // EditorPane — note editor + preview // ═══════════════════════════════════════════ function EditorPane({ note, onSave, onDelete, onRefresh }) { var [title, setTitle] = useState(note ? note.title : ''); var [body, setBody] = useState(note ? note.body : ''); var [preview, setPreview] = useState(false); var [dirty, setDirty] = useState(false); var [saving, setSaving] = useState(false); var textareaRef = useRef(null); // sync when note changes useEffect(function() { if (note) { setTitle(note.title || ''); setBody(note.body || ''); setDirty(false); setPreview(false); } }, [note ? note.id : null]); function handleTitleChange(e) { setTitle(e.target.value); setDirty(true); debounce('save', function() { doSave(e.target.value, body); }, 1000); } function handleBodyChange(e) { setBody(e.target.value); setDirty(true); debounce('save', function() { doSave(title, e.target.value); }, 1000); } async function doSave(t, b) { if (!note) return; setSaving(true); try { await api.put('/notes/' + note.id, { title: t || 'Untitled', body: b }); setDirty(false); onRefresh(); } catch (e) { console.error('Save failed:', e); } finally { setSaving(false); } } async function handlePin() { if (!note) return; await api.put('/notes/' + note.id, { pinned: note.pinned ? 0 : 1 }); onRefresh(); } async function handleArchive() { if (!note || !confirm('Archive this note?')) return; await api.del('/notes/' + note.id); onDelete(); } function handleKeyDown(e) { // Tab inserts two spaces if (e.key === 'Tab') { e.preventDefault(); var ta = e.target; var start = ta.selectionStart; var end = ta.selectionEnd; var val = ta.value; ta.value = val.substring(0, start) + ' ' + val.substring(end); ta.selectionStart = ta.selectionEnd = start + 2; setBody(ta.value); setDirty(true); debounce('save', function() { doSave(title, ta.value); }, 1000); } // Ctrl/Cmd+S force save if (e.key === 's' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); doSave(title, body); } } var previewHtml = useMemo(function() { return renderMarkdown(body); }, [body]); if (!note) { return html`
Select a note or create a new one
`; } return html`
${saving ? 'Saving…' : (dirty ? 'Unsaved' : 'Saved')}
${preview ? html`
` : html`