claude-code-kanban 6.1.2 → 7.0.1

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/public/index.html CHANGED
@@ -26,6 +26,7 @@
26
26
  <script defer src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/highlight.min.js" integrity="sha384-wjfDDhOPPdjtva8vWBhWeVprSpmxisEu5aYT3q1JyACqXpdKpo3PWZTMVq24MBix" crossorigin="anonymous"></script>
27
27
  <script>window.__STORAGE_NS__ = null;</script>
28
28
  <script defer src="/project-match.js"></script>
29
+ <script defer src="/tree-highlight.js"></script>
29
30
  <script defer src="/link-url.js"></script>
30
31
  <script defer src="/app.js"></script>
31
32
  </head>
@@ -285,6 +286,22 @@
285
286
  </div>
286
287
  </header>
287
288
 
289
+ <div id="pane-strip" class="pane-strip" role="tablist" aria-label="Session panes">
290
+ <div id="pane-pin" class="pane-pin"></div>
291
+ <div id="pane-tabs" class="pane-tabs"></div>
292
+ <button id="pane-add" class="icon-btn pane-add" title="Add pane (paste a URL or a file path, or a linked doc)" aria-label="Add pane">
293
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
294
+ </button>
295
+ <div class="pane-tools">
296
+ <button id="pane-reload" class="icon-btn pane-tool" title="Reload pane" aria-label="Reload pane">
297
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><polyline points="21 3 21 8 16 8"/></svg>
298
+ </button>
299
+ <button id="pane-open" class="icon-btn pane-tool" title="Open in new tab" aria-label="Open pane in new tab">
300
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
301
+ </button>
302
+ </div>
303
+ </div>
304
+
288
305
  <div class="kanban" id="main-content">
289
306
  <div id="owner-filter-bar" class="owner-filter-bar">
290
307
  <select id="owner-filter" class="filter-dropdown" onchange="filterByOwner(this.value)" aria-label="Filter by team member">
@@ -336,6 +353,7 @@
336
353
  <div id="terminal-host" class="terminal-host"></div>
337
354
  <textarea id="terminal-key-proxy" class="terminal-key-proxy" tabindex="-1" aria-hidden="true"></textarea>
338
355
  </div>
356
+ <div id="pane-views" class="pane-views"></div>
339
357
  <div id="agent-footer" class="agent-footer">
340
358
  <div class="agent-footer-header" onclick="toggleAgentFooter()">
341
359
  <span id="agent-footer-label">Agents</span>
@@ -435,6 +453,9 @@
435
453
  <div class="modal-header">
436
454
  <h3 class="modal-title" id="preview-modal-title">Preview</h3>
437
455
  <div style="display:flex;gap:4px;align-items:center;">
456
+ <button id="preview-pane-btn" class="icon-btn" aria-label="Open in a pane" title="Open in a pane" onclick="openPreviewInPane()">
457
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v5"/></svg>
458
+ </button>
438
459
  <button id="preview-link-btn" class="icon-btn" aria-label="Link to current session" title="Link to current session" onclick="togglePreviewSessionLink()">
439
460
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
440
461
  </button>
@@ -825,6 +846,22 @@
825
846
  </div>
826
847
  </div>
827
848
 
849
+ <div id="pane-pop" class="modal-overlay" onclick="closePanePop()">
850
+ <div class="modal pane-pop-dialog" role="dialog" aria-labelledby="pane-pop-title" onclick="event.stopPropagation()">
851
+ <div class="modal-header">
852
+ <h3 id="pane-pop-title" class="modal-title">Add a pane</h3>
853
+ <button class="modal-close" aria-label="Close dialog" onclick="closePanePop()">
854
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
855
+ </button>
856
+ </div>
857
+ <input id="pane-pop-input" class="form-input" placeholder="Search linked docs, or paste a URL or a file path" autocomplete="off" spellcheck="false">
858
+ <div id="pane-pop-detect" class="pane-pop-detect"></div>
859
+ <div class="pane-pop-label">Linked docs</div>
860
+ <div id="pane-pop-docs" class="pane-pop-docs"></div>
861
+ <div class="sp-hint"><kbd>Enter</kbd> add &middot; <kbd>&uarr;</kbd><kbd>&darr;</kbd> select &middot; <kbd>Esc</kbd> close</div>
862
+ </div>
863
+ </div>
864
+
828
865
  <div id="session-picker-modal" class="modal-overlay top-aligned" onclick="closeSessionPicker()">
