feat(spaces): docs-kind spaces render as pure documentation repos

Adds a `kind` column to spaces ('project' default, 'docs' for Wiki).
Docs spaces skip projects/tasks fetches and render only the page tree.
Sidebar caret for docs spaces expands to top-level pages (#/page/:id).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-05 23:41:46 +10:00
parent 71adc51c00
commit 43bfa23a00
5 changed files with 119 additions and 13 deletions

View File

@@ -30,6 +30,13 @@ async function loadProjects(space_id) {
} catch { return []; }
}
async function loadTopPages(space_id) {
try {
const pages = await api.get(`/api/spaces/${space_id}/pages`);
return pages.filter(p => p.parent_id == null);
} catch { return []; }
}
async function renderSpaceTree(container) {
let spaces;
try { spaces = await api.get('/api/spaces'); }
@@ -52,11 +59,20 @@ async function renderSpaceTree(container) {
if (expanded.has(s.id)) { expanded.delete(s.id); clear(childWrap); }
else {
expanded.add(s.id);
const projects = await loadProjects(s.id);
clear(childWrap);
if (!projects.length) childWrap.appendChild(el('div', { class: 'sb-item muted' }, '(no projects)'));
for (const p of projects) {
childWrap.appendChild(el('a', { class: 'sb-item', href: '#/project/' + p.id }, p.name));
if (s.kind === 'docs') {
const pages = await loadTopPages(s.id);
clear(childWrap);
if (!pages.length) childWrap.appendChild(el('div', { class: 'sb-item muted' }, '(no pages)'));
for (const p of pages) {
childWrap.appendChild(el('a', { class: 'sb-item', href: '#/page/' + p.id }, p.title || '(untitled)'));
}
} else {
const projects = await loadProjects(s.id);
clear(childWrap);
if (!projects.length) childWrap.appendChild(el('div', { class: 'sb-item muted' }, '(no projects)'));
for (const p of projects) {
childWrap.appendChild(el('a', { class: 'sb-item', href: '#/project/' + p.id }, p.name));
}
}
}
}
@@ -67,13 +83,23 @@ async function renderSpaceTree(container) {
);
container.appendChild(header);
if (isOpen) {
loadProjects(s.id).then(projects => {
clear(childWrap);
if (!projects.length) childWrap.appendChild(el('div', { class: 'sb-item muted' }, '(no projects)'));
for (const p of projects) {
childWrap.appendChild(el('a', { class: 'sb-item', href: '#/project/' + p.id }, p.name));
}
});
if (s.kind === 'docs') {
loadTopPages(s.id).then(pages => {
clear(childWrap);
if (!pages.length) childWrap.appendChild(el('div', { class: 'sb-item muted' }, '(no pages)'));
for (const p of pages) {
childWrap.appendChild(el('a', { class: 'sb-item', href: '#/page/' + p.id }, p.title || '(untitled)'));
}
});
} else {
loadProjects(s.id).then(projects => {
clear(childWrap);
if (!projects.length) childWrap.appendChild(el('div', { class: 'sb-item muted' }, '(no projects)'));
for (const p of projects) {
childWrap.appendChild(el('a', { class: 'sb-item', href: '#/project/' + p.id }, p.name));
}
});
}
}
container.appendChild(childWrap);
}