// Docs.jsx — Rich Documents & File Storage (() => { const { Card, Badge, Avatar } = window.TechyFuelOSDesignSystem_be0222 || {}; // Some legacy files (migrated from the old Supabase storage) were saved with a // DOUBLE-prefixed URL, e.g. "https://api.techyfuel.com/storage/https://xxx.supabase.co/...". // That never loads. Unwrap it to the real inner URL so old images/files still show. function fileUrl(u) { if (!u) return u; const i = u.indexOf('http', 5); // a second "http" inside means double-prefixed if (i > 0) return u.slice(i); return u; } // Error boundary to prevent the whole app crashing on doc-editor errors class DocErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { err: null }; } static getDerivedStateFromError(e) { return { err: e }; } render() { if (this.state.err) return (

Document error

{String(this.state.err)}

); return this.props.children; } } const BLOCK_TYPES = [ { type: 'paragraph', label: 'Text', icon: 'type' }, { type: 'h1', label: 'Heading 1', icon: 'heading-1' }, { type: 'h2', label: 'Heading 2', icon: 'heading-2' }, { type: 'h3', label: 'Heading 3', icon: 'heading-3' }, { type: 'bullet', label: 'Bullet list', icon: 'list' }, { type: 'numbered', label: 'Numbered list', icon: 'list-ordered' }, { type: 'checklist', label: 'To-do', icon: 'check-square' }, { type: 'code', label: 'Code block', icon: 'code' }, { type: 'quote', label: 'Quote', icon: 'quote' }, { type: 'image', label: 'Image', icon: 'image' }, { type: 'table', label: 'Table', icon: 'table' }, { type: 'divider', label: 'Divider', icon: 'minus' }, ]; function uid() { return Math.random().toString(36).slice(2) + Date.now().toString(36); } function mkBlock(type = 'paragraph') { const id = uid(); if (type === 'table') return { id, type, rows: [['', ''], ['', '']] }; if (type === 'checklist') return { id, type, text: '', checked: false }; if (type === 'code') return { id, type, code: '', language: 'javascript' }; if (type === 'image') return { id, type, url: '', caption: '' }; if (type === 'divider') return { id, type }; return { id, type, text: '' }; } // Shared props that block browser extensions (Grammarly, translate, spellcheck) // from wrapping/modifying textareas, which breaks React's removeChild calls const NO_EXT = { spellCheck: false, autoComplete: 'off', autoCorrect: 'off', autoCapitalize: 'off', translate: 'no', 'data-gramm': 'false', 'data-gramm_editor': 'false', 'data-enable-grammarly': 'false', }; // ── Single Block ────────────────────────────────────────────────────────────── function Block({ block, index, onChange, onKeyDown, elRef }) { const base = { outline: 'none', width: '100%', border: 'none', background: 'transparent', fontFamily: 'var(--font-sans)', resize: 'none', padding: 0 }; function detectMarkdown(val) { if (val === '# ') return onChange({ ...block, type: 'h1', text: '' }); if (val === '## ') return onChange({ ...block, type: 'h2', text: '' }); if (val === '### ') return onChange({ ...block, type: 'h3', text: '' }); if (val === '- ' || val === '* ') return onChange({ ...block, type: 'bullet', text: '' }); if (val === '1. ') return onChange({ ...block, type: 'numbered', text: '' }); if (val === '[] ' || val === '[ ] ') return onChange({ ...block, type: 'checklist', text: '', checked: false }); if (val === '```') return onChange({ ...block, type: 'code', code: '', language: 'javascript' }); if (val === '> ') return onChange({ ...block, type: 'quote', text: '' }); if (val === '---') return onChange({ ...block, type: 'divider' }); onChange({ ...block, text: val }); } if (block.type === 'divider') return
; if (block.type === 'image') { if (block.url) return (
{block.caption} onChange({ ...block, caption: e.target.value })} placeholder="Caption..." style={{ ...base, textAlign: 'center', fontSize: 13, color: 'var(--text-muted)', marginTop: 6, display: 'block' }} />
); return (
{ const u = prompt('Paste image URL:'); if (u) onChange({ ...block, url: u }); }}>

Click to embed image URL

); } if (block.type === 'code') return (
{['#ff5f57','#febc2e','#28c840'].map(c =>
)}