829
866
  <div class="modal session-picker" onclick="event.stopPropagation()">
830
867
  <input type="text" id="session-picker-input" class="form-input sp-input" placeholder="Jump to session..." autocomplete="off" spellcheck="false">
@@ -1,6 +1,9 @@
1
1
  // Loaded by the page as a plain script and by lib/linked-docs.js through require, so the
2
- // sidebar, the API and the CLI accept the same URLs. Only http(s): linked docs render as
3
- // links, so a `javascript:` or `data:` value sent through the API must not become one.
2
+ // sidebar, the API and the CLI accept the same URLs and keep the same number of docs.
3
+ const MAX_LINKED_DOCS = 200;
4
+
5
+ // Only http(s): linked docs render as links, so a `javascript:` or `data:` value sent
6
+ // through the API must not become one.
4
7
  function linkUrl(value) {
5
8
  if (typeof value !== 'string') return null;
6
9
  const v = value.trim();
@@ -12,4 +15,4 @@ function linkUrl(value) {
12
15
  }
13
16
  }
14
17
 
15
- if (typeof module === 'object' && module.exports) module.exports = { linkUrl };
18
+ if (typeof module === 'object' && module.exports) module.exports = { linkUrl, MAX_LINKED_DOCS };
package/public/style.css CHANGED
@@ -2444,6 +2444,14 @@ body.booting .boot-splash {
2444
2444
  overflow-x: auto;
2445
2445
  }
2446
2446
 
