feat(pages): explicit position ordering + sectioned space view
Add position column to pages (migration 020), update listBySpace to ORDER BY position, title, expose position in update(), add to patchSchema, and replace the space view flat table with a tree renderer grouping pages by parent_id under h4 section headers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,10 +11,38 @@ function taskItem(t) {
|
||||
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));
|
||||
function pageLink(p) {
|
||||
return el('a', { href: '#/page/' + p.id }, p.title || '(untitled)');
|
||||
}
|
||||
|
||||
function renderPageTree(pages, refs) {
|
||||
const byParent = new Map();
|
||||
for (const p of pages) {
|
||||
const k = p.parent_id || '__root__';
|
||||
if (!byParent.has(k)) byParent.set(k, []);
|
||||
byParent.get(k).push(p);
|
||||
}
|
||||
const roots = byParent.get('__root__') || [];
|
||||
const blocks = [];
|
||||
for (const r of roots) {
|
||||
const kids = byParent.get(r.id) || [];
|
||||
blocks.push(el('div', { class: 'doc-section' },
|
||||
el('h4', { style: { margin: '12px 0 4px' } }, pageLink(r)),
|
||||
kids.length
|
||||
? el('ul', { class: 'plain', style: { margin: '0 0 0 14px' } },
|
||||
kids.map(k => {
|
||||
const gk = byParent.get(k.id) || [];
|
||||
return el('li', {}, pageLink(k),
|
||||
gk.length ? el('ul', { class: 'plain', style: { margin: '0 0 0 14px' } },
|
||||
gk.map(g => el('li', {}, pageLink(g)))) : null);
|
||||
}))
|
||||
: null));
|
||||
}
|
||||
if (refs.length) blocks.push(el('div', { class: 'doc-section' },
|
||||
el('h4', { style: { margin: '12px 0 4px' } }, 'References'),
|
||||
el('ul', { class: 'plain', style: { margin: '0 0 0 14px' } },
|
||||
refs.map(rf => el('li', {}, el('a', { href: '#/ref/' + rf.id }, rf.title || rf.source_url))))));
|
||||
return blocks;
|
||||
}
|
||||
|
||||
export async function render(main, ctx) {
|
||||
@@ -46,10 +74,6 @@ export async function render(main, ctx) {
|
||||
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' },
|
||||
@@ -75,13 +99,9 @@ export async function render(main, ctx) {
|
||||
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('h3', {}, `Pages & references${(pages.length + refs.length) ? ` (${pages.length + refs.length})` : ''}`),
|
||||
(pages.length + refs.length) > 0
|
||||
? el('div', {}, renderPageTree(pages, refs))
|
||||
: el('p', { class: 'muted' }, 'Nothing here yet.'))
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user