collabmd 0.1.47 → 0.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +9 -6
  2. package/dist/client/assets/JetBrainsMono-Regular-BQaDgvhP.woff2 +0 -0
  3. package/dist/client/assets/babel-CY5KOv-I.js +16 -0
  4. package/dist/client/assets/comment-ui-shared-DuoyznXW.js +7 -0
  5. package/dist/client/assets/dist-ChRoo3IB.js +9 -0
  6. package/dist/client/assets/document-formatter-Dxl_a8XO.js +2 -0
  7. package/dist/client/assets/{drawio-editor-BjrxTe3l.js → drawio-editor-BVaNNtYp.js} +2 -2
  8. package/dist/client/assets/{drawioEditor-DUVZjSSr.js → drawioEditor-BkIBlXx0.js} +2 -2
  9. package/dist/client/assets/editor-session-DMyVdHLj.js +54 -0
  10. package/dist/client/assets/embedded-editor-base-B3l6Xy65.css +1 -0
  11. package/dist/client/assets/estree-CuT_pB8K.js +44 -0
  12. package/dist/client/assets/{excalidraw-editor-BlcN6umJ.css → excalidraw-editor-BbSbI80a.css} +1 -1
  13. package/dist/client/assets/excalidraw-editor-BjYaNEMY.js +9 -0
  14. package/dist/client/assets/{excalidrawEditor-B0jOK3lv.js → excalidrawEditor-ChqF_d82.js} +2 -2
  15. package/dist/client/assets/export-page-Dyu0XCbu.js +57 -0
  16. package/dist/client/assets/exportDocument-CJBx4Xjx.css +1 -0
  17. package/dist/client/assets/{exportDocument-Dg9MjkIk.js → exportDocument-Dgvfa2Sd.js} +2 -2
  18. package/dist/client/assets/{file-icon-svg-CQAjlWjK.js → file-icon-svg-Y_kpF957.js} +1 -1
  19. package/dist/client/assets/file-kind-BwMIcZbi.js +1 -0
  20. package/dist/client/assets/file-search-DDLyyLDU.js +1 -0
  21. package/dist/client/assets/html-Cdnr2qzU.js +25 -0
  22. package/dist/client/assets/index-CSNIMyOP.css +1 -0
  23. package/dist/client/assets/index-DG3N2Ps-.js +2 -0
  24. package/dist/client/assets/main-D0wCxpVn.js +903 -0
  25. package/dist/client/assets/markdown-CByV-4CU.js +70 -0
  26. package/dist/client/assets/mermaid.core-BJRLUQZE.js +11 -0
  27. package/dist/client/assets/{preview-render-compiler-BApvlLLo.js → preview-render-compiler-CrJIooVD.js} +2 -2
  28. package/dist/client/assets/preview-render-compiler-O0JxBVNF.js +1 -0
  29. package/dist/client/assets/{preview-render-profile-CE-5rVTC.js → preview-render-profile-B4RWYWHt.js} +1 -1
  30. package/dist/client/assets/{preview-render-worker-BmN79bVc.js → preview-render-worker-_QTxqKvP.js} +22 -22
  31. package/dist/client/assets/quick-switcher-controller-CaEjEtq_.js +1 -0
  32. package/dist/client/assets/standalone-6z5x8CWe.js +29 -0
  33. package/dist/client/assets/{vault-api-client-_LpV1aIn.js → vault-api-client-BkwdFjx0.js} +2 -2
  34. package/dist/client/assets/{es-CAsWR6hG.js → vault-utils-DvoSftJT.js} +1 -1
  35. package/dist/client/assets/yaml-9ltHPDNO.js +154 -0
  36. package/dist/client/drawio-editor.html +1 -1
  37. package/dist/client/excalidraw-editor.html +1 -1
  38. package/dist/client/export-document.html +2 -2
  39. package/dist/client/index.html +41 -19
  40. package/package.json +4 -1
  41. package/src/client/app/index.html +39 -17
  42. package/src/client/application/app-shell/export-feature.js +17 -1
  43. package/src/client/application/app-shell/git-feature.js +28 -9
  44. package/src/client/application/app-shell/ui-feature-identity.js +13 -0
  45. package/src/client/application/app-shell/ui-feature-shell.js +164 -9
  46. package/src/client/application/app-shell-elements.js +4 -0
  47. package/src/client/application/workspace-coordinator.js +21 -4
  48. package/src/client/application/workspace-preview-controller.js +120 -2
  49. package/src/client/application/workspace-route-controller.js +4 -1
  50. package/src/client/assets/vendor/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
  51. package/src/client/assets/vendor/jetbrains-mono/OFL.txt +93 -0
  52. package/src/client/bootstrap/collabmd-app-shell.js +21 -5
  53. package/src/client/domain/document-formatter.js +193 -0
  54. package/src/client/domain/file-search.js +73 -0
  55. package/src/client/domain/git-labels.js +5 -0
  56. package/src/client/domain/vault-paths.js +6 -4
  57. package/src/client/domain/wiki-link-completions.js +20 -20
  58. package/src/client/drawio-editor.js +8 -3
  59. package/src/client/excalidraw-editor.js +152 -6
  60. package/src/client/export/export-host.js +56 -35
  61. package/src/client/export/export-page.js +14 -4
  62. package/src/client/export/export-pipeline.js +266 -25
  63. package/src/client/infrastructure/editor-session.js +4 -0
  64. package/src/client/infrastructure/editor-view-adapter.js +60 -14
  65. package/src/client/presentation/bases-preview-controller.js +35 -11
  66. package/src/client/presentation/file-action-controller.js +15 -2
  67. package/src/client/presentation/file-explorer-controller.js +3 -0
  68. package/src/client/presentation/file-explorer-view.js +2 -1
  69. package/src/client/presentation/file-history-view-controller.js +6 -1
  70. package/src/client/presentation/file-tree-state.js +1 -11
  71. package/src/client/presentation/git-diff-view-controller.js +36 -11
  72. package/src/client/presentation/git-panel-controller.js +10 -6
  73. package/src/client/presentation/quick-switcher-controller.js +54 -154
  74. package/src/client/styles/components/scrollbars.css +10 -0
  75. package/src/client/styles/export-document.css +44 -29
  76. package/src/client/styles/features/collaboration-presence.css +13 -1
  77. package/src/client/styles/features/preview-markdown.css +54 -0
  78. package/src/client/styles/foundation/tokens.css +8 -0
  79. package/src/client/styles/layout/shell.css +76 -24
  80. package/src/client/styles/layout/view-modes.css +2 -0
  81. package/src/client/styles/overrides/codemirror.css +31 -19
  82. package/src/client/styles/surfaces/excalidraw-editor.css +42 -0
  83. package/src/domain/file-kind.js +12 -1
  84. package/src/server/domain/ripgrep-search-service.js +110 -37
  85. package/src/server/infrastructure/persistence/path-utils.js +10 -4
  86. package/src/server/infrastructure/persistence/vault-file-store.js +2 -0
  87. package/dist/client/assets/comment-ui-shared-DWkk9Vh3.js +0 -7
  88. package/dist/client/assets/editor-session-DfZuRgB_.js +0 -54
  89. package/dist/client/assets/embedded-editor-base-DvMynhKu.css +0 -1
  90. package/dist/client/assets/excalidraw-editor-D7pnUCLH.js +0 -9
  91. package/dist/client/assets/export-page-Bs3xE1fg.js +0 -57
  92. package/dist/client/assets/exportDocument-PnYU3v1s.css +0 -1
  93. package/dist/client/assets/file-kind-BbqgGzpc.js +0 -1
  94. package/dist/client/assets/index-BIpgxyGZ.css +0 -1
  95. package/dist/client/assets/index-Z5sI0Bj-.js +0 -2
  96. package/dist/client/assets/main-Dwnj9Gaq.js +0 -903
  97. package/dist/client/assets/mermaid.core-CLZBlpIW.js +0 -11
  98. package/dist/client/assets/preview-render-compiler-BQ_3h9vD.js +0 -1
  99. package/dist/client/assets/quick-switcher-controller-ButWPmCI.js +0 -12
  100. package/dist/client/assets/vault-utils-wdQ0RNR9.js +0 -1