2447
+ code.language-tree .hljs-punctuation {
2448
+ color: var(--text-muted);
2449
+ }
2450
+
2451
+ code.language-tree .hljs-title {
2452
+ font-weight: 600;
2453
+ }
2454
+
2447
2455
  .detail-desc code {
2448
2456
  background: color-mix(in srgb, var(--text-muted) 12%, transparent);
2449
2457
  border: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
@@ -3412,6 +3420,419 @@ body.booting .boot-splash {
3412
3420
 
3413
3421
  /* #endregion */
3414
3422
 
3423
+ /* #region PANES */
3424
+ .pane-strip {
3425
+ display: none;
3426
+ align-items: stretch;
3427
+ gap: 2px;
3428
+ padding: 4px 10px 0;
3429
+ background: var(--bg-surface);
3430
+ border-bottom: 1px solid var(--border);
3431
+ }
3432
+
3433
+ .pane-pin {
3434
+ display: flex;
3435
+ flex: none;
3436
+ margin-bottom: -1px;
3437
+ }
3438
+
3439
+ .session-view.has-panes .pane-strip {
3440
+ display: flex;
3441
+ }
3442
+
3443
+ .session-view.terminal-mode .pane-strip {
3444
+ display: none;
3445
+ }
3446
+
3447
+ .session-view.pane-mode .kanban,
3448
+ .session-view.pane-mode .agent-footer {
3449
+ display: none;
3450
+ }
3451
+
3452
+ .pane-tabs {
3453
+ display: flex;
3454
+ gap: 2px;
3455
+ min-width: 0;
3456
+ margin-bottom: -1px;
3457
+ overflow-x: auto;
3458
+ overflow-y: hidden;
3459
+ scrollbar-width: none;
3460
+ overscroll-behavior-x: contain;
3461
+ }
3462
+
3463
+ .pane-tabs::-webkit-scrollbar {
3464
+ display: none;
3465
+ }
3466
+
3467
+ .pane-tabs.fade-l,
3468
+ .pane-tabs.fade-r {
3469
+ mask-image: linear-gradient(
3470
+ to right,
3471
+ transparent,
3472
+ #000 var(--fade-l, 0),
3473
+ #000 calc(100% - var(--fade-r, 0)),
3474
+ transparent
3475
+ );
3476
+ }
3477
+
3478
+ .pane-tabs.fade-l {
3479
+ --fade-l: 28px;
3480
+ }
3481
+
3482
+ .pane-tabs.fade-r {
3483
+ --fade-r: 28px;
3484
+ }
3485
+
3486
+ .pane-tab {
3487
+ display: flex;
3488
+ flex: 0 1 auto;
3489
+ align-items: center;
3490
+ gap: 6px;
3491
+ min-width: 112px;
3492
+ max-width: 190px;
3493
+ padding: 4px 5px 4px 9px;
3494
+ border: 1px solid transparent;
3495
+ border-bottom: 0;
3496
+ border-radius: 6px 6px 0 0;
3497
+ background: none;
3498
+ color: var(--text-tertiary);
3499
+ font-family: var(--font-mono);
3500
+ font-size: 11.5px;
3501
+ white-space: nowrap;
3502
+ cursor: pointer;
3503
+ }
3504
+
3505
+ .pane-tab:hover {
3506
+ background: var(--bg-hover);
3507
+ }
3508
+
3509
+ .pane-tab.on {
3510
+ background: var(--bg-deep);
3511
+ border-color: var(--border);
3512
+ color: var(--text-primary);
3513
+ }
3514
+
3515
+ .pane-tab.pinned {
3516
+ flex: none;
3517
+ min-width: 0;
3518
+ padding-right: 10px;
3519
+ font-weight: 500;
3520
+ }
3521
+
3522
+ .pane-tab .pane-title {
3523
+ flex: 1;
3524
+ min-width: 0;
3525
+ overflow: hidden;
3526
+ text-overflow: ellipsis;
3527
+ }
3528
+
3529
+ .pane-tab .pin-meta {
3530
+ color: var(--text-muted);
3531
+ font-size: 10px;
3532
+ font-weight: 400;
3533
+ }
3534
+
3535
+ .pane-tab .kind {
3536
+ flex: none;
3537
+ width: 13px;
3538
+ height: 13px;
3539
+ }
3540
+
3541
+ .pane-tab .state {
3542
+ flex: none;
3543
+ width: 6px;
3544
+ height: 6px;
3545
+ border: 1px solid var(--text-muted);
3546
+ border-radius: 50%;
3547
+ }
3548
+
3549
+ .pane-tab .state.loaded {
3550
+ background: var(--success);
3551
+ border-color: var(--success);
3552
+ }
3553
+
3554
+ .pane-tab .state.blocked {
3555
+ background: var(--warning);
3556
+ border-color: var(--warning);
3557
+ }
3558
+
3559
+ .pane-tab .pane-x {
3560
+ flex: none;
3561
+ width: 16px;
3562
+ height: 16px;
3563
+ padding: 0;
3564
+ border: 0;
3565
+ border-radius: 4px;
3566
+ background: none;
3567
+ color: var(--text-muted);
3568
+ font-size: 13px;
3569
+ line-height: 1;
3570
+ cursor: pointer;
3571
+ }
3572
+
3573
+ .pane-tab:not(.on, :hover, :focus-within) .pane-x {
3574
+ visibility: hidden;
3575
+ }
3576
+
3577
+ .pane-tab .pane-x:hover {
3578
+ background: var(--bg-hover);
3579
+ color: var(--text-primary);
3580
+ }
3581
+
3582
+ .pane-sep {
3583
+ flex: none;
3584
+ width: 1px;
3585
+ margin: 5px 6px 7px;
3586
+ background: var(--border);
3587
+ }
3588
+
3589
+ .pane-add,
3590
+ .pane-tool {
3591
+ width: 26px;
3592
+ height: 26px;
3593
+ border-color: transparent;
3594
+ }
3595
+
3596
+ .pane-add svg,
3597
+ .pane-tool svg {
3598
+ width: 14px;
3599
+ height: 14px;
3600
+ }
3601
+
3602
+ .pane-add {
3603
+ align-self: center;
3604
+ margin: 0 0 3px 4px;
3605
+ }
3606
+
3607
+ .pane-tools {
3608
+ display: flex;
3609
+ align-items: center;
3610
+ gap: 2px;
3611
+ margin: 0 0 3px auto;
3612
+ }
3613
+
3614
+ .pane-tool:disabled {
3615
+ opacity: 0.35;
3616
+ cursor: default;
3617
+ background: none;
3618
+ }
3619
+
3620
+ .pane-views {
3621
+ position: relative;
3622
+ display: none;
3623
+ flex: 1;
3624
+ min-height: 0;
3625
+ background: var(--bg-deep);
3626
+ }
3627
+
3628
+ .session-view.pane-mode:not(.terminal-mode) .pane-views {
3629
+ display: flex;
3630
+ }
3631
+
3632
+ .pane-view {
3633
+ position: absolute;
3634
+ inset: 0;
3635
+ display: none;
3636
+ flex-direction: column;
3637
+ }
3638
+
3639
+ .pane-view.on {
3640
+ display: flex;
3641
+ }
3642
+
3643
+ .pane-view:has(> .review-panel) {
3644
+ flex-direction: row;
3645
+ }
3646
+
3647
+ .pane-view:has(> .pane-doc) {
3648
+ gap: 16px;
3649
+ padding: 16px;
3650
+ }
3651
+
3652
+ .pane-view:has(> .review-panel) > :not(.review-panel) {
3653
+ width: auto;
3654
+ min-width: 0;
3655
+ }
3656
+
3657
+ .pane-view > .review-panel {
3658
+ padding: 12px;
3659
+ }
3660
+
3661
+ .pane-frame {
3662
+ flex: 1;
3663
+ width: 100%;
3664
+ min-height: 0;
3665
+ border: 0;
3666
+ background: #fff;
3667
+ }
3668
+
3669
+ .pane-doc {
3670
+ flex: 1;
3671
+ min-height: 0;
3672
+ position: relative;
3673
+ display: flex;
3674
+ width: 100%;
3675
+ max-width: var(--pane-doc-w, 860px);
3676
+ margin: 0 auto;
3677
+ background: var(--bg-surface);
3678
+ border: 1px solid var(--border);
3679
+ border-radius: 12px;
3680
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
3681
+ }
3682
+
3683
+ .pane-doc-body {
3684
+ flex: 1;
3685
+ min-width: 0;
3686
+ overflow-y: auto;
3687
+ padding: 24px;
3688
+ font-size: 13px;
3689
+ line-height: 1.6;
3690
+ word-break: break-word;
3691
+ }
3692
+
3693
+ /* The modal's corner grip; the pane fills the height, so it sets the width only. */
3694
+ .pane-doc > .modal-resize-handle {
3695
+ cursor: ew-resize;
3696
+ }
3697
+
3698
+ .pane-card {
3699
+ display: grid;
3700
+ flex: 1;
3701
+ place-items: center;
3702
+ font-family: var(--font-mono);
3703
+ }
3704
+
3705
+ .pane-card-box {
3706
+ max-width: 460px;
3707
+ padding: 26px 28px;
3708
+ border: 1px solid var(--border);
3709
+ border-radius: 10px;
3710
+ background: var(--bg-elevated);
3711
+ text-align: center;
3712
+ }
3713
+
3714
+ .pane-card-box svg {
3715
+ width: 30px;
3716
+ height: 30px;
3717
+ color: var(--warning);
3718
+ }
3719
+
3720
+ .pane-card-box h3 {
3721
+ margin: 10px 0 6px;
3722
+ font-family: var(--font-serif);
3723
+ font-size: 17px;
3724
+ }
3725
+
3726
+ .pane-card-box p {
3727
+ margin: 0 0 16px;
3728
+ color: var(--text-tertiary);
3729
+ font-size: 12px;
3730
+ overflow-wrap: anywhere;
3731
+ }
3732
+
3733
+ .pane-card .btn {
3734
+ padding: 6px 12px;
3735
+ font-size: 12px;
3736
+ }
3737
+
3738
+ .modal.pane-pop-dialog {
3739
+ display: flex;
3740
+ flex-direction: column;
3741
+ width: 520px;
3742
+ max-width: calc(100vw - 32px);
3743
+ padding: 18px 20px 14px;
3744
+ font-family: var(--font-mono);
3745
+ }
3746
+
3747
+ .pane-pop-dialog .modal-header {
3748
+ margin-bottom: 12px;
3749
+ }
3750
+
3751
+ .pane-pop-dialog .modal-title {
3752
+ margin: 0;
3753
+ font-family: var(--font-serif);
3754
+ font-weight: 500;
3755
+ }
3756
+
3757
+ .pane-pop-dialog .form-input {
3758
+ box-sizing: border-box;
3759
+ font: 13px var(--font-mono);
3760
+ }
3761
+
3762
+ .pane-pop-detect {
3763
+ min-height: 16px;
3764
+ margin: 7px 2px 14px;
3765
+ color: var(--text-muted);
3766
+ font-size: 11px;
3767
+ }
3768
+
3769
+ .pane-pop-detect.error {
3770
+ color: var(--warning);
3771
+ }
3772
+
3773
+ .pane-pop-dialog .sp-hint {
3774
+ margin-top: 12px;
3775
+ padding-top: 10px;
3776
+ border-top: 1px solid var(--border);
3777
+ }
3778
+
3779
+ .pane-pop-label {
3780
+ margin: 6px 2px;
3781
+ color: var(--text-muted);
3782
+ font-size: 10px;
3783
+ letter-spacing: 0.06em;
3784
+ text-transform: uppercase;
3785
+ }
3786
+
3787
+ .pane-pop-docs {
3788
+ max-height: 260px;
3789
+ margin: 0 -6px;
3790
+ overflow-y: auto;
3791
+ }
3792
+
3793
+ .pane-pop-row {
3794
+ display: flex;
3795
+ align-items: center;
3796
+ gap: 8px;
3797
+ width: 100%;
3798
+ padding: 7px 8px;
3799
+ border: 0;
3800
+ border-radius: 6px;
3801
+ outline: none;
3802
+ background: none;
3803
+ color: var(--text-secondary);
3804
+ font: 12px var(--font-mono);
3805
+ text-align: left;
3806
+ cursor: pointer;
3807
+ }
3808
+
3809
+ .pane-pop-row:hover,
3810
+ .pane-pop-row:focus-visible,
3811
+ .pane-pop-row.active {
3812
+ background: var(--bg-hover);
3813
+ color: var(--text-primary);
3814
+ }
3815
+
3816
+ .pane-pop-row span {
3817
+ overflow: hidden;
3818
+ text-overflow: ellipsis;
3819
+ white-space: nowrap;
3820
+ }
3821
+
3822
+ .pane-pop-row svg {
3823
+ flex: none;
3824
+ width: 13px;
3825
+ height: 13px;
3826
+ }
3827
+
3828
+ .pane-pop-empty {
3829
+ padding: 4px 6px;
3830
+ color: var(--text-muted);
3831
+ font-size: 11px;
3832
+ }
3833
+
3834
+ /* #endregion */
3835
+
3415
3836
  /* #region MESSAGE_PANEL */
3416
3837
  .message-panel {
3417
3838
  position: fixed;
@@ -3925,6 +4346,9 @@ body.booting .boot-splash {
3925
4346
  .rendered-md li {
3926
4347
  margin: 0.2em 0;
3927
4348
  }
4349
+ .rendered-md a {
4350
+ color: var(--accent);
4351
+ }
3928
4352
  .rendered-md pre {
3929
4353
  margin: 0.6em 0;
3930
4354
  }
@@ -4920,7 +5344,7 @@ body.booting .boot-splash {
4920
5344
  white-space: nowrap;
4921
5345
  }
4922
5346
 
4923
- .scratch-files-more {
5347
+ .list-more {
4924
5348
  margin: 6px 0 0;
4925
5349
  font-size: 11px;
4926
5350
  }
@@ -5674,16 +6098,19 @@ body.light .msg-assistant .msg-text {
5674
6098
  padding-right: 8px;
5675
6099
  }
5676
6100
 
5677
- #preview-modal-body .preview-image {
6101
+ .preview-image {
5678
6102
  display: block;
5679
6103
  max-width: 100%;
5680
- max-height: 70vh;
5681
6104
  margin: 0 auto;
5682
6105
  border: 1px solid var(--border);
5683
6106
  border-radius: 4px;
5684
6107
  background: var(--bg-hover);
5685
6108
  }
5686
6109
 
6110
+ #preview-modal-body .preview-image {
6111
+ max-height: 70vh;
6112
+ }
6113
+
5687
6114
  #preview-modal-meta {
5688
6115
  margin-top: 12px;
5689
6116
  font-size: 11px;
@@ -7975,8 +8402,9 @@ pre.mermaid svg {
7975
8402
  color: var(--text-primary);
7976
8403
  }
7977
8404
 
7978
- /* Move-to-group menu (right-click / long-press) */
7979
- .sg-menu {
8405
+ /* Move-to-group menu (right-click / long-press); the pane tab menu shares the look */
8406
+ .sg-menu,
8407
+ .pane-menu {
7980
8408
  position: fixed;
7981
8409
  z-index: 3000;
7982
8410
  min-width: 180px;
@@ -7991,7 +8419,8 @@ pre.mermaid svg {
7991
8419
  gap: 1px;
7992
8420
  }
7993
8421
 
7994
- .sg-menu-label {
8422
+ .sg-menu-label,
8423
+ .pane-menu-label {
7995
8424
  padding: 6px 8px 4px;
7996
8425
  font-size: 10px;
7997
8426
  text-transform: uppercase;
@@ -7999,7 +8428,8 @@ pre.mermaid svg {
7999
8428
  color: var(--text-muted);
8000
8429
  }
8001
8430
 
8002
- .sg-menu-item {
8431
+ .sg-menu-item,
8432
+ .pane-menu-item {
8003
8433
  display: flex;
8004
8434
  align-items: center;
8005
8435
  gap: 6px;
@@ -8016,7 +8446,9 @@ pre.mermaid svg {
8016
8446
  }
8017
8447
 
8018
8448
  .sg-menu-item:hover,
8019
- .sg-menu-item:focus-visible {
8449
+ .sg-menu-item:focus-visible,
8450
+ .pane-menu-item:hover,
8451
+ .pane-menu-item:focus-visible {
8020
8452
  background: var(--bg-hover);
8021
8453
  outline: none;
8022
8454
  }
@@ -8024,4 +8456,16 @@ pre.mermaid svg {
8024
8456
  .sg-menu-remove {
8025
8457
  color: var(--text-secondary);
8026
8458
  }
8459
+
8460
+ .pane-menu {
8461
+ font-family: var(--font-mono);
8462
+ }
8463
+
8464
+ .pane-menu-label {
8465
+ overflow: hidden;
8466
+ text-overflow: ellipsis;
8467
+ text-transform: none;
8468
+ letter-spacing: 0;
8469
+ white-space: nowrap;
8470
+ }
8027
8471
  /* #endregion */
@@ -0,0 +1,74 @@
1
+ // Loaded by the page as a plain script and by the tests through require.
2
+ // Highlights plain-text call trees and flows in message code blocks. The output only wraps the
3
+ // input in spans, so a copy of the block stays byte-identical to what Claude wrote.
4
+ const treeHighlight = (() => {
5
+ const TREE_TAGS = new Set(['tree', 'callstack', 'flow']);
6
+ const PLAIN_TAGS = new Set(['', 'text', 'txt', 'plaintext', 'diff']);
7
+ const TREE_LINE = /^[+-]?[ \t]*[│├└▼]/;
8
+ const BOX_CORNERS = /[┌┐┬┼┴┘╔╗╚╝╠╣╦╩╬]/;
9
+ const NAME = String.raw`[\p{L}_$@][\p{L}\p{N}_$@.:<>\-/\\]*`;
10
+ const LINE = new RegExp(
11
+ String.raw`^([+-](?=[ \t│├└▼]|$))?([ \t]*(?:[│├└─▼↓→ \t]*[│├└─▼↓→][ \t]*)?)(${NAME})?(.*)$`,
12
+ 'su',
13
+ );
14
+ const INDENT_NODE_LINE = new RegExp(String.raw`^([ \t]*)${NAME}(\([^()]*\))?( {2,}\S.*)?$`, 'u');
15
+
16
+ function escapeText(s) {
17
+ return s
18
+ .replace(/&/g, '&amp;')
19
+ .replace(/</g, '&lt;')
20
+ .replace(/>/g, '&gt;')
21
+ .replace(/"/g, '&quot;')
22
+ .replace(/'/g, '&#39;');
23
+ }
24
+
25
+ function span(cls, s) {
26
+ return s ? `<span class="${cls}">${escapeText(s)}</span>` : '';
27
+ }
28
+
29
+ // Two tree lines at least, and no table corners: box tables and ASCII art draw with those.
30
+ function looksLikeTree(text) {
31
+ if (BOX_CORNERS.test(text)) return false;
32
+ let hits = 0;
33
+ for (const line of text.split('\n')) {
34
+ if (TREE_LINE.test(line) && ++hits >= 2) return true;
35
+ }
36
+ return false;
37
+ }
38
+
39
+ // A call tree drawn with indentation only: every line is one name or call, with an optional
40
+ // note after two spaces. Prose and pseudocode have single-spaced words, so they never match.
41
+ function looksLikeIndentTree(text, diff) {
42
+ const lines = text.split('\n').filter((l) => l.trim());
43
+ if (lines.length < 3) return false;
44
+ const indents = new Set();
45
+ for (const raw of lines) {
46
+ if (diff && !/^[ +-]/.test(raw)) return false;
47
+ const m = (diff ? raw.slice(1) : raw).match(INDENT_NODE_LINE);
48
+ if (!m) return false;
49
+ indents.add(m[1].length);
50
+ }
51
+ return indents.size >= 2;
52
+ }
53
+
54
+ function wantsTree(lang, text) {
55
+ const tag = (lang || '').trim().toLowerCase();
56
+ if (TREE_TAGS.has(tag)) return true;
57
+ return PLAIN_TAGS.has(tag) && (looksLikeTree(text) || looksLikeIndentTree(text, tag === 'diff'));
58
+ }
59
+
60
+ function highlightLine(line) {
61
+ const [, marker = '', glyphs, node = '', rest] = line.match(LINE);
62
+ const body = span('hljs-punctuation', glyphs) + span('hljs-title', node) + escapeText(rest);
63
+ if (!marker) return body;
64
+ return `<span class="${marker === '+' ? 'hljs-addition' : 'hljs-deletion'}">${marker}${body}</span>`;
65
+ }
66
+
67
+ function highlightTree(text) {
68
+ return text.split('\n').map(highlightLine).join('\n');
69
+ }
70
+
71
+ return { wantsTree, highlightTree };
72
+ })();
73
+
74
+ if (typeof module === 'object' && module.exports) module.exports = treeHighlight;