lvwerra HF Staff commited on
Commit
1f1507e
·
verified ·
1 Parent(s): ab66bca

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. client/app.css +14 -4
  2. client/src/doc.js +32 -6
  3. server/collab.js +20 -10
  4. server/playground.js +1 -8
client/app.css CHANGED
@@ -461,13 +461,23 @@ textarea { resize: vertical; }
461
  border: 1px solid var(--border); border-radius: 12px; font-size: 13px;
462
  }
463
  #slug-legend .legend-title { color: var(--text-3); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; }
464
- #slug-legend .legend-row { display: flex; align-items: center; gap: 10px; padding: 2px 0; }
465
- #slug-legend .legend-row a { color: var(--text); text-decoration: none; }
466
- #slug-legend .legend-row a:hover { text-decoration: underline; }
467
- #slug-legend .arrow { color: var(--text-3); }
 
 
468
  #slug-legend code {
469
  background: rgba(0, 0, 0, 0.05); padding: 1px 7px; border-radius: 5px; cursor: pointer;
470
  font-size: 12px;
471
  }
472
  #slug-legend code:hover { background: rgba(29, 92, 69, 0.12); }
473
  #slug-legend code.copied { background: var(--accent-soft); color: var(--accent); }
 
 
 
 
 
 
 
 
 
461
  border: 1px solid var(--border); border-radius: 12px; font-size: 13px;
462
  }
463
  #slug-legend .legend-title { color: var(--text-3); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; }
464
+ #slug-legend table { border-collapse: collapse; width: 100%; }
465
+ #slug-legend th { text-align: left; font-size: 11.5px; color: var(--text-3); font-weight: 600; padding: 4px 12px 4px 0; border-bottom: 1px solid var(--line); }
466
+ #slug-legend td { padding: 5px 12px 5px 0; border-bottom: 1px solid var(--line); }
467
+ #slug-legend tr:last-child td { border-bottom: none; }
468
+ #slug-legend td a { color: var(--text); text-decoration: none; }
469
+ #slug-legend td a:hover { text-decoration: underline; }
470
  #slug-legend code {
471
  background: rgba(0, 0, 0, 0.05); padding: 1px 7px; border-radius: 5px; cursor: pointer;
472
  font-size: 12px;
473
  }
474
  #slug-legend code:hover { background: rgba(29, 92, 69, 0.12); }
475
  #slug-legend code.copied { background: var(--accent-soft); color: var(--accent); }
476
+
477
+
478
+ /* compact structure page: no phantom card height, hint above the field */
479
+ .structure-mode #editor { min-height: 0; padding: 22px 28px; }
480
+ #structure-hint {
481
+ margin-bottom: 12px; padding: 10px 14px; font-size: 12.5px; color: var(--text-2);
482
+ background: rgba(29, 92, 69, 0.05); border: 1px solid var(--line); border-radius: 10px;
483
+ }
client/src/doc.js CHANGED
@@ -203,6 +203,17 @@ async function initPage() {
203
  })
204
  }
205
 
 
 
 
 
 
 
 
 
 
 
 
206
  renderMargin(editor)
207
  setTimeout(() => updateTitle(editor), 600)
208
  }
@@ -440,27 +451,37 @@ function renderSlugLegend(s) {
440
  return
441
  }
442
  const box = existing || el('div', { id: 'slug-legend' })
443
- box.replaceChildren(el('div', { class: 'legend-title' }, 'Pages on this project — use the slug in the structure above'))
 
 
 
 
 
 
444
  for (const [slug, title] of Object.entries(s.titles || {})) {
445
  if (slug === '_structure') continue
446
- const row = el('div', { class: 'legend-row' })
 
447
  const a = el('a', { href: slug === 'home' ? `/d/${docId}` : `/d/${docId}/${slug}` }, title || slug)
448
  a.addEventListener('click', e => {
449
  if (e.metaKey || e.ctrlKey || e.button !== 0) return
450
  e.preventDefault()
451
  openPage(slug)
452
  })
453
- row.appendChild(a)
454
- row.appendChild(el('span', { class: 'arrow' }, '→'))
455
  const code = el('code', { title: 'Click to copy' }, slug)
456
  code.addEventListener('click', () => {
457
  navigator.clipboard?.writeText(slug)
458
  code.classList.add('copied')
459
  setTimeout(() => code.classList.remove('copied'), 800)
460
  })
461
- row.appendChild(code)
462
- box.appendChild(row)
 
463
  }
 
 