@@ -1,5 +1,4 @@
1
- import { stripVaultFileExtension } from '../../domain/file-kind.js';
2
- import { escapeHtml } from '../domain/vault-utils.js';
1
+ import { createFileSearchEntry, findFileSearchMatch } from '../domain/file-search.js';
3
2
  import { getVaultFileIconSvg } from './file-icon-svg.js';
4
3
  import {
5
4
  DEFAULT_SEARCH_DEBOUNCE_MS,
@@ -17,117 +16,15 @@ function getRawFileName(filePath) {
17
16
  return String(filePath ?? '').split('/').pop() || String(filePath ?? '');
18
17
  }
19
18
 
20
- function createCorpusEntry(filePath) {
21
- const displayName = stripVaultFileExtension(filePath);
22
- const fileName = displayName.split('/').pop() || displayName;
23
- const rawFileName = getRawFileName(filePath);
24
-
25
- return {
26
- displayName,
27
- fileName,
28
- filePath,
29
- lowerDisplayName: displayName.toLowerCase(),
30
- lowerFileName: fileName.toLowerCase(),
31
- lowerPath: String(filePath).toLowerCase(),
32
- lowerRawFileName: rawFileName.toLowerCase(),
33
- };
34
- }
35
-
36
19
  function getFileName(filePath) {
37
- const displayName = stripVaultFileExtension(filePath);
38
- return displayName.split('/').pop() || displayName;
20
+ return createFileSearchEntry(filePath).fileName;
39
21
  }
40
22
 
41
23
  function getDirPath(filePath) {
42
- const displayName = stripVaultFileExtension(filePath);
24
+ const displayName = createFileSearchEntry(filePath).displayName;
43
25
  return displayName.includes('/') ? displayName.substring(0, displayName.lastIndexOf('/')) : '';
44
26
  }
45
27
 
46
- function createRangeIndices(start, length) {
47
- return Array.from({ length }, (_, index) => start + index);
48
- }
49
-
50
- function findFuzzyMatch(text, query) {
51
- const indices = [];
52
- let queryIndex = 0;
53
- let score = 0;
54
- let consecutiveBonus = 0;
55
-
56
- for (let index = 0; index < text.length && queryIndex < query.length; index += 1) {
57
- if (/\s/u.test(query[queryIndex]) && /[\s/\\_-]/u.test(text[index])) {
58
- queryIndex += 1;
59
- consecutiveBonus = 0;
60
- continue;
61
- }
62
-
63
- if (text[index] !== query[queryIndex]) {
64
- if (queryIndex > 0) {
65
- score -= 0.25;
66
- }
67
- consecutiveBonus = 0;
68
- continue;
69
- }
70
-
71
- queryIndex += 1;
72
- indices.push(index);
73
- consecutiveBonus += 1;
74
- score += consecutiveBonus;
75
-
76
- if (
77
- index === 0
78
- || text[index - 1] === '/'
79
- || text[index - 1] === '-'
80
- || text[index - 1] === '_'
81
- || text[index - 1] === ' '
82
- ) {
83
- score += 5;
84
- }
85
- }
86
-
87
- return queryIndex === query.length && score > 0
88
- ? { indices, score }
89
- : null;
90
- }
91
-
92
- function findCorpusMatch(entry, query) {
93
- const fileIndex = entry.lowerFileName.indexOf(query);
94
- if (fileIndex >= 0) {
95
- return {
96
- indices: createRangeIndices(fileIndex + (entry.displayName.length - entry.fileName.length), query.length),
97
- score: 100 + (1 / entry.fileName.length),
98
- };
99
- }
100
-
101
- const pathIndex = entry.lowerDisplayName.indexOf(query);
102
- if (pathIndex >= 0) {
103
- return {
104
- indices: createRangeIndices(pathIndex, query.length),
105
- score: 50 + (1 / entry.displayName.length),
106
- };
107
- }
108
-
109
- const rawFileIndex = entry.lowerRawFileName.indexOf(query);
110
- if (rawFileIndex >= 0) {
111
- return {
112
- indices: createRangeIndices(
113
- rawFileIndex + (entry.displayName.length - entry.fileName.length),
114
- query.length,
115
- ),
116
- score: 40 + (1 / entry.lowerRawFileName.length),
117
- };
118
- }
119
-
120
- const rawPathIndex = entry.lowerPath.indexOf(query);
121
- if (rawPathIndex >= 0) {
122
- return {
123
- indices: createRangeIndices(rawPathIndex, query.length),
124
- score: 25 + (1 / entry.lowerPath.length),
125
- };
126
- }
127
-
128
- return findFuzzyMatch(entry.lowerDisplayName, query);
129
- }
130
-
131
28
  function splitMatchIndices(entry, indices = []) {
132
29
  const fileNameStart = entry.displayName.length - entry.fileName.length;
133
30
  return {
@@ -138,42 +35,25 @@ function splitMatchIndices(entry, indices = []) {
138
35
  };
139
36
  }
140
37
 
141
- function highlightText(text, indices = []) {
142
- if (indices.length === 0) {
143
- return escapeHtml(text);
144
- }
145
-
38
+ function appendHighlightedText(element, text, indices = []) {
146
39
  const matchedIndices = new Set(indices);
147
- let result = '';
148
- let runStart = null;
149
40
  let cursor = 0;
150
41
 
151
- const appendRun = (runEnd) => {
152
- result += escapeHtml(text.slice(cursor, runStart));
153
- result += `<mark>${escapeHtml(text.slice(runStart, runEnd + 1))}</mark>`;
154
- cursor = runEnd + 1;
155
- runStart = null;
156
- };
42
+ while (cursor < text.length) {
43
+ const matched = matchedIndices.has(cursor);
44
+ let end = cursor + 1;
45
+ while (end < text.length && matchedIndices.has(end) === matched) end += 1;
157
46
 
158
- for (let index = 0; index < text.length; index += 1) {
159
- if (!matchedIndices.has(index)) {
160
- if (runStart !== null) {
161
- appendRun(index - 1);
162
- }
163
- continue;
164
- }
165
-
166
- if (runStart === null) {
167
- runStart = index;
47
+ const value = text.slice(cursor, end);
48
+ if (matched) {
49
+ const mark = document.createElement('mark');
50
+ mark.textContent = value;
51
+ element.append(mark);
52
+ } else {
53
+ element.append(value);
168
54
  }
55
+ cursor = end;
169
56
  }
170
-
171
- if (runStart !== null) {
172
- appendRun(text.length - 1);
173
- }
174
-
175
- result += escapeHtml(text.slice(cursor));
176
- return result;
177
57
  }
178
58
 
179
59
  export class QuickSwitcherController {
@@ -429,7 +309,7 @@ export class QuickSwitcherController {
429
309
  const allFiles = this.getFileList?.() ?? [];
430
310
  if (allFiles !== this.lastFileListRef) {
431
311
  this.lastFileListRef = allFiles;
432
- this.fileCorpus = allFiles.map((filePath) => createCorpusEntry(filePath));
312
+ this.fileCorpus = allFiles.map((filePath) => createFileSearchEntry(filePath));
433
313
  }
434
314
 
435
315
  this.fileMatches.clear();
@@ -462,7 +342,7 @@ export class QuickSwitcherController {
462
342
  const ranked = [];
463
343
  let fileMatchCount = 0;
464
344
  this.fileCorpus.forEach((entry) => {
465
- const match = findCorpusMatch(entry, query);
345
+ const match = findFileSearchMatch(entry, query);
466
346
  if (!match) {
467
347
  return;
468
348
  }
@@ -556,11 +436,20 @@ export class QuickSwitcherController {
556
436
  const corpusEntry = this.fileCorpus.find((entry) => entry.filePath === filePath);
557
437
  const matchIndices = corpusEntry ? splitMatchIndices(corpusEntry, match?.indices) : { dirPath: [], fileName: [] };
558
438
 
559
- item.innerHTML = `
560
- ${getVaultFileIconSvg(filePath, { className: 'qs-result-icon' })}
561
- <span class="qs-result-name">${highlightText(fileName, matchIndices.fileName)}</span>
562
- ${dirPath ? `<span class="qs-result-path" title="${escapeHtml(dirPath)}">${highlightText(dirPath, matchIndices.dirPath)}</span>` : ''}
563
- `;
439
+ const svg = getVaultFileIconSvg(filePath, { className: 'qs-result-icon' });
440
+ item.append(document.createRange().createContextualFragment(svg));
441
+ const name = document.createElement('span');
442
+ name.className = 'qs-result-name';
443
+ appendHighlightedText(name, fileName, matchIndices.fileName);
444
+ item.append(name);
445
+
446
+ if (dirPath) {
447
+ const path = document.createElement('span');
448
+ path.className = 'qs-result-path';
449
+ path.title = dirPath;
450
+ appendHighlightedText(path, dirPath, matchIndices.dirPath);
451
+ item.append(path);
452
+ }
564
453
 
565
454
  item.addEventListener('click', () => {
566
455
  this.selectedIndex = index;
@@ -645,11 +534,16 @@ export class QuickSwitcherController {
645
534
 
646
535
  const header = document.createElement('div');
647
536
  header.className = 'qs-text-group-header';
648
- header.innerHTML = `
649
- <span class="qs-text-file-name">${escapeHtml(getFileName(fileGroup.file))}</span>
650
- <span class="qs-text-file-meta">${escapeHtml(getDirPath(fileGroup.file))}</span>
651
- <span class="qs-text-count">${escapeHtml(formatMatchCount(fileGroup.matchCount, { truncated: fileGroup.truncated }))}</span>
652
- `;
537
+ [
538
+ ['qs-text-file-name', getFileName(fileGroup.file)],
539
+ ['qs-text-file-meta', getDirPath(fileGroup.file)],
540
+ ['qs-text-count', formatMatchCount(fileGroup.matchCount, { truncated: fileGroup.truncated })],
541
+ ].forEach(([className, text]) => {
542
+ const span = document.createElement('span');
543
+ span.className = className;
544
+ span.textContent = text;
545
+ header.append(span);
546
+ });
653
547
  group.appendChild(header);
654
548
 
655
549
  (fileGroup.snippets ?? []).forEach((snippet) => {
@@ -664,10 +558,13 @@ export class QuickSwitcherController {
664
558
  item.setAttribute('role', 'option');
665
559
  item.setAttribute('aria-selected', itemIndex === this.selectedTextIndex ? 'true' : 'false');
666
560
  item.dataset.textIndex = String(itemIndex);
667
- item.innerHTML = `
668
- <span class="qs-text-line">L${escapeHtml(String(snippet.line ?? 1))}</span>
669
- <span class="qs-text-snippet">${this.highlightSnippet(snippet)}</span>
670
- `;
561
+ const line = document.createElement('span');
562
+ line.className = 'qs-text-line';
563
+ line.textContent = `L${snippet.line ?? 1}`;
564
+ const snippetText = document.createElement('span');
565
+ snippetText.className = 'qs-text-snippet';
566
+ this.appendHighlightedSnippet(snippetText, snippet);
567
+ item.append(line, snippetText);
671
568
  item.addEventListener('click', () => {
672
569
  this.selectedTextIndex = itemIndex;
673
570
  this.confirmSelection();
@@ -687,11 +584,14 @@ export class QuickSwitcherController {
687
584
  this.updateSelection();
688
585
  }
689
586
 
690
- highlightSnippet(snippet = {}) {
587
+ appendHighlightedSnippet(element, snippet = {}) {
691
588
  const text = String(snippet.text ?? '');
692
589
  const start = Math.min(Math.max(Number(snippet.matchStart) || 0, 0), text.length);
693
590
  const end = Math.min(Math.max(Number(snippet.matchEnd) || start, start), text.length);
694
- return `${escapeHtml(text.slice(0, start))}<mark>${escapeHtml(text.slice(start, end))}</mark>${escapeHtml(text.slice(end))}`;
591
+ element.append(text.slice(0, start));
592
+ const mark = document.createElement('mark');
593
+ mark.textContent = text.slice(start, end);
594
+ element.append(mark, text.slice(end));
695
595
  }
696
596
 
697
597
  moveSelection(delta) {
@@ -2,6 +2,8 @@
2
2
 
3
3
  .preview-container,
4
4
  .cm-scroller,
5
+ .cm-tooltip-autocomplete > ul,
6
+ .presence-panel-list,
5
7
  .file-tree,
6
8
  .git-panel,
7
9
  .diff-scroll,
@@ -25,6 +27,8 @@
25
27
 
26
28
  .preview-container::-webkit-scrollbar,
27
29
  .cm-scroller::-webkit-scrollbar,
30
+ .cm-tooltip-autocomplete > ul::-webkit-scrollbar,
31
+ .presence-panel-list::-webkit-scrollbar,
28
32
  .file-tree::-webkit-scrollbar,
29
33
  .git-panel::-webkit-scrollbar,
30
34
  .diff-scroll::-webkit-scrollbar,
@@ -48,6 +52,8 @@
48
52
 
49
53
  .preview-container::-webkit-scrollbar-track,
50
54
  .cm-scroller::-webkit-scrollbar-track,
55
+ .cm-tooltip-autocomplete > ul::-webkit-scrollbar-track,
56
+ .presence-panel-list::-webkit-scrollbar-track,
51
57
  .file-tree::-webkit-scrollbar-track,
52
58
  .git-panel::-webkit-scrollbar-track,
53
59
  .diff-scroll::-webkit-scrollbar-track,
@@ -70,6 +76,8 @@
70
76
 
71
77
  .preview-container::-webkit-scrollbar-thumb,
72
78
  .cm-scroller::-webkit-scrollbar-thumb,
79
+ .cm-tooltip-autocomplete > ul::-webkit-scrollbar-thumb,
80
+ .presence-panel-list::-webkit-scrollbar-thumb,
73
81
  .file-tree::-webkit-scrollbar-thumb,
74
82
  .git-panel::-webkit-scrollbar-thumb,
75
83
  .diff-scroll::-webkit-scrollbar-thumb,
@@ -93,6 +101,8 @@
93
101
 
94
102
  .preview-container::-webkit-scrollbar-thumb:hover,
95
103
  .cm-scroller::-webkit-scrollbar-thumb:hover,
104
+ .cm-tooltip-autocomplete > ul::-webkit-scrollbar-thumb:hover,
105
+ .presence-panel-list::-webkit-scrollbar-thumb:hover,
96
106
  .file-tree::-webkit-scrollbar-thumb:hover,
97
107
  .git-panel::-webkit-scrollbar-thumb:hover,
98
108
  .diff-scroll::-webkit-scrollbar-thumb:hover,
@@ -63,42 +63,31 @@ body[data-surface='export-document']::-webkit-scrollbar {
63
63
  padding-left: 20px;
64
64
  }
65
65
 
66
- .export-content {
67
- padding: 32px 40px;
68
- border: 1px solid var(--color-divider);
69
- border-radius: 20px;
70
- background: var(--color-surface);
71
- box-shadow: var(--shadow-card-strong);
72
- }
73
-
74
- .export-content p,
75
- .export-content li,
76
- .export-content blockquote {
77
- max-width: none;
78
- }
79
-
80
66
  .export-content .task-list-item input[type='checkbox'] {
81
67
  cursor: default;
82
68
  }
83
69
 
84
- .export-content .table-wrapper {
85
- overflow: visible;
70
+ .export-directory-toc {
71
+ margin-bottom: 48px;
86
72
  }
87
73
 
88
- .export-content .bases-table-shell,
89
- .export-content pre {
90
- overflow: visible;
74
+ .export-directory-toc ol {
75
+ columns: 2;
91
76
  }
92
77
 
93
- .export-content table {
94
- white-space: normal;
78
+ .export-document-section + .export-document-section {
79
+ margin-top: 64px;
80
+ padding-top: 48px;
81
+ border-top: 1px solid var(--color-divider);
95
82
  }
96
83
 
97
- .export-content pre code {
98
- width: auto;
99
- white-space: pre-wrap;
100
- word-break: break-word;
101
- overflow-wrap: anywhere;
84
+ .export-document-heading {
85
+ margin-bottom: 32px;
86
+ }
87
+
88
+ .export-document-heading p {
89
+ color: var(--color-text-muted);
90
+ font-size: 13px;
102
91
  }
103
92
 
104
93
  .export-diagram {
@@ -243,19 +232,45 @@ body[data-surface='export-document']::-webkit-scrollbar {
243
232
 
244
233
  .export-content {
245
234
  width: auto;
235
+ max-width: none;
246
236
  padding: 0;
247
- border: 0;
248
- border-radius: 0;
249
- box-shadow: none;
250
237
  overflow: visible;
251
238
  }
252
239
 
240
+ .export-content p,
241
+ .export-content li,
242
+ .export-content blockquote {
243
+ max-width: none;
244
+ }
245
+
246
+ .export-directory-toc {
247
+ break-after: page;
248
+ }
249
+
250
+ .export-document-section {
251
+ break-before: page;
252
+ }
253
+
254
+ .export-document-section:first-of-type {
255
+ break-before: auto;
256
+ }
257
+
258
+ .export-document-section + .export-document-section {
259
+ margin-top: 0;
260
+ padding-top: 0;
261
+ border-top: 0;
262
+ }
263
+
253
264
  .export-content pre,
254
265
  .export-content .table-wrapper,
255
266
  .export-content .bases-table-shell {
256
267
  overflow: visible !important;
257
268
  }
258
269
 
270
+ .export-content table {
271
+ white-space: normal;
272
+ }
273
+
259
274
  .export-content pre code {
260
275
  width: auto;
261
276
  white-space: pre-wrap;
@@ -9,6 +9,19 @@
9
9
  align-items: center;
10
10
  }
11
11
 
12
+ .toolbar-presence-cluster {
13
+ display: inline-flex;
14
+ align-items: center;
15
+ gap: var(--space-1);
16
+ flex-shrink: 0;
17
+ }
18
+
19
+ .toolbar-presence-count {
20
+ background: transparent;
21
+ padding-inline: var(--space-1);
22
+ min-height: auto;
23
+ }
24
+
12
25
  .toolbar-badge-button {
13
26
  cursor: pointer;
14
27
  transition: background var(--transition-interactive), box-shadow var(--transition-interactive), color var(--transition-interactive);
@@ -68,7 +81,6 @@
68
81
  padding: 0 8px 0 6px;
69
82
  gap: 4px;
70
83
  border-radius: var(--radius-full);
71
- box-shadow: var(--shadow-accent-ring);
72
84
  }
73
85
 
74
86
  .user-avatar.is-local + .user-avatar {
@@ -1,7 +1,26 @@
1
1
  /* ===== PREVIEW / MARKDOWN ===== */
2
2
 
3
3
  .preview-content {
4
+ position: relative;
5
+ min-width: 0;
6
+ max-width: var(--preview-content-max-width, var(--preview-content-width));
7
+ margin: 0 auto;
8
+ overflow: visible;
9
+ padding: var(--space-6) var(--space-6) var(--space-12);
10
+ color: var(--color-text);
4
11
  font-family: var(--font-prose);
12
+ font-size: var(--text-sm);
13
+ line-height: 1.7;
14
+ }
15
+
16
+ .preview-content[data-render-phase='shell'] {
17
+ color: var(--color-text-muted);
18
+ }
19
+
20
+ @media (min-width: 769px) {
21
+ .preview-content {
22
+ padding-bottom: calc(var(--space-12) + 96px);
23
+ }
5
24
  }
6
25
 
7
26
  .preview-content h1,
@@ -52,6 +71,40 @@
52
71
  font-weight: 600;
53
72
  }
54
73
 
74
+ .preview-content .preview-heading-fold-button {
75
+ margin-inline: 0 var(--space-2);
76
+ border: 0;
77
+ border-radius: var(--radius-full);
78
+ font-size: var(--text-sm);
79
+ line-height: 1;
80
+ vertical-align: middle;
81
+ }
82
+
83
+ .preview-content .preview-heading-fold-button::before {
84
+ width: 8px;
85
+ height: 8px;
86
+ border-right: 2px solid currentColor;
87
+ border-bottom: 2px solid currentColor;
88
+ content: '';
89
+ transform: rotate(-45deg);
90
+ transition: transform var(--transition-interactive);
91
+ }
92
+
93
+ .preview-content .preview-heading-fold-button[aria-expanded='true']::before {
94
+ transform: rotate(45deg);
95
+ }
96
+
97
+ .preview-content .preview-heading-fold-button:hover {
98
+ background: color-mix(in oklab, var(--color-primary) 12%, transparent);
99
+ color: var(--color-primary);
100
+ }
101
+
102
+ .preview-content .preview-heading-fold-button:focus-visible {
103
+ box-shadow: var(--shadow-focus-compact);
104
+ color: var(--color-primary);
105
+ outline: 0;
106
+ }
107
+
55
108
  .preview-content .preview-heading-link-button {
56
109
  display: inline-flex;
57
110
  width: 1.18em;
@@ -231,6 +284,7 @@
231
284
  }
232
285
 
233
286
  .preview-content p:has(> .excalidraw-embed-placeholder:only-child),
287
+ .preview-content p:has(> .drawio-embed-placeholder:only-child),
234
288
  .preview-content p:has(> .bases-embed-placeholder:only-child),
235
289
  .preview-content p:has(> .video-embed-placeholder:only-child),
236
290
  .preview-content p:has(> .plantuml-shell:only-child) {
@@ -1,5 +1,13 @@
1
1
  /* ===== FOUNDATION / TOKENS ===== */
2
2
 
3
+ @font-face {
4
+ font-display: swap;
5
+ font-family: 'JetBrains Mono';
6
+ font-style: normal;
7
+ font-weight: 400;
8
+ src: url('../../assets/vendor/jetbrains-mono/JetBrainsMono-Regular.woff2') format('woff2');
9
+ }
10
+
3
11
  :root {
4
12
  --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
5
13
  --text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
@@ -62,6 +62,7 @@
62
62
  align-items: baseline;
63
63
  gap: var(--space-3);
64
64
  min-width: 0;
65
+ overflow: hidden;
65
66
  }
66
67
 
67
68
  .pane-label {
@@ -76,6 +77,9 @@
76
77
  color: var(--color-text-muted);
77
78
  font-family: var(--font-mono);
78
79
  font-size: var(--text-meta);
80
+ white-space: nowrap;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
79
83
  }
80
84
 
81
85
  .resizer {
@@ -171,29 +175,6 @@
171
175
  overscroll-behavior: contain;
172
176
  }
173
177
 
174
- .preview-content {
175
- position: relative;
176
- min-width: 0;
177
- max-width: var(--preview-content-max-width);
178
- margin: 0 auto;
179
- overflow: visible;
180
- padding: var(--space-6) var(--space-6) var(--space-12);
181
- color: var(--color-text);
182
- font-family: var(--font-body);
183
- font-size: var(--text-sm);
184
- line-height: 1.7;
185
- }
186
-
187
- .preview-content[data-render-phase='shell'] {
188
- color: var(--color-text-muted);
189
- }
190
-
191
- @media (min-width: 769px) {
192
- .preview-content {
193
- padding-bottom: calc(var(--space-12) + 96px);
194
- }
195
- }
196
-
197
178
  .preview-content.is-excalidraw-file-preview {
198
179
  position: relative;
199
180
  display: flex;
@@ -217,7 +198,8 @@
217
198
  padding: var(--space-4);
218
199
  }
219
200
 
220
- .preview-content.is-pdf-file-preview {
201
+ .preview-content.is-pdf-file-preview,
202
+ .preview-content.is-html-file-preview {
221
203
  position: relative;
222
204
  display: flex;
223
205
  box-sizing: border-box;
@@ -277,6 +259,7 @@
277
259
  .preview-content.is-excalidraw-file-preview [data-preview-render-host='true'],
278
260
  .preview-content.is-image-file-preview [data-preview-render-host='true'],
279
261
  .preview-content.is-pdf-file-preview [data-preview-render-host='true'],
262
+ .preview-content.is-html-file-preview [data-preview-render-host='true'],
280
263
  .preview-content.is-base-file-preview [data-preview-render-host='true'] {
281
264
  display: flex;
282
265
  flex: 1;
@@ -290,6 +273,75 @@
290
273
  flex-direction: column;
291
274
  }
292
275
 
276
+ .preview-content.is-html-file-preview [data-preview-render-host='true'] {
277
+ height: 100%;
278
+ min-height: 0;
279
+ }
280
+
281
+ .html-file-preview-shell {
282
+ position: relative;
283
+ display: flex;
284
+ flex: 1;
285
+ flex-direction: column;
286
+ width: 100%;
287
+ min-width: 0;
288
+ min-height: 0;
289
+ height: 100%;
290
+ }
291
+
292
+ .html-file-preview-shell.is-maximized {
293
+ position: fixed;
294
+ inset: var(--space-3);
295
+ z-index: 172;
296
+ width: auto;
297
+ height: auto;
298
+ padding: var(--space-3);
299
+ border-radius: var(--radius-lg);
300
+ background: var(--color-bg);
301
+ box-shadow: var(--shadow-lg);
302
+ }
303
+
304
+ body.html-preview-maximized-open {
305
+ overflow: hidden;
306
+ }
307
+
308
+ body.html-preview-maximized-open #htmlPreviewMaximize {
309
+ position: fixed;
310
+ top: var(--space-5);
311
+ right: var(--space-5);
312
+ z-index: 174;
313
+ background: var(--color-surface-raised);
314
+ box-shadow: var(--shadow-md);
315
+ }
316
+
317
+ .html-file-preview-script-gate {
318
+ display: flex;
319
+ flex-wrap: wrap;
320
+ align-items: center;
321
+ justify-content: space-between;
322
+ gap: var(--space-2);
323
+ margin-bottom: var(--space-2);
324
+ padding: var(--space-2);
325
+ border: 1px solid var(--border-warning-toast);
326
+ border-radius: var(--radius-md);
327
+ background: var(--surface-warning-soft);
328
+ color: var(--color-text);
329
+ font-size: var(--text-xs);
330
+ }
331
+
332
+ .html-file-preview-frame {
333
+ display: block;
334
+ flex: 1;
335
+ box-sizing: border-box;
336
+ width: 100%;
337
+ min-width: 0;
338
+ min-height: 0;
339
+ height: 100%;
340
+ border: 1px solid var(--color-divider);
341
+ border-radius: var(--radius-lg);
342
+ background: white;
343
+ }
344
+
293
345
  .image-file-preview-shell {
294
346
  display: flex;
295
347
  align-items: center;