collabmd 0.1.29 → 0.1.30
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.
- package/README.md +5 -0
- package/dist/client/assets/{CodeMirrorEditor-UCMRAMCV-9k1Shd4H.js → CodeMirrorEditor-UCMRAMCV-tj0_ycIx.js} +1 -1
- package/dist/client/assets/architectureDiagram-2XIMDMQ5-BPG5b65m.js +36 -0
- package/dist/client/assets/base-CaIEJUF9.css +1 -0
- package/dist/client/assets/{blockDiagram-WCTKOSBZ-0rR9ivwb.js → blockDiagram-WCTKOSBZ-F38TRzI3.js} +1 -1
- package/dist/client/assets/{chunk-ETSVUAFD-CN-gPl_j.js → chunk-AXZTUZBK-Cxfm7hmm.js} +1 -1
- package/dist/client/assets/{chunk-GLR3WWYH-ZnuaBvXp.js → chunk-GLR3WWYH-CQ1LryrI.js} +2 -2
- package/dist/client/assets/{chunk-NQ4KR5QH-BCjGNd0g.js → chunk-NQ4KR5QH-BBX89uUa.js} +1 -1
- package/dist/client/assets/chunk-NU2UP6EN-CGeKI3BV.js +35 -0
- package/dist/client/assets/{chunk-WL4C6EOR-OuH0lN6M.js → chunk-WL4C6EOR-zSV-8h7F.js} +1 -1
- package/dist/client/assets/classDiagram-VBA2DB6C-BQqG4q1b.js +1 -0
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-BUKbprX1.js +1 -0
- package/dist/client/assets/dagre-KLK3FWXG-CAG8WeES.js +4 -0
- package/dist/client/assets/diagram-E7M64L7V-Bm5FD0I5.js +24 -0
- package/dist/client/assets/diagram-IFDJBPK2-pqicH-av.js +43 -0
- package/dist/client/assets/diagram-P4PSJMXO-DwIXO27H.js +24 -0
- package/dist/client/assets/dist-3bQB_we3.js +1 -0
- package/dist/client/assets/dist-3uAMa5w5.js +1 -0
- package/dist/client/assets/{dist-D9c0A4Uh.js → dist-B-02vKOA.js} +1 -1
- package/dist/client/assets/{dist-A0eSMIyy.js → dist-BLAlZer_.js} +1 -1
- package/dist/client/assets/dist-BVB8rn7o.js +1 -0
- package/dist/client/assets/{dist-DveOCUUJ.js → dist-Bc2ND6i3.js} +1 -1
- package/dist/client/assets/{dist-BNcU46Ik.js → dist-BqQjnEWr.js} +1 -1
- package/dist/client/assets/{dist-Bay3vk5z.js → dist-BtnXjRx0.js} +1 -1
- package/dist/client/assets/{dist-CEYpWxod.js → dist-BzbYwa8m.js} +1 -1
- package/dist/client/assets/{dist-Ct1bmQnL.js → dist-BzpfHRaE.js} +1 -1
- package/dist/client/assets/{dist-BbKpzsTB.js → dist-CGV1S_as.js} +1 -1
- package/dist/client/assets/dist-CL-jjdvG.js +1 -0
- package/dist/client/assets/{dist-D0e6Qsad.js → dist-CXpKYW9s.js} +1 -1
- package/dist/client/assets/{dist-BchrxlnW.js → dist-CjII8VDS.js} +1 -1
- package/dist/client/assets/{dist-DnrO-JZd.js → dist-CxfohGIV.js} +1 -1
- package/dist/client/assets/{dist-CMHIjMVe.js → dist-D5QOYy7y.js} +1 -1
- package/dist/client/assets/{dist-CEtbxXlI.js → dist-DIi0uqLJ.js} +1 -1
- package/dist/client/assets/dist-DJ16N7ol.js +1 -0
- package/dist/client/assets/dist-DUvaQ-Bj.js +1 -0
- package/dist/client/assets/{dist-DnxPQ0-w.js → dist-DWJL3_bF.js} +6 -6
- package/dist/client/assets/dist-DsL7sqZR.js +1 -0
- package/dist/client/assets/{dist-DbhQ5ANS.js → dist-Dyt7n5Ui.js} +1 -1
- package/dist/client/assets/{dist-BOrlJDrR.js → dist-E_jPb93e.js} +1 -1
- package/dist/client/assets/{dist-D-gRgZ6e.js → dist-O1z0Ayg0.js} +1 -1
- package/dist/client/assets/dist-OQE68wIX.js +1 -0
- package/dist/client/assets/dist-k47-Io7D.js +1 -0
- package/dist/client/assets/{dist-Cdzvi8cf.js → dist-lDhnP-K1.js} +1 -1
- package/dist/client/assets/{dist-DS6mEQYy.js → dist-rMhng5DK.js} +1 -1
- package/dist/client/assets/dist-xO1ImhKz.js +1 -0
- package/dist/client/assets/{drawio-editor-CgbaOvpF.js → drawio-editor-CTZtMo1J.js} +1 -1
- package/dist/client/assets/{drawioEditor-Eb2yYhjE.js → drawioEditor-keSJVTcv.js} +2 -2
- package/dist/client/assets/editor-session-C9l9XwVg.js +16 -0
- package/dist/client/assets/{embedded-editor-base-DXRHYz6R.css → embedded-editor-base-DgmRcsR8.css} +1 -1
- package/dist/client/assets/{en-CTDJDZ6K-BHbXrqU0.js → en-IQZW2TQM-CZeIyvu2.js} +1 -1
- package/dist/client/assets/{erDiagram-INFDFZHY-DP3f20BF.js → erDiagram-INFDFZHY-DlhPv4az.js} +1 -1
- package/dist/client/assets/{excalidraw-editor-Dj0c0PO6.js → excalidraw-editor-CBEWfuhb.js} +2 -2
- package/dist/client/assets/excalidraw-editor-Dj3i59LW.css +1 -0
- package/dist/client/assets/excalidraw-scene-BL-OtvCd.js +176 -0
- package/dist/client/assets/excalidrawEditor-Ws-fwGOc.js +2 -0
- package/dist/client/assets/{export-page-CUOMczdp.js → export-page-B6xYnwy7.js} +2 -2
- package/dist/client/assets/exportDocument-8JBw0p8O.js +2 -0
- package/dist/client/assets/{exportDocument-CLPYN_gg.css → exportDocument-CtX_4PSk.css} +1 -1
- package/dist/client/assets/{flowDiagram-PKNHOUZH-CRbK5_5h.js → flowDiagram-PKNHOUZH-BU4MOSq8.js} +1 -1
- package/dist/client/assets/ganttDiagram-A5KZAMGK-CbX67lZu.js +292 -0
- package/dist/client/assets/gitGraphDiagram-K3NZZRJ6-DhyvWcW1.js +65 -0
- package/dist/client/assets/image-XLNPCCAD-B4wZgh-Y.js +1 -0
- package/dist/client/assets/{index-CVgVzuh6.js → index-CywRKxsk.js} +2 -2
- package/dist/client/assets/index-R4OArFku.css +1 -0
- package/dist/client/assets/infoDiagram-LFFYTUFH-Bap6MyHX.js +2 -0
- package/dist/client/assets/{ishikawaDiagram-PHBUUO56-BDVPgkv4.js → ishikawaDiagram-PHBUUO56-DbftskqN.js} +1 -1
- package/dist/client/assets/{journeyDiagram-4ABVD52K-DtNnwL48.js → journeyDiagram-4ABVD52K-165YoX6B.js} +1 -1
- package/dist/client/assets/{jsx-runtime-B8dyIONw.js → jsx-runtime-DiaMmmG6.js} +1 -1
- package/dist/client/assets/{kanban-definition-K7BYSVSG-C565sUNo.js → kanban-definition-K7BYSVSG-MlixoNyx.js} +1 -1
- package/dist/client/assets/{main-C8adLQnt.js → main-CB97rY6B.js} +172 -133
- package/dist/client/assets/mermaid-parser.core-lf87rdG-.js +4 -0
- package/dist/client/assets/{mermaid-runtime-DqncQoYK.js → mermaid-runtime-C4a7TrTS.js} +3 -3
- package/dist/client/assets/{mindmap-definition-YRQLILUH-BbYzGfTD.js → mindmap-definition-YRQLILUH-_YbQSc4Q.js} +1 -1
- package/dist/client/assets/pieDiagram-SKSYHLDU-CqCgXFom.js +30 -0
- package/dist/client/assets/preview-render-compiler-TmHpBDlh.js +1 -0
- package/dist/client/assets/preview-render-compiler-aYpnXmNm.js +35 -0
- package/dist/client/assets/preview-render-worker-BVbVXXPS.js +59 -0
- package/dist/client/assets/{quadrantDiagram-337W2JSQ-DhlKLozd.js → quadrantDiagram-337W2JSQ-B8eMuhg4.js} +1 -1
- package/dist/client/assets/{requirementDiagram-Z7DCOOCP-B7ZOMsIb.js → requirementDiagram-Z7DCOOCP-B6POvqz4.js} +1 -1
- package/dist/client/assets/{room-B0UGbzQj.js → room-COWSP--i.js} +1 -1
- package/dist/client/assets/stateDiagram-RAJIS63D-DbewhqwJ.js +1 -0
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-DbW-jVkS.js +1 -0
- package/dist/client/assets/{vennDiagram-LZ73GAT5-Cs9aQmVM.js → vennDiagram-LZ73GAT5-C1hrSbf2.js} +1 -1
- package/dist/client/assets/{xychartDiagram-JWTSCODW-B40ybbzC.js → xychartDiagram-JWTSCODW-DhpWp8LV.js} +1 -1
- package/dist/client/assets/{yjs-provider-reset-guard-DUP9XupO.js → yjs-provider-reset-guard-DWGhQClh.js} +1 -1
- package/dist/client/drawio-editor.html +1 -1
- package/dist/client/excalidraw-editor.html +1 -1
- package/dist/client/export-document.html +4 -4
- package/dist/client/index.html +4 -4
- package/docker-compose.yml +1 -0
- package/package.json +23 -21
- package/src/client/application/app-shell/ui-feature-shell.js +47 -1
- package/src/client/application/app-shell/ui-feature-toolbar.js +25 -3
- package/src/client/application/app-shell-elements.js +2 -0
- package/src/client/application/diagram-preview-hydrator.js +1 -1
- package/src/client/application/markdown-frontmatter.js +152 -0
- package/src/client/application/preview-render-compiler.js +10 -1
- package/src/client/application/preview-render-executor.js +8 -1
- package/src/client/application/preview-render-worker.js +4 -0
- package/src/client/application/preview-renderer.js +51 -1
- package/src/client/application/workspace-preview-controller.js +1 -0
- package/src/client/bootstrap/collabmd-app-shell.js +5 -0
- package/src/client/domain/markdown-formatting.js +1 -1
- package/src/client/domain/markdown-toolbar-actions.js +36 -0
- package/src/client/export/export-pipeline.js +3 -1
- package/src/client/infrastructure/editor-session.js +4 -0
- package/src/client/infrastructure/editor-view-adapter.js +36 -2
- package/src/client/infrastructure/excalidraw-room-client.js +12 -4
- package/src/client/presentation/app-shell-template.js +15 -0
- package/src/client/presentation/file-action-controller.js +1 -2
- package/src/client/presentation/git-diff-view-controller.js +7 -10
- package/src/client/styles/base.css +5 -0
- package/src/client/styles/features/preview-markdown.css +138 -0
- package/src/client/styles/layout/editor-page.css +6 -0
- package/src/client/styles/layout/responsive.css +89 -0
- package/src/client/styles/layout/shell.css +6 -0
- package/src/client/styles/overrides/codemirror.css +74 -0
- package/src/server/auth/create-auth-service.js +32 -6
- package/src/server/config/env.js +27 -0
- package/src/server/config/perf-logging.js +39 -0
- package/src/server/create-app-server.js +102 -5
- package/src/server/domain/backlink-index.js +31 -7
- package/src/server/domain/collaboration/collaboration-room.js +28 -5
- package/src/server/infrastructure/git/history-service.js +1 -1
- package/src/server/infrastructure/persistence/pull-backup-store.js +1 -1
- package/src/server/infrastructure/persistence/vault-file-store.js +74 -31
- package/src/server/infrastructure/workspace/file-system-sync-service.js +46 -12
- package/src/server/infrastructure/workspace/workspace-mutation-coordinator.js +32 -7
- package/src/server/shared/async-utils.js +25 -0
- package/dist/client/assets/architectureDiagram-2XIMDMQ5-Bhh8YQHn.js +0 -36
- package/dist/client/assets/base-B5gONpJK.css +0 -1
- package/dist/client/assets/chunk-27N2SPOM-DbHKsQ_j.js +0 -34
- package/dist/client/assets/classDiagram-VBA2DB6C-Bpdym3dj.js +0 -1
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-CRAsdVDB.js +0 -1
- package/dist/client/assets/dagre-KLK3FWXG-CXSnjrX-.js +0 -4
- package/dist/client/assets/diagram-E7M64L7V-De0StFef.js +0 -24
- package/dist/client/assets/diagram-IFDJBPK2-B2m6AJbU.js +0 -43
- package/dist/client/assets/diagram-P4PSJMXO-YBWLVENw.js +0 -24
- package/dist/client/assets/dist-BUEzOS23.js +0 -1
- package/dist/client/assets/dist-BflL-_8D.js +0 -1
- package/dist/client/assets/dist-CI_qEj5K.js +0 -1
- package/dist/client/assets/dist-CtFNDttZ.js +0 -1
- package/dist/client/assets/dist-DS_4VpEd.js +0 -1
- package/dist/client/assets/dist-DwsFUbuB.js +0 -1
- package/dist/client/assets/dist-VCYAp958.js +0 -1
- package/dist/client/assets/dist-Yxt8txtM.js +0 -1
- package/dist/client/assets/dist-npxgF_6V.js +0 -1
- package/dist/client/assets/dist-u0zDRMQt.js +0 -1
- package/dist/client/assets/editor-session-DFzBn4RY.js +0 -16
- package/dist/client/assets/excalidraw-editor-Bat1FRoT.css +0 -1
- package/dist/client/assets/excalidraw-scene-CixfM-jL.js +0 -174
- package/dist/client/assets/excalidrawEditor-diK4VITg.js +0 -2
- package/dist/client/assets/exportDocument-BM4_f2n8.js +0 -2
- package/dist/client/assets/ganttDiagram-A5KZAMGK-R-AMIgxR.js +0 -292
- package/dist/client/assets/gitGraphDiagram-K3NZZRJ6-CIPit_Sh.js +0 -65
- package/dist/client/assets/image-32MOCVDX-BB6utPzr.js +0 -1
- package/dist/client/assets/index-BOOhEiLo.css +0 -1
- package/dist/client/assets/infoDiagram-LFFYTUFH-CF8jXFTm.js +0 -2
- package/dist/client/assets/mermaid-parser.core-By_5k2SF.js +0 -4
- package/dist/client/assets/pieDiagram-SKSYHLDU-CAU_gC7U.js +0 -30
- package/dist/client/assets/preview-render-compiler-CVfDHnqM.js +0 -1
- package/dist/client/assets/preview-render-compiler-CapHJDHT.js +0 -1
- package/dist/client/assets/preview-render-worker-wMS710Bv.js +0 -23
- package/dist/client/assets/stateDiagram-RAJIS63D-CPoSrNwn.js +0 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-5GW0Y21a.js +0 -1
- /package/dist/client/assets/{base-r5Zg2n3k.js → base-h1uscjTi.js} +0 -0
|
@@ -242,6 +242,14 @@ export function appShellTemplate() {
|
|
|
242
242
|
<span class="ui-toolbar-button-label">Share</span>
|
|
243
243
|
</button>
|
|
244
244
|
|
|
245
|
+
<button class=${buttonClassNames({ variant: 'ghost', toolbar: true, extra: 'toolbar-search-files-btn' })} id="searchFilesBtn" aria-label="Search files" title="Search files">
|
|
246
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
|
247
|
+
<circle cx="11" cy="11" r="7"></circle>
|
|
248
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
249
|
+
</svg>
|
|
250
|
+
<span class="ui-toolbar-button-label">Search files</span>
|
|
251
|
+
</button>
|
|
252
|
+
|
|
245
253
|
<details class="toolbar-overflow-group hidden" id="exportMenuGroup">
|
|
246
254
|
<summary class=${buttonClassNames({ variant: 'ghost', toolbar: true, extra: 'toolbar-overflow-group-toggle' })} aria-label="Export options" title="Export">
|
|
247
255
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
@@ -327,6 +335,13 @@ export function appShellTemplate() {
|
|
|
327
335
|
<span class="pane-label-meta" id="lineInfo">Ln 1, Col 1</span>
|
|
328
336
|
</div>
|
|
329
337
|
<div class="pane-header-actions">
|
|
338
|
+
<button class=${buttonClassNames({ variant: 'ghost', size: 'compact', toggle: true, extra: 'mobile-editor-find-btn' })} id="editorFindBtn" aria-label="Find in file" title="Find in file">
|
|
339
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
|
340
|
+
<circle cx="11" cy="11" r="7"></circle>
|
|
341
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
342
|
+
</svg>
|
|
343
|
+
<span class="ui-action-label">Find</span>
|
|
344
|
+
</button>
|
|
330
345
|
<button class=${buttonClassNames({ variant: 'ghost', size: 'compact', toggle: true, extra: 'hidden' })} id="commentSelectionBtn" aria-label="Comment current line or selection" title="Comment current line or selection">
|
|
331
346
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
332
347
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
@@ -436,11 +436,10 @@ export class FileActionController {
|
|
|
436
436
|
|
|
437
437
|
this.setActionDialogBusy(true);
|
|
438
438
|
|
|
439
|
-
let shouldClose
|
|
439
|
+
let shouldClose;
|
|
440
440
|
try {
|
|
441
441
|
shouldClose = await this.pendingAction.onSubmit(rawValue);
|
|
442
442
|
} catch (error) {
|
|
443
|
-
shouldClose = false;
|
|
444
443
|
this.showError('Failed to complete action', error);
|
|
445
444
|
}
|
|
446
445
|
|
|
@@ -696,16 +696,13 @@ export class GitDiffViewController {
|
|
|
696
696
|
const errorMessage = this.fileErrors.get(file.path);
|
|
697
697
|
const detail = this.getFileDetail(file.path) ?? file;
|
|
698
698
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
} else {
|
|
707
|
-
bodyMarkup = this.renderDiffDetail(detail, index, { includeHeader: false });
|
|
708
|
-
}
|
|
699
|
+
const bodyMarkup = isCollapsed
|
|
700
|
+
? ''
|
|
701
|
+
: isLoading
|
|
702
|
+
? '<div class="diff-empty-state">Loading file diff...</div>'
|
|
703
|
+
: errorMessage
|
|
704
|
+
? `<div class="diff-empty-state">${escapeHtml(errorMessage)}</div>`
|
|
705
|
+
: this.renderDiffDetail(detail, index, { includeHeader: false });
|
|
709
706
|
|
|
710
707
|
return `
|
|
711
708
|
<section
|
|
@@ -11,10 +11,15 @@ html {
|
|
|
11
11
|
scroll-behavior: smooth;
|
|
12
12
|
hanging-punctuation: first last;
|
|
13
13
|
scroll-padding-top: var(--space-16);
|
|
14
|
+
height: 100%;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
overscroll-behavior: none;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
body {
|
|
20
|
+
height: 100%;
|
|
17
21
|
min-height: 100dvh;
|
|
22
|
+
overflow: hidden;
|
|
18
23
|
line-height: 1.6;
|
|
19
24
|
font-family: var(--font-body, sans-serif);
|
|
20
25
|
font-size: var(--text-sm);
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
margin-top: 0;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
.preview-content .frontmatter-block + h1 {
|
|
17
|
+
margin-top: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
.preview-content h2 {
|
|
17
21
|
margin: var(--space-6) 0 var(--space-3);
|
|
18
22
|
font-size: var(--text-lg);
|
|
@@ -40,6 +44,128 @@
|
|
|
40
44
|
margin-bottom: var(--space-4);
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
.preview-content .frontmatter-block {
|
|
48
|
+
max-width: 72ch;
|
|
49
|
+
margin: 0 0 var(--space-5);
|
|
50
|
+
padding: var(--space-4);
|
|
51
|
+
border: 1px solid var(--color-border);
|
|
52
|
+
border-radius: var(--radius-lg);
|
|
53
|
+
background: color-mix(in oklab, var(--color-surface-offset) 72%, var(--color-surface));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.preview-content .frontmatter-label {
|
|
57
|
+
color: var(--color-text-muted);
|
|
58
|
+
font-size: var(--text-ui-sm);
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
letter-spacing: 0.04em;
|
|
61
|
+
text-transform: uppercase;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.preview-content .frontmatter-block > .frontmatter-label {
|
|
65
|
+
display: block;
|
|
66
|
+
margin-bottom: var(--space-3);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.preview-content .frontmatter-header {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: space-between;
|
|
73
|
+
gap: var(--space-3);
|
|
74
|
+
margin-bottom: var(--space-3);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.preview-content .frontmatter-toggle {
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
min-height: 30px;
|
|
80
|
+
padding: 0 var(--space-3);
|
|
81
|
+
border: 1px solid var(--color-border);
|
|
82
|
+
border-radius: 999px;
|
|
83
|
+
background: var(--color-surface);
|
|
84
|
+
color: var(--color-text);
|
|
85
|
+
font: inherit;
|
|
86
|
+
font-size: var(--text-ui-sm);
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.preview-content .frontmatter-toggle:hover {
|
|
91
|
+
background: var(--color-surface-offset);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.preview-content .frontmatter-toggle:focus-visible {
|
|
95
|
+
outline: 2px solid var(--color-primary);
|
|
96
|
+
outline-offset: 2px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.preview-content .frontmatter-summary {
|
|
100
|
+
margin-bottom: var(--space-1);
|
|
101
|
+
color: var(--color-text-muted);
|
|
102
|
+
font-size: var(--text-ui-sm);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.preview-content .frontmatter-properties {
|
|
106
|
+
display: grid;
|
|
107
|
+
gap: var(--space-3);
|
|
108
|
+
margin: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.preview-content .frontmatter-property {
|
|
112
|
+
display: grid;
|
|
113
|
+
grid-template-columns: minmax(0, 160px) minmax(0, 1fr);
|
|
114
|
+
gap: var(--space-3);
|
|
115
|
+
align-items: start;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.preview-content .frontmatter-key {
|
|
119
|
+
margin: 0;
|
|
120
|
+
color: var(--color-text-muted);
|
|
121
|
+
font-size: var(--text-ui-sm);
|
|
122
|
+
font-weight: 600;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.preview-content .frontmatter-value {
|
|
126
|
+
min-width: 0;
|
|
127
|
+
margin: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.preview-content .frontmatter-value-text {
|
|
131
|
+
display: inline-block;
|
|
132
|
+
color: var(--color-text);
|
|
133
|
+
overflow-wrap: anywhere;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.preview-content .frontmatter-value-list {
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-wrap: wrap;
|
|
139
|
+
gap: var(--space-2);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.preview-content .frontmatter-value-pill {
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
min-height: 28px;
|
|
146
|
+
padding: 0 var(--space-3);
|
|
147
|
+
border: 1px solid var(--color-border);
|
|
148
|
+
border-radius: 999px;
|
|
149
|
+
background: var(--color-surface);
|
|
150
|
+
color: var(--color-text);
|
|
151
|
+
font-size: var(--text-ui-sm);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.preview-content .frontmatter-value-code {
|
|
155
|
+
margin: 0;
|
|
156
|
+
border-radius: var(--radius-md);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.preview-content .frontmatter-value-code code {
|
|
160
|
+
white-space: pre-wrap;
|
|
161
|
+
word-break: break-word;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.preview-content .frontmatter-empty {
|
|
165
|
+
color: var(--color-text-muted);
|
|
166
|
+
font-size: var(--text-ui-sm);
|
|
167
|
+
}
|
|
168
|
+
|
|
43
169
|
.preview-content p:has(> .excalidraw-embed-placeholder:only-child),
|
|
44
170
|
.preview-content p:has(> .video-embed-placeholder:only-child),
|
|
45
171
|
.preview-content p:has(> .plantuml-shell:only-child) {
|
|
@@ -202,3 +328,15 @@
|
|
|
202
328
|
border-radius: var(--radius-md);
|
|
203
329
|
cursor: zoom-in;
|
|
204
330
|
}
|
|
331
|
+
|
|
332
|
+
@media (max-width: 768px) {
|
|
333
|
+
.preview-content .frontmatter-header {
|
|
334
|
+
align-items: flex-start;
|
|
335
|
+
flex-direction: column;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.preview-content .frontmatter-property {
|
|
339
|
+
grid-template-columns: 1fr;
|
|
340
|
+
gap: var(--space-1);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -316,6 +316,12 @@
|
|
|
316
316
|
border-left: 1px solid var(--color-divider);
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
.markdown-toolbar .markdown-toolbar-group--mobile-only,
|
|
320
|
+
.pane-header-actions .mobile-editor-find-btn,
|
|
321
|
+
.toolbar-overflow-menu .toolbar-search-files-btn {
|
|
322
|
+
display: none !important;
|
|
323
|
+
}
|
|
324
|
+
|
|
319
325
|
.markdown-toolbar-block-trigger {
|
|
320
326
|
display: inline-flex;
|
|
321
327
|
align-items: center;
|
|
@@ -130,6 +130,10 @@
|
|
|
130
130
|
width: 100%;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
.toolbar-overflow-menu .toolbar-search-files-btn {
|
|
134
|
+
display: inline-flex !important;
|
|
135
|
+
}
|
|
136
|
+
|
|
133
137
|
.toolbar-overflow-menu .ui-toolbar-button-label {
|
|
134
138
|
display: inline;
|
|
135
139
|
min-width: 0;
|
|
@@ -204,6 +208,53 @@
|
|
|
204
208
|
flex: 1;
|
|
205
209
|
}
|
|
206
210
|
|
|
211
|
+
.editor-pane {
|
|
212
|
+
position: relative;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.editor-pane > .pane-header {
|
|
216
|
+
position: sticky;
|
|
217
|
+
top: 0;
|
|
218
|
+
z-index: 8;
|
|
219
|
+
gap: var(--space-2);
|
|
220
|
+
padding-inline: var(--space-2);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.editor-pane > .pane-header .pane-header-meta {
|
|
224
|
+
flex: 1 1 auto;
|
|
225
|
+
gap: var(--space-2);
|
|
226
|
+
min-width: 0;
|
|
227
|
+
overflow: hidden;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.editor-pane > .pane-header .pane-label {
|
|
231
|
+
flex-shrink: 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.editor-pane > .pane-header .pane-label-meta {
|
|
235
|
+
min-width: 0;
|
|
236
|
+
overflow: hidden;
|
|
237
|
+
text-overflow: ellipsis;
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.editor-pane > .pane-header .pane-header-actions {
|
|
242
|
+
flex-shrink: 0;
|
|
243
|
+
gap: 0;
|
|
244
|
+
min-width: 0;
|
|
245
|
+
margin-left: auto;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.editor-pane > .pane-header .ui-button--compact {
|
|
249
|
+
gap: 4px;
|
|
250
|
+
padding-inline: 6px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.editor-pane > .pane-header .ui-action-label,
|
|
254
|
+
.editor-pane > .pane-header #wrapToggleLabel {
|
|
255
|
+
font-size: 11px;
|
|
256
|
+
}
|
|
257
|
+
|
|
207
258
|
.preview-content {
|
|
208
259
|
padding: var(--space-4);
|
|
209
260
|
}
|
|
@@ -283,15 +334,29 @@
|
|
|
283
334
|
}
|
|
284
335
|
|
|
285
336
|
.markdown-toolbar {
|
|
337
|
+
position: sticky;
|
|
338
|
+
top: 28px;
|
|
339
|
+
z-index: 7;
|
|
286
340
|
overflow-x: auto;
|
|
287
341
|
flex-wrap: nowrap;
|
|
288
342
|
scrollbar-width: thin;
|
|
289
343
|
}
|
|
290
344
|
|
|
345
|
+
.markdown-toolbar .markdown-toolbar-group--mobile-only {
|
|
346
|
+
display: inline-flex !important;
|
|
347
|
+
margin-left: 0;
|
|
348
|
+
padding-left: 0;
|
|
349
|
+
border-left: none;
|
|
350
|
+
}
|
|
351
|
+
|
|
291
352
|
.markdown-toolbar-popover {
|
|
292
353
|
max-width: min(240px, calc(100vw - 24px));
|
|
293
354
|
}
|
|
294
355
|
|
|
356
|
+
.pane-header-actions .mobile-editor-find-btn {
|
|
357
|
+
display: inline-flex !important;
|
|
358
|
+
}
|
|
359
|
+
|
|
295
360
|
.outline-panel {
|
|
296
361
|
position: absolute;
|
|
297
362
|
inset: 0;
|
|
@@ -403,6 +468,30 @@
|
|
|
403
468
|
}
|
|
404
469
|
}
|
|
405
470
|
|
|
471
|
+
@media (max-width: 480px) {
|
|
472
|
+
.editor-pane > .pane-header {
|
|
473
|
+
gap: var(--space-1);
|
|
474
|
+
padding-inline: var(--space-1);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.editor-pane > .pane-header .pane-header-meta {
|
|
478
|
+
gap: var(--space-1);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.editor-pane > .pane-header .pane-label-meta {
|
|
482
|
+
font-size: 11px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.editor-pane > .pane-header .ui-button--compact {
|
|
486
|
+
padding-inline: 4px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.editor-pane > .pane-header .ui-action-label,
|
|
490
|
+
.editor-pane > .pane-header #wrapToggleLabel {
|
|
491
|
+
font-size: 10px;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
406
495
|
@media (min-width: 769px) {
|
|
407
496
|
.mobile-view-toggle {
|
|
408
497
|
display: none !important;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/* ===== APP SHELL ===== */
|
|
2
2
|
|
|
3
3
|
.app-shell {
|
|
4
|
+
position: fixed;
|
|
5
|
+
top: var(--app-viewport-offset-top, 0px);
|
|
6
|
+
right: 0;
|
|
7
|
+
left: 0;
|
|
4
8
|
display: flex;
|
|
9
|
+
width: 100%;
|
|
5
10
|
height: 100dvh;
|
|
11
|
+
height: var(--app-viewport-height, 100dvh);
|
|
6
12
|
overflow: hidden;
|
|
7
13
|
}
|
|
8
14
|
|
|
@@ -51,3 +51,77 @@
|
|
|
51
51
|
content: '📄';
|
|
52
52
|
font-size: var(--text-ui-sm);
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
.cm-panels-top {
|
|
56
|
+
border-bottom: 1px solid var(--color-divider);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.cm-panel.cm-search {
|
|
60
|
+
gap: var(--space-2);
|
|
61
|
+
align-items: center;
|
|
62
|
+
padding: var(--space-2);
|
|
63
|
+
background: oklch(from var(--color-surface) l c h / 0.98);
|
|
64
|
+
color: var(--color-text);
|
|
65
|
+
box-shadow: inset 0 -1px 0 var(--color-divider);
|
|
66
|
+
font-family: var(--font-body);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.cm-panel.cm-search label {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 6px;
|
|
73
|
+
color: var(--color-text-muted);
|
|
74
|
+
font-size: var(--text-xs);
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.cm-panel.cm-search .cm-textfield {
|
|
79
|
+
min-height: 32px;
|
|
80
|
+
padding: 0 var(--space-2);
|
|
81
|
+
border: 1px solid var(--color-border);
|
|
82
|
+
border-radius: var(--radius-sm);
|
|
83
|
+
background: var(--color-surface-offset);
|
|
84
|
+
color: var(--color-text);
|
|
85
|
+
font-family: var(--font-body);
|
|
86
|
+
font-size: var(--text-sm);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.cm-panel.cm-search .cm-button {
|
|
90
|
+
min-height: 32px;
|
|
91
|
+
padding: 0 10px;
|
|
92
|
+
border: 1px solid var(--color-border);
|
|
93
|
+
border-radius: var(--radius-sm);
|
|
94
|
+
background: var(--color-surface-offset);
|
|
95
|
+
color: var(--color-text);
|
|
96
|
+
font-family: var(--font-body);
|
|
97
|
+
font-size: var(--text-xs);
|
|
98
|
+
font-weight: 600;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.cm-panel.cm-search .cm-button:hover:not(:disabled),
|
|
102
|
+
.cm-panel.cm-search .cm-button:focus-visible {
|
|
103
|
+
background: var(--color-surface-dynamic);
|
|
104
|
+
outline: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@media (max-width: 768px) {
|
|
108
|
+
.cm-panel.cm-search {
|
|
109
|
+
flex-wrap: wrap;
|
|
110
|
+
align-items: stretch;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.cm-panel.cm-search label {
|
|
114
|
+
flex: 1 1 100%;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.cm-panel.cm-search .cm-textfield {
|
|
118
|
+
width: 100%;
|
|
119
|
+
min-height: 40px;
|
|
120
|
+
font-size: 16px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.cm-panel.cm-search .cm-button {
|
|
124
|
+
min-height: 40px;
|
|
125
|
+
padding: 0 12px;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -168,8 +168,7 @@ function readAuthenticatedOidcSession(sessionCookieManager, req) {
|
|
|
168
168
|
return null;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
if (Number.isFinite(expiresAt) && expiresAt <= Date.now()) {
|
|
171
|
+
if (hasExpiredSession(session)) {
|
|
173
172
|
return null;
|
|
174
173
|
}
|
|
175
174
|
|
|
@@ -185,6 +184,29 @@ function readAuthenticatedOidcSession(sessionCookieManager, req) {
|
|
|
185
184
|
};
|
|
186
185
|
}
|
|
187
186
|
|
|
187
|
+
function hasExpiredSession(session) {
|
|
188
|
+
const expiresAt = Number(session?.expiresAt);
|
|
189
|
+
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function resolveSessionExpiresAt(sessionMaxAgeMs, fallbackExpiresAt = null) {
|
|
193
|
+
if (Number.isFinite(sessionMaxAgeMs) && sessionMaxAgeMs > 0) {
|
|
194
|
+
return Date.now() + sessionMaxAgeMs;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return Number.isFinite(fallbackExpiresAt) ? fallbackExpiresAt : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function createSessionCookieOptions(expiresAt) {
|
|
201
|
+
if (!Number.isFinite(expiresAt)) {
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
expires: new Date(expiresAt),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
188
210
|
function buildClientConfig(authConfig, { basePath = '' } = {}) {
|
|
189
211
|
const loginEndpoint = prependBasePath(basePath, '/api/auth/oidc/login');
|
|
190
212
|
const sessionEndpoint = prependBasePath(basePath, '/api/auth/session');
|
|
@@ -232,7 +254,7 @@ function createPasswordStrategy(authConfig, sessionCookieManager, clientConfig)
|
|
|
232
254
|
|
|
233
255
|
function hasValidSession(req) {
|
|
234
256
|
const session = sessionCookieManager.readSession(req);
|
|
235
|
-
return session?.strategy === AUTH_STRATEGY_PASSWORD;
|
|
257
|
+
return session?.strategy === AUTH_STRATEGY_PASSWORD && !hasExpiredSession(session);
|
|
236
258
|
}
|
|
237
259
|
|
|
238
260
|
return {
|
|
@@ -260,6 +282,8 @@ function createPasswordStrategy(authConfig, sessionCookieManager, clientConfig)
|
|
|
260
282
|
}, { kind: 'invalid_credentials' });
|
|
261
283
|
}
|
|
262
284
|
|
|
285
|
+
const expiresAt = resolveSessionExpiresAt(authConfig.sessionMaxAgeMs);
|
|
286
|
+
|
|
263
287
|
return createResponse(200, {
|
|
264
288
|
auth: clientConfig,
|
|
265
289
|
authenticated: true,
|
|
@@ -269,8 +293,9 @@ function createPasswordStrategy(authConfig, sessionCookieManager, clientConfig)
|
|
|
269
293
|
kind: 'session_created',
|
|
270
294
|
setCookie: sessionCookieManager.createSessionCookie(req, {
|
|
271
295
|
authenticatedAt: Date.now(),
|
|
296
|
+
...(Number.isFinite(expiresAt) ? { expiresAt } : {}),
|
|
272
297
|
strategy: AUTH_STRATEGY_PASSWORD,
|
|
273
|
-
}),
|
|
298
|
+
}, createSessionCookieOptions(expiresAt)),
|
|
274
299
|
});
|
|
275
300
|
},
|
|
276
301
|
|
|
@@ -456,9 +481,10 @@ function createOidcStrategy(authConfig, sessionCookieManager, flowCookieManager,
|
|
|
456
481
|
return createErrorRedirect(req, accessDecision.error, flowPayload);
|
|
457
482
|
}
|
|
458
483
|
|
|
459
|
-
const
|
|
484
|
+
const tokenExpiresAt = Number.isFinite(claims.exp)
|
|
460
485
|
? claims.exp * 1000
|
|
461
486
|
: Date.now() + (Math.max(Number(tokens.expires_in) || 3600, 1) * 1000);
|
|
487
|
+
const expiresAt = resolveSessionExpiresAt(authConfig.sessionMaxAgeMs, tokenExpiresAt);
|
|
462
488
|
const returnTo = sanitizeReturnTo(flowPayload.returnTo, {
|
|
463
489
|
basePath,
|
|
464
490
|
publicBaseUrl: authConfig.oidc.publicBaseUrl,
|
|
@@ -480,7 +506,7 @@ function createOidcStrategy(authConfig, sessionCookieManager, flowCookieManager,
|
|
|
480
506
|
expiresAt,
|
|
481
507
|
strategy: AUTH_STRATEGY_OIDC,
|
|
482
508
|
user,
|
|
483
|
-
}),
|
|
509
|
+
}, createSessionCookieOptions(expiresAt)),
|
|
484
510
|
flowCookieManager.clear(req),
|
|
485
511
|
],
|
|
486
512
|
});
|
package/src/server/config/env.js
CHANGED
|
@@ -10,12 +10,34 @@ import {
|
|
|
10
10
|
createRandomSessionSecret,
|
|
11
11
|
} from '../auth/create-auth-service.js';
|
|
12
12
|
import { loadBuildInfo } from './build-info.js';
|
|
13
|
+
import { isPerfLoggingEnabled } from './perf-logging.js';
|
|
13
14
|
|
|
14
15
|
function parsePositiveInt(rawValue, fallbackValue) {
|
|
15
16
|
const parsed = Number.parseInt(rawValue ?? '', 10);
|
|
16
17
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallbackValue;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function parseOptionalPositiveInt(rawValue, {
|
|
21
|
+
fallbackValue = null,
|
|
22
|
+
variableName = 'value',
|
|
23
|
+
} = {}) {
|
|
24
|
+
const normalized = String(rawValue ?? '').trim();
|
|
25
|
+
if (!normalized) {
|
|
26
|
+
return fallbackValue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!/^[1-9]\d*$/u.test(normalized)) {
|
|
30
|
+
throw new Error(`${variableName} must be a positive integer.`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parsed = Number(normalized);
|
|
34
|
+
if (Number.isSafeInteger(parsed) && parsed > 0) {
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
throw new Error(`${variableName} must be a positive integer.`);
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
function parsePort(rawPort, fallbackPort) {
|
|
20
42
|
return parsePositiveInt(rawPort, fallbackPort);
|
|
21
43
|
}
|
|
@@ -284,6 +306,10 @@ export function loadConfig(overrides = {}) {
|
|
|
284
306
|
password,
|
|
285
307
|
passwordWasGenerated,
|
|
286
308
|
sessionCookieName: authOverrides.sessionCookieName || process.env.AUTH_SESSION_COOKIE_NAME || 'collabmd_auth',
|
|
309
|
+
sessionMaxAgeMs: parseOptionalPositiveInt(
|
|
310
|
+
authOverrides.sessionMaxAgeMs ?? process.env.AUTH_SESSION_MAX_AGE_MS,
|
|
311
|
+
{ variableName: 'AUTH_SESSION_MAX_AGE_MS' },
|
|
312
|
+
),
|
|
287
313
|
sessionSecret: authOverrides.sessionSecret || process.env.AUTH_SESSION_SECRET || createRandomSessionSecret(),
|
|
288
314
|
strategy: authStrategy,
|
|
289
315
|
},
|
|
@@ -296,6 +322,7 @@ export function loadConfig(overrides = {}) {
|
|
|
296
322
|
httpRequestTimeoutMs: parsePositiveInt(process.env.HTTP_REQUEST_TIMEOUT_MS, 30_000),
|
|
297
323
|
git,
|
|
298
324
|
gitEnabled: git.enabled,
|
|
325
|
+
perfLoggingEnabled: overrides.perfLoggingEnabled ?? isPerfLoggingEnabled(process.env.COLLABMD_PERF_LOGGING),
|
|
299
326
|
port: parsePort(process.env.PORT, 1234),
|
|
300
327
|
nodeEnv,
|
|
301
328
|
plantumlServerUrl: process.env.PLANTUML_SERVER_URL || 'https://www.plantuml.com/plantuml',
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function formatPerfValue(value) {
|
|
2
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
3
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/\.?0+$/u, '');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (typeof value === 'string') {
|
|
7
|
+
return /[\s=]/u.test(value) ? JSON.stringify(value) : value;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (typeof value === 'boolean') {
|
|
11
|
+
return value ? 'true' : 'false';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return JSON.stringify(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isPerfLoggingEnabled(value = '') {
|
|
18
|
+
if (typeof value === 'boolean') {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
23
|
+
return normalized === '1'
|
|
24
|
+
|| normalized === 'true'
|
|
25
|
+
|| normalized === 'yes'
|
|
26
|
+
|| normalized === 'on';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function logPerfEvent(enabled, scope, fields = {}) {
|
|
30
|
+
if (!enabled) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const formattedFields = Object.entries(fields)
|
|
35
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
36
|
+
.map(([key, value]) => `${key}=${formatPerfValue(value)}`);
|
|
37
|
+
const suffix = formattedFields.length > 0 ? ` ${formattedFields.join(' ')}` : '';
|
|
38
|
+
console.info(`[perf][${scope}]${suffix}`);
|
|
39
|
+
}
|