464
  if (!existing) document.getElementById('editor').after(box)
465
  }
466
 
@@ -572,6 +593,11 @@ async function loadStructure() {
572
  }
573
 
574
  function updateTitle(editor) {
 
 
 
 
 
575
  let title = 'Untitled'
576
  editor.state.doc.descendants(node => {
577
  if (title === 'Untitled' && node.isTextblock && node.textContent.trim()) {
 
203
  })
204
  }
205
 
206
+ document.getElementById('layout').classList.toggle('structure-mode', pageSlug === '_structure')
207
+ document.getElementById('structure-hint')?.remove()
208
+ if (pageSlug === '_structure') {
209
+ const hint = el(
210
+ 'div',
211
+ { id: 'structure-hint' },
212
+ 'This YAML is the page tree: one "- slug" line per page, indent two spaces to nest. Pages missing here show under "unfiled" — nothing is ever deleted by editing this. Changes can also be proposed as suggestions.'
213
+ )
214
+ document.getElementById('editor').before(hint)
215
+ }
216
+
217
  renderMargin(editor)
218
  setTimeout(() => updateTitle(editor), 600)
219
  }
 
451
  return
452
  }
453
  const box = existing || el('div', { id: 'slug-legend' })
454
+ const table = el('table', {})
455
+ const thead = el('thead', {})
456
+ const hr = el('tr', {})
457
+ hr.append(el('th', {}, 'Page'), el('th', {}, 'Slug (click to copy)'))
458
+ thead.appendChild(hr)
459
+ table.appendChild(thead)
460
+ const tbody = el('tbody', {})
461
  for (const [slug, title] of Object.entries(s.titles || {})) {
462
  if (slug === '_structure') continue
463
+ const tr = el('tr', {})
464
+ const tdTitle = el('td', {})
465
  const a = el('a', { href: slug === 'home' ? `/d/${docId}` : `/d/${docId}/${slug}` }, title || slug)
466
  a.addEventListener('click', e => {
467
  if (e.metaKey || e.ctrlKey || e.button !== 0) return
468
  e.preventDefault()
469
  openPage(slug)
470
  })
471
+ tdTitle.appendChild(a)
472
+ const tdSlug = el('td', {})
473
  const code = el('code', { title: 'Click to copy' }, slug)
474
  code.addEventListener('click', () => {
475
  navigator.clipboard?.writeText(slug)
476
  code.classList.add('copied')
477
  setTimeout(() => code.classList.remove('copied'), 800)
478
  })
479
+ tdSlug.appendChild(code)
480
+ tr.append(tdTitle, tdSlug)
481
+ tbody.appendChild(tr)
482
  }
483
+ table.appendChild(tbody)
484
+ box.replaceChildren(el('div', { class: 'legend-title' }, 'Pages in this project'), table)
485
  if (!existing) document.getElementById('editor').after(box)
486
  }
487
 
 
593
  }
594
 
