// Space view — rich project cards (status, research, edit/delete) + open tasks, // then a full pages & references table. import { api } from '../api.js'; import { el, mount } from '../dom.js'; import { exportMenu } from '../components/export_menu.js'; import { projectCard } from '../components/project_card.js'; import { openProjectModal } from '../components/project_modal.js'; function taskItem(t) { return el('li', {}, el('span', { class: 'status' + (t.status === 'blocked' ? ' bad' : '') }, t.status), ' ', t.title); } function tableRow(href, title, type) { return el('tr', {}, el('td', { style: { padding: '5px 8px', borderTop: '1px solid var(--border)' } }, el('a', { href }, title || '(untitled)')), el('td', { class: 'muted', style: { padding: '5px 8px', borderTop: '1px solid var(--border)', width: '90px' } }, type)); } export async function render(main, ctx) { const id = ctx.params.id; mount(main, el('h1', { class: 'view-h1' }, 'Space'), el('p', { class: 'view-sub muted' }, 'Loading …')); localStorage.setItem('last_space_id', id); let space; try { space = await api.get('/api/spaces/' + id); } catch (e) { mount(main, el('h1', { class: 'view-h1' }, 'Space not found'), el('p', { class: 'view-sub muted' }, e.message)); return; } let projects = []; const [tasks, pages, refs] = await Promise.all([ api.get(`/api/spaces/${id}/tasks?status=todo`).catch(() => []), api.get(`/api/spaces/${id}/pages`).catch(() => []), api.get(`/api/refs?space_id=${id}&limit=200`).catch(() => []) ]); try { projects = await api.get(`/api/spaces/${id}/projects`); } catch { /* */ } // ---- Projects section (rich cards) ---- const projWrap = el('div', { class: 'proj-list' }); function renderProjects() { projWrap.replaceChildren(); projHead.textContent = `Projects${projects.length ? ` (${projects.length})` : ''}`; if (!projects.length) { projWrap.appendChild(el('div', { class: 'muted', style: { padding: '4px 2px' } }, 'No projects yet.')); return; } for (const p of projects) projWrap.appendChild(projectCard(p, { reload, rerender: renderProjects })); } async function reload() { try { projects = await api.get(`/api/spaces/${id}/projects`); } catch { /* */ } renderProjects(); } const projHead = el('h3', {}, 'Projects'); renderProjects(); const rows = [ ...pages.map(p => tableRow('#/page/' + p.id, p.title, 'page')), ...refs.map(r => tableRow('#/ref/' + r.id, r.title || r.source_url, r.kind)) ]; mount(main, el('div', { class: 'doc-head' }, el('h1', { class: 'view-h1', style: { margin: '0' } }, space.name), exportMenu({ filenameBase: 'space-' + (space.slug || space.name), getContent: async () => { const full = await Promise.all(pages.map(p => api.get('/api/pages/' + p.id).catch(() => null))); const md = full.filter(Boolean).map(p => `# ${p.title}\n\n${p.body_md || ''}`).join('\n\n---\n\n'); return { title: space.name, md }; } }) ), el('p', { class: 'view-sub' }, space.description || el('span', { class: 'muted' }, 'No description.')), el('div', { class: 'card' }, el('div', { class: 'card-head' }, projHead, el('button', { class: 'primary', onclick: () => openProjectModal(id, null, reload) }, '+ New')), projWrap), el('div', { class: 'card' }, el('h3', {}, `Open tasks${tasks.length ? ` (${tasks.length})` : ''}`), tasks.length ? el('ul', { class: 'plain' }, tasks.map(taskItem)) : el('p', { class: 'muted' }, 'Clear board.')), el('div', { class: 'card' }, el('h3', {}, `Pages & references${rows.length ? ` (${pages.length + refs.length})` : ''}`), rows.length ? el('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: '13px' } }, el('thead', {}, el('tr', {}, el('th', { class: 'muted', style: { textAlign: 'left', padding: '5px 8px', fontWeight: '500' } }, 'Title'), el('th', { class: 'muted', style: { textAlign: 'left', padding: '5px 8px', width: '90px', fontWeight: '500' } }, 'Type'))), el('tbody', {}, rows)) : el('p', { class: 'muted' }, 'Nothing here yet.')) ); }