595
  function updateTitle(editor) {
596
+ if (pageSlug === '_structure') {
597
+ document.getElementById('doc-title').textContent = 'Structure'
598
+ document.title = 'Structure'
599
+ return
600
+ }
601
  let title = 'Untitled'
602
  editor.state.doc.descendants(node => {
603
  if (title === 'Untitled' && node.isTextblock && node.textContent.trim()) {
server/collab.js CHANGED
@@ -77,6 +77,24 @@ export const hocuspocus = new Hocuspocus({
77
  try {
78
  migrateLegacySuggestionAnchors(document)
79
  } catch {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }, 50)
81
  }
82
  },
@@ -791,21 +809,13 @@ export async function ensureStructurePage(projectId) {
791
  await withDoc(name, document => {
792
  const fragment = document.getXmlFragment(FIELD)
793
  if (fragment.length > 0) return
794
- const h = new Y.XmlElement('heading')
795
- h.setAttribute('level', 1)
796
- const ht = new Y.XmlText()
797
- ht.insert(0, 'Structure')
798
- h.insert(0, [ht])
799
- const intro = new Y.XmlElement('paragraph')
800
- const it = new Y.XmlText()
801
- it.insert(0, 'This page defines the sidebar tree: one "- slug" line per page, indent 2 spaces to nest. Pages missing here appear under "unfiled". Edit it directly or discuss changes as suggestions.')
802
- intro.insert(0, [it])
803
  const code = new Y.XmlElement('codeBlock')
804
  code.setAttribute('language', 'yaml')
805
  const ct = new Y.XmlText()
806
  ct.insert(0, defaultStructureYaml(slugs))
807
  code.insert(0, [ct])
808
- fragment.insert(0, [h, intro, code])
809
  })
810
  store.upsertPageMeta(projectId, '_structure', { title: 'Structure', createdAt: Date.now() })
811
  ensuredStructure.add(projectId)
 
77
  try {
78
  migrateLegacySuggestionAnchors(document)
79
  } catch {}
80
+ // structure pages are just the yaml field now — drop old heading/intro
81
+ try {
82
+ if (String(documentName).endsWith('::_structure') && !document.isDestroyed) {
83
+ const fragment = document.getXmlFragment(FIELD)
84
+ let hasCode = false
85
+ for (let i = 0; i < fragment.length; i++) {
86
+ if (fragment.get(i) instanceof Y.XmlElement && fragment.get(i).nodeName === 'codeBlock') hasCode = true
87
+ }
88
+ if (hasCode) {
89
+ document.transact(() => {
90
+ for (let i = fragment.length - 1; i >= 0; i--) {
91
+ const el2 = fragment.get(i)
92
+ if (!(el2 instanceof Y.XmlElement) || el2.nodeName !== 'codeBlock') fragment.delete(i, 1)
93
+ }
94
+ })
95
+ }
96
+ }
97
+ } catch {}
98
  }, 50)
99
  }
100
  },
 
809
  await withDoc(name, document => {
810
  const fragment = document.getXmlFragment(FIELD)
811
  if (fragment.length > 0) return
812
+ // just the yaml field — the explanation lives in the page chrome
 
 
 
 
 
 
 
 
813
  const code = new Y.XmlElement('codeBlock')
814
  code.setAttribute('language', 'yaml')
815
  const ct = new Y.XmlText()
816
  ct.insert(0, defaultStructureYaml(slugs))
817
  code.insert(0, [ct])
818
+ fragment.insert(0, [code])
819
  })
820
  store.upsertPageMeta(projectId, '_structure', { title: 'Structure', createdAt: Date.now() })
821
  ensuredStructure.add(projectId)
server/playground.js CHANGED
@@ -134,14 +134,7 @@ export async function seedPlayground(username, existingId = null) {
134
  await seedPage(id, home)
135
  await seedPage(docNameFor(id, 'notes'), NOTES_MD)
136
  store.upsertPageMeta(id, 'notes', { title: 'Notes', createdAt: Date.now() })
137
- await seedPage(docNameFor(id, '_structure'), `# Structure
138
-
139
- This page defines the sidebar tree: one "- slug" line per page, indent 2 spaces to nest. Pages missing here appear under "unfiled". Edit it directly or discuss changes as suggestions.
140
-
141
- \`\`\`yaml
142
- - home
143
- - notes
144
- \`\`\``)
145
  store.upsertPageMeta(id, '_structure', { title: 'Structure', createdAt: Date.now() })
146
 
147
  // block indices in HOME_MD (after image insertion): find dynamically
 
134
  await seedPage(id, home)
135
  await seedPage(docNameFor(id, 'notes'), NOTES_MD)
136
  store.upsertPageMeta(id, 'notes', { title: 'Notes', createdAt: Date.now() })
137
+ await seedPage(docNameFor(id, '_structure'), '```yaml\n- home\n- notes\n```')
 
 
 
 
 
 
 
138
  store.upsertPageMeta(id, '_structure', { title: 'Structure', createdAt: Date.now() })
139
 
140
  // block indices in HOME_MD (after image insertion): find dynamically