@tiagojct/subsub 0.22.0 → 0.23.0
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/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/web/app.css +106 -53
- package/web/app.js +46 -9
- package/web/i18n.js +2 -0
- package/web/index.html +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Sub-Sub follows the version numbers of npm: a change in the second number (0.13) adds features or changes how you use it; a change in the third (0.13.1) only fixes.
|
|
4
4
|
|
|
5
|
+
## 0.23.0 (2026-10-24)
|
|
6
|
+
|
|
7
|
+
- The browser view is set like the website. Replies and notes have the website's headings (a fine rule above each section), its numbered steps and a slightly larger text. A note opens on the lighter field of a page, and the list of notes is grouped by folder under small-capital heads, with the date on the right. The empty conversation has the website's running head and a larger heading. Notes and the model in the top bar are plain text buttons, as in the website's menu. A command you send (`/read ...`) is shown in the mono. The working line is drawn dotted, dashed and solid, as in the terminal, instead of a moving gradient. Scrollbars are thin. On a phone, Notes is no longer cut to "No..." (the theme button is in the side panel there).
|
|
8
|
+
- In the browser view, the sections of the side panel (Library, Actions, Conversations) and the two groups of actions (Research, Library) fold at their heading. With the others folded, the list of conversations takes the whole panel. The browser remembers which ones you folded.
|
|
9
|
+
|
|
5
10
|
## 0.22.0 (2026-10-24)
|
|
6
11
|
|
|
7
12
|
- `subsub tui`: Sub-Sub on the whole terminal. It is a full-screen client of pi's RPC mode, like the browser view, with the look of `subsub`: a conversation that scrolls (mouse wheel, PgUp and PgDn), a side panel from 110 columns (mode, library, keys, earlier conversations), an editor that completes commands, and the footer. Alt+N, Alt+L, Alt+A and Alt+M open the notes, the library search and the actions, and switch the mode, as in `subsub`. `/sessions` and `/new` change the conversation, `/model` chooses the model, `/restart` starts pi again in the same conversation, `/quit` leaves. Ctrl+O shows the output of tool rows, Esc stops a running answer, and Ctrl+C twice quits. `--light` or `--dark` set the colours when the terminal does not say (the default is dark), and `--librarian` starts in the Librarian.
|
package/package.json
CHANGED
package/web/app.css
CHANGED
|
@@ -115,9 +115,13 @@ body {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
button, input, select, textarea { font: inherit; color: inherit; }
|
|
118
|
-
a { color: var(--accent); text-underline-offset: 0.
|
|
118
|
+
a { color: var(--accent); text-underline-offset: 0.2em; text-decoration-thickness: 1px; }
|
|
119
119
|
a:hover { text-decoration-thickness: 2px; }
|
|
120
120
|
svg { flex: none; }
|
|
121
|
+
/* Scrollbars as fine rules, not as grey bars. */
|
|
122
|
+
* { scrollbar-width: thin; }
|
|
123
|
+
/* The name breaks nowhere, least of all at its hyphen. */
|
|
124
|
+
.nobr { white-space: nowrap; }
|
|
121
125
|
|
|
122
126
|
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
123
127
|
[hidden] { display: none !important; }
|
|
@@ -164,20 +168,20 @@ button:disabled { opacity: 0.45; cursor: default; }
|
|
|
164
168
|
.icon-btn:hover { color: var(--text); background: var(--surface); }
|
|
165
169
|
.icon-btn[aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); }
|
|
166
170
|
|
|
167
|
-
|
|
171
|
+
/* Top bar text buttons, as the website's navigation: muted, inked and underlined on hover or when open. */
|
|
168
172
|
.chip-btn {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
white-space: nowrap;
|
|
173
|
-
font-family: var(--mono);
|
|
174
|
-
font-size: 0.78rem;
|
|
173
|
+
flex: none;
|
|
174
|
+
padding: 0.35rem 0.5rem;
|
|
175
|
+
font-size: 0.9rem;
|
|
175
176
|
color: var(--muted);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
text-underline-offset: 0.35em;
|
|
179
|
+
text-decoration-thickness: 1px;
|
|
179
180
|
}
|
|
180
|
-
.chip-btn:hover { color: var(--text);
|
|
181
|
+
.chip-btn:hover { color: var(--text); text-decoration-line: underline; }
|
|
182
|
+
.chip-btn.model-btn { flex: 0 1 auto; max-width: 16rem; overflow: hidden; text-overflow: ellipsis; font-family: var(--mono); font-size: 0.8rem; }
|
|
183
|
+
/* No model yet: pending, so gamboge and dashed. */
|
|
184
|
+
.model-btn.needs { color: var(--warn); border-color: var(--warn); border-style: dashed; font-family: var(--sans); }
|
|
181
185
|
|
|
182
186
|
select {
|
|
183
187
|
appearance: none;
|
|
@@ -319,7 +323,7 @@ select:hover { border-color: var(--rule-strong); }
|
|
|
319
323
|
.new-btn:hover { border-color: var(--text); }
|
|
320
324
|
.new-btn svg { width: 16px; height: 16px; color: var(--accent); }
|
|
321
325
|
|
|
322
|
-
.actions { display: flex; flex-direction: column; }
|
|
326
|
+
.actions, .actions > div { display: flex; flex-direction: column; }
|
|
323
327
|
.actions button {
|
|
324
328
|
text-align: left;
|
|
325
329
|
padding: 0.32rem 0.5rem;
|
|
@@ -329,9 +333,35 @@ select:hover { border-color: var(--rule-strong); }
|
|
|
329
333
|
}
|
|
330
334
|
.actions button:hover { background: var(--bg); }
|
|
331
335
|
.actions .group { margin: 0.6rem 0 0.15rem; padding: 0 0.5rem; font-family: var(--serif); font-style: italic; font-size: 0.86rem; color: var(--muted); }
|
|
332
|
-
.actions
|
|
336
|
+
.actions > :first-child .group { margin-top: 0; }
|
|
337
|
+
|
|
338
|
+
/* Sections and action groups fold at their heading; a fine chevron points down when open, right when folded. */
|
|
339
|
+
.side .fold {
|
|
340
|
+
display: inline-flex;
|
|
341
|
+
align-items: center;
|
|
342
|
+
gap: 0.55em;
|
|
343
|
+
padding: 0;
|
|
344
|
+
border: 0;
|
|
345
|
+
border-radius: 0;
|
|
346
|
+
font-size: inherit;
|
|
347
|
+
letter-spacing: inherit;
|
|
348
|
+
color: inherit;
|
|
349
|
+
text-align: left;
|
|
350
|
+
}
|
|
351
|
+
.side .fold:hover { color: var(--text); background: none; }
|
|
352
|
+
.side .fold::after {
|
|
353
|
+
content: "";
|
|
354
|
+
width: 0.34em;
|
|
355
|
+
height: 0.34em;
|
|
356
|
+
border-right: 1px solid currentColor;
|
|
357
|
+
border-bottom: 1px solid currentColor;
|
|
358
|
+
transform: translateY(-0.15em) rotate(45deg);
|
|
359
|
+
}
|
|
360
|
+
.side .fold[aria-expanded="false"]::after { transform: rotate(-45deg); }
|
|
361
|
+
[data-fold].folded > :not(h2, .side-subhead, .group) { display: none; }
|
|
333
362
|
|
|
334
363
|
.side .grow { flex: 1; display: flex; flex-direction: column; min-height: 8rem; }
|
|
364
|
+
.side .grow.folded { flex: none; min-height: 0; }
|
|
335
365
|
.side-subhead { display: flex; justify-content: space-between; align-items: baseline; padding-right: 0.3rem; }
|
|
336
366
|
|
|
337
367
|
.search {
|
|
@@ -423,7 +453,6 @@ body.layout-full { --measure: 72rem; }
|
|
|
423
453
|
padding: 0.6rem 0.6rem 0.6rem 0.9rem;
|
|
424
454
|
border: 1px solid var(--rule);
|
|
425
455
|
border-top: 2px solid var(--warn);
|
|
426
|
-
border-radius: var(--radius);
|
|
427
456
|
background: var(--raised);
|
|
428
457
|
display: flex;
|
|
429
458
|
gap: 0.75rem;
|
|
@@ -454,14 +483,16 @@ body.layout-full { --measure: 72rem; }
|
|
|
454
483
|
overflow-wrap: anywhere;
|
|
455
484
|
font-size: 0.95rem;
|
|
456
485
|
}
|
|
486
|
+
/* A command is code: it is set in the mono, as the website's plate draws it. */
|
|
487
|
+
.msg.user .bubble.cmd { font-family: var(--mono); font-size: 0.86rem; }
|
|
457
488
|
.msg.user details.bubble summary { cursor: pointer; color: var(--muted); }
|
|
458
489
|
.msg.user details.bubble[open] summary { margin-bottom: 0.4rem; }
|
|
459
490
|
|
|
460
491
|
/* Replies read as text, in the serif. */
|
|
461
492
|
.md {
|
|
462
493
|
font-family: var(--serif);
|
|
463
|
-
font-size: 1.
|
|
464
|
-
line-height: 1.
|
|
494
|
+
font-size: 1.0625rem;
|
|
495
|
+
line-height: 1.65;
|
|
465
496
|
font-variant-numeric: oldstyle-nums proportional-nums;
|
|
466
497
|
overflow-wrap: anywhere;
|
|
467
498
|
}
|
|
@@ -470,16 +501,20 @@ body.layout-full { --measure: 72rem; }
|
|
|
470
501
|
.md p, .md ul, .md ol { margin: 0.7rem 0; }
|
|
471
502
|
.md li { margin: 0.2rem 0; }
|
|
472
503
|
.md li::marker { color: var(--faint); }
|
|
504
|
+
/* Numbered steps as on the website: the number in the accent, lining figures. */
|
|
505
|
+
.md ol > li::marker { color: var(--accent); font-weight: 600; font-variant-numeric: lining-nums tabular-nums; }
|
|
506
|
+
/* Headings as on the website: roman, weight 500 for the large ones, a fine rule above a section. */
|
|
473
507
|
.md h1, .md h2, .md h3, .md h4 {
|
|
474
508
|
font-family: var(--serif);
|
|
475
|
-
line-height: 1.
|
|
476
|
-
margin: 1.
|
|
509
|
+
line-height: 1.25;
|
|
510
|
+
margin: 1.8rem 0 0.6rem;
|
|
477
511
|
font-weight: 600;
|
|
512
|
+
text-wrap: balance;
|
|
478
513
|
}
|
|
479
|
-
.md h1 { font-size: 1.
|
|
480
|
-
.md h2 { font-size: 1.
|
|
481
|
-
.md h3 { font-size:
|
|
482
|
-
.md h4 { font-size: 0.
|
|
514
|
+
.md h1 { font-size: 1.5rem; font-weight: 500; line-height: 1.15; letter-spacing: -0.012em; }
|
|
515
|
+
.md h2 { font-size: 1.25rem; font-weight: 500; letter-spacing: -0.008em; padding-top: 0.5rem; border-top: 1px solid var(--rule-strong); }
|
|
516
|
+
.md h3 { font-size: 1.04rem; }
|
|
517
|
+
.md h4 { font-size: 0.95rem; color: var(--muted); }
|
|
483
518
|
.md strong { font-weight: 600; }
|
|
484
519
|
.md hr { border: 0; border-top: 1px solid var(--rule); margin: 1.5rem 0; }
|
|
485
520
|
.msg.assistant + .msg.assistant { margin-top: 0.9rem; }
|
|
@@ -568,7 +603,7 @@ body.layout-full { --measure: 72rem; }
|
|
|
568
603
|
|
|
569
604
|
.msg-actions { display: flex; gap: 0.25rem; margin-top: 0.4rem; opacity: 0; transition: opacity 0.12s ease; }
|
|
570
605
|
.msg.assistant:hover .msg-actions, .msg-actions:focus-within { opacity: 1; }
|
|
571
|
-
.msg-action-btn { font-size: 0.75rem; padding: 0.15rem 0.45rem; color: var(--faint); }
|
|
606
|
+
.msg-action-btn { font-family: var(--sans); font-size: 0.75rem; padding: 0.15rem 0.45rem; color: var(--faint); }
|
|
572
607
|
.msg-action-btn:hover { color: var(--text); background: var(--surface); }
|
|
573
608
|
.msg-action-btn.copied { color: var(--add); }
|
|
574
609
|
.table-actions { display: flex; justify-content: flex-end; margin: -0.35rem 0 0.9rem; }
|
|
@@ -633,7 +668,6 @@ body.layout-full { --measure: 72rem; }
|
|
|
633
668
|
.note {
|
|
634
669
|
margin-top: 1rem;
|
|
635
670
|
padding: 0.6rem 0.9rem;
|
|
636
|
-
border-radius: var(--radius);
|
|
637
671
|
background: var(--surface);
|
|
638
672
|
border: 1px solid var(--rule);
|
|
639
673
|
font-size: 0.88rem;
|
|
@@ -659,44 +693,48 @@ body.layout-full { --measure: 72rem; }
|
|
|
659
693
|
gap: 0.55rem;
|
|
660
694
|
align-items: center;
|
|
661
695
|
}
|
|
696
|
+
/* Working is drawn by line, as in the terminal: dotted, dashed, solid. Without motion it stays dashed: pending. */
|
|
662
697
|
.working::before {
|
|
663
698
|
content: "";
|
|
664
699
|
width: 28px;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
background: linear-gradient(90deg, var(--accent) 0 40%, var(--rule) 40% 100%);
|
|
668
|
-
background-size: 250% 100%;
|
|
669
|
-
animation: sweep 1.3s ease-in-out infinite alternate;
|
|
700
|
+
border-top: 2px dashed var(--accent);
|
|
701
|
+
animation: lines 1.5s steps(1, end) infinite;
|
|
670
702
|
}
|
|
671
|
-
@keyframes
|
|
703
|
+
@keyframes lines { 0% { border-top-style: dotted; } 33% { border-top-style: dashed; } 67% { border-top-style: solid; } }
|
|
672
704
|
@keyframes pulse { 50% { opacity: 0.3; } }
|
|
673
705
|
|
|
674
706
|
/* Empty conversation. */
|
|
675
707
|
.empty { padding-top: 11vh; }
|
|
676
|
-
|
|
708
|
+
/* The running head of the website's plates: the name, an italic middle, the mode in its colour. */
|
|
709
|
+
.empty .runhead {
|
|
677
710
|
display: flex;
|
|
678
711
|
justify-content: space-between;
|
|
679
|
-
|
|
680
|
-
|
|
712
|
+
align-items: baseline;
|
|
713
|
+
gap: 0.5rem 1.5rem;
|
|
714
|
+
margin: 0 0 1.75rem;
|
|
715
|
+
padding-bottom: 0.35rem;
|
|
681
716
|
border-bottom: 1px solid var(--text);
|
|
682
717
|
font-family: var(--serif);
|
|
683
|
-
font-size:
|
|
718
|
+
font-size: 1.02rem;
|
|
719
|
+
line-height: 1.3;
|
|
684
720
|
font-variant-caps: all-small-caps;
|
|
685
721
|
letter-spacing: 0.08em;
|
|
686
|
-
color: var(--
|
|
722
|
+
color: var(--muted);
|
|
687
723
|
}
|
|
688
|
-
.empty .
|
|
724
|
+
.empty .runhead .mid { font-style: italic; font-variant-caps: normal; letter-spacing: 0; text-align: center; }
|
|
725
|
+
.empty .runhead .mode { color: var(--accent); }
|
|
689
726
|
.empty h1 {
|
|
690
727
|
font-family: var(--serif);
|
|
691
728
|
font-weight: 500;
|
|
692
|
-
font-size: clamp(
|
|
693
|
-
line-height: 1.
|
|
694
|
-
letter-spacing: -0.
|
|
695
|
-
margin: 0 0 0.
|
|
729
|
+
font-size: clamp(2rem, 3.6vw, 2.75rem);
|
|
730
|
+
line-height: 1.06;
|
|
731
|
+
letter-spacing: -0.018em;
|
|
732
|
+
margin: 0 0 0.9rem;
|
|
733
|
+
text-wrap: balance;
|
|
696
734
|
}
|
|
697
|
-
.empty .lede { color: var(--muted); max-width:
|
|
735
|
+
.empty .lede { font-family: var(--serif); font-size: 1.08rem; line-height: 1.55; color: var(--muted); max-width: 36rem; margin: 0 0 2rem; text-wrap: pretty; }
|
|
698
736
|
/* Suggestions: a short list of lines to start from, not a row of cards. */
|
|
699
|
-
.chips { display: grid; max-width:
|
|
737
|
+
.chips { display: grid; max-width: 36rem; border-bottom: 1px solid var(--rule-strong); }
|
|
700
738
|
.chips button {
|
|
701
739
|
text-align: left;
|
|
702
740
|
border: 0;
|
|
@@ -792,7 +830,7 @@ body > p.empty { text-align: center; color: var(--muted); padding: 30vh 1rem 0;
|
|
|
792
830
|
}
|
|
793
831
|
.modal-kicker::after { content: "Sub-Sub"; color: var(--muted); }
|
|
794
832
|
.modal-kicker:empty { display: none; }
|
|
795
|
-
.modal h2 { font-family: var(--serif); font-size: 1.
|
|
833
|
+
.modal h2 { font-family: var(--serif); font-size: 1.4rem; font-weight: 500; line-height: 1.2; letter-spacing: -0.01em; margin: 0 0 0.35rem; overflow-wrap: anywhere; }
|
|
796
834
|
.modal h3 { font-family: var(--serif); font-size: 0.98rem; font-weight: 400; font-variant-caps: all-small-caps; letter-spacing: 0.08em; color: var(--muted); margin: 1.4rem 0 0.5rem; }
|
|
797
835
|
.modal .hint { color: var(--muted); margin: 0 0 1rem; font-size: 0.88rem; }
|
|
798
836
|
|
|
@@ -888,8 +926,7 @@ body > p.empty { text-align: center; color: var(--muted); padding: 30vh 1rem 0;
|
|
|
888
926
|
|
|
889
927
|
/* ---------------------------------------------------------------- library search and notes */
|
|
890
928
|
|
|
891
|
-
#notes-btn {
|
|
892
|
-
#notes-btn[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }
|
|
929
|
+
#notes-btn[aria-expanded="true"] { color: var(--text); text-decoration-line: underline; }
|
|
893
930
|
|
|
894
931
|
.items { list-style: none; margin: 0; padding: 0; max-height: 18rem; overflow-y: auto; }
|
|
895
932
|
.items:empty { display: none; }
|
|
@@ -910,11 +947,12 @@ body > p.empty { text-align: center; color: var(--muted); padding: 30vh 1rem 0;
|
|
|
910
947
|
.session-tools button { width: auto; padding: 0.1rem 0.35rem; font-size: 0.7rem; color: var(--muted); }
|
|
911
948
|
.session-tools button:hover { color: var(--text); background: var(--bg); }
|
|
912
949
|
|
|
950
|
+
/* A note is a page: the lighter plate field, as on the website. */
|
|
913
951
|
.note-pane {
|
|
914
952
|
grid-row: 2;
|
|
915
953
|
grid-column: 3;
|
|
916
|
-
border-left: 1px solid var(--rule);
|
|
917
|
-
background: var(--
|
|
954
|
+
border-left: 1px solid var(--rule-strong);
|
|
955
|
+
background: var(--raised);
|
|
918
956
|
display: flex;
|
|
919
957
|
flex-direction: column;
|
|
920
958
|
min-height: 0;
|
|
@@ -923,7 +961,7 @@ body > p.empty { text-align: center; color: var(--muted); padding: 30vh 1rem 0;
|
|
|
923
961
|
.note-pane[hidden] { display: none; }
|
|
924
962
|
.note-head { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 0.9rem; border-bottom: 1px solid var(--rule); }
|
|
925
963
|
.note-name { flex: 1; min-width: 0; font-family: var(--mono); font-size: 0.8rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
926
|
-
.note-pane > .search { margin: 0.6rem 0.9rem 0; width: auto; }
|
|
964
|
+
.note-pane > .search { margin: 0.6rem 0.9rem 0; width: auto; background: var(--bg); }
|
|
927
965
|
.note-body { flex: 1; overflow-y: auto; padding: 0.9rem 1.1rem 1.5rem; }
|
|
928
966
|
.note-body .front { font-size: 0.74rem; color: var(--muted); margin: 0 0 1rem; }
|
|
929
967
|
/* What a note was written from, drawn by line: solid for the full text, dashed for the abstract,
|
|
@@ -940,11 +978,25 @@ body > p.empty { text-align: center; color: var(--muted); padding: 30vh 1rem 0;
|
|
|
940
978
|
#note-read[aria-pressed="true"] { color: var(--accent); text-decoration: underline; }
|
|
941
979
|
.note-body .front summary { cursor: pointer; }
|
|
942
980
|
.note-body .front pre { background: var(--bg); border: 1px solid var(--rule); border-radius: var(--radius); padding: 0.5rem 0.7rem; margin: 0.4rem 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
943
|
-
|
|
944
|
-
.note-list
|
|
981
|
+
/* The notes, one folder per section under a small-capitals head, rows ruled as in a table. */
|
|
982
|
+
.note-list section + section { margin-top: 1.4rem; }
|
|
983
|
+
.note-list h3 {
|
|
984
|
+
margin: 0;
|
|
985
|
+
padding: 0 0.5rem 0.25rem;
|
|
986
|
+
border-bottom: 1px solid var(--text);
|
|
987
|
+
font-family: var(--serif);
|
|
988
|
+
font-size: 1rem;
|
|
989
|
+
font-weight: 400;
|
|
990
|
+
font-variant-caps: all-small-caps;
|
|
991
|
+
letter-spacing: 0.08em;
|
|
992
|
+
color: var(--muted);
|
|
993
|
+
}
|
|
994
|
+
.note-list ul { list-style: none; margin: 0; padding: 0; }
|
|
995
|
+
.note-list li + li { border-top: 1px solid var(--rule); }
|
|
996
|
+
.note-list button { width: 100%; text-align: left; padding: 0.5rem; border-radius: 0; display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
|
|
945
997
|
.note-list button:hover { background: var(--bg); }
|
|
946
|
-
.note-list .rel { font-size: 0.
|
|
947
|
-
.note-list .when { font-size: 0.
|
|
998
|
+
.note-list .rel { min-width: 0; font-size: 0.9rem; overflow-wrap: anywhere; }
|
|
999
|
+
.note-list .when { flex: none; font-size: 0.74rem; color: var(--faint); font-variant-numeric: tabular-nums; }
|
|
948
1000
|
.note-body img.note-img { display: block; max-width: 100%; height: auto; margin: 0.8rem 0; border: 1px solid var(--rule); border-radius: var(--radius); background: #fff; }
|
|
949
1001
|
a[data-note] { color: var(--accent); text-decoration: underline; text-decoration-color: var(--accent-soft); text-underline-offset: 0.18em; cursor: pointer; }
|
|
950
1002
|
|
|
@@ -980,6 +1032,7 @@ a[data-note] { color: var(--accent); text-decoration: underline; text-decoration
|
|
|
980
1032
|
@media (max-width: 30rem) {
|
|
981
1033
|
.brand > span { display: none; }
|
|
982
1034
|
.modes button { padding: 0.28rem 0.6rem; }
|
|
1035
|
+
.empty .runhead .mid { display: none; }
|
|
983
1036
|
}
|
|
984
1037
|
|
|
985
1038
|
@media (prefers-reduced-motion: reduce) {
|
package/web/app.js
CHANGED
|
@@ -296,6 +296,16 @@ function hasCommand(name) {
|
|
|
296
296
|
return commands.length === 0 || commands.some((c) => c.name === base);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
// Side panel sections and action groups fold at their heading; the folded ones are remembered on this computer.
|
|
300
|
+
const folded = new Set((localStorage.getItem("subsub-folded") || "").split(" ").filter(Boolean));
|
|
301
|
+
function setFold(box, on) {
|
|
302
|
+
box.classList.toggle("folded", on);
|
|
303
|
+
box.querySelector(".fold").setAttribute("aria-expanded", String(!on));
|
|
304
|
+
if (on) folded.add(box.dataset.fold);
|
|
305
|
+
else folded.delete(box.dataset.fold);
|
|
306
|
+
localStorage.setItem("subsub-folded", [...folded].join(" "));
|
|
307
|
+
}
|
|
308
|
+
|
|
299
309
|
function renderActions() {
|
|
300
310
|
const box = $("actions");
|
|
301
311
|
box.replaceChildren();
|
|
@@ -305,8 +315,9 @@ function renderActions() {
|
|
|
305
315
|
for (const [group, label] of groups) {
|
|
306
316
|
const list = ACTIONS[group].filter(shown);
|
|
307
317
|
if (!list.length) continue;
|
|
308
|
-
|
|
309
|
-
|
|
318
|
+
const g = el("div", { "data-fold": `actions-${group}` }, el("p", { class: "group" }, el("button", { type: "button", class: "fold", text: t(label) })), list.map(actionButton));
|
|
319
|
+
setFold(g, folded.has(g.dataset.fold));
|
|
320
|
+
box.append(g);
|
|
310
321
|
}
|
|
311
322
|
}
|
|
312
323
|
|
|
@@ -315,7 +326,10 @@ function actionButton(a) {
|
|
|
315
326
|
type: "button",
|
|
316
327
|
text: a[lang] ?? a.en,
|
|
317
328
|
onclick: () => {
|
|
318
|
-
if (a.item)
|
|
329
|
+
if (a.item) {
|
|
330
|
+
setFold($("library-search").closest("[data-fold]"), false);
|
|
331
|
+
return $("library-search").focus();
|
|
332
|
+
}
|
|
319
333
|
closeSide();
|
|
320
334
|
if (a.send) sendMessage(`/${a.cmd}`);
|
|
321
335
|
else {
|
|
@@ -407,16 +421,34 @@ function renderNoteList() {
|
|
|
407
421
|
const q = $("note-filter").value.trim().toLowerCase();
|
|
408
422
|
const list = notesList.filter((n) => !q || n.rel.toLowerCase().includes(q));
|
|
409
423
|
const fmt = new Intl.DateTimeFormat(lang === "pt" ? "pt-PT" : "en-GB", { day: "numeric", month: "short", year: "numeric" });
|
|
424
|
+
// One group per folder (Inbox, Literature, ...), newest first inside each, as the server sorts them.
|
|
425
|
+
const groups = new Map();
|
|
426
|
+
for (const n of list) {
|
|
427
|
+
const [folder, ...rest] = n.rel.split(/[\\/]/);
|
|
428
|
+
if (!groups.has(folder)) groups.set(folder, []);
|
|
429
|
+
groups.get(folder).push({ ...n, name: rest.join("/").replace(/\.md$/i, "") || folder });
|
|
430
|
+
}
|
|
410
431
|
body.replaceChildren(
|
|
411
432
|
list.length
|
|
412
433
|
? el(
|
|
413
|
-
"
|
|
434
|
+
"div",
|
|
414
435
|
{ class: "note-list" },
|
|
415
|
-
|
|
436
|
+
[...groups].map(([folder, notes]) =>
|
|
416
437
|
el(
|
|
417
|
-
"
|
|
438
|
+
"section",
|
|
418
439
|
{},
|
|
419
|
-
el("
|
|
440
|
+
el("h3", { text: folder }),
|
|
441
|
+
el(
|
|
442
|
+
"ul",
|
|
443
|
+
{},
|
|
444
|
+
notes.map((n) =>
|
|
445
|
+
el(
|
|
446
|
+
"li",
|
|
447
|
+
{},
|
|
448
|
+
el("button", { type: "button", title: n.rel, onclick: () => openNote(n.path) }, el("span", { class: "rel", text: n.name }), el("span", { class: "when", text: fmt.format(new Date(n.modified)) })),
|
|
449
|
+
),
|
|
450
|
+
),
|
|
451
|
+
),
|
|
420
452
|
),
|
|
421
453
|
),
|
|
422
454
|
)
|
|
@@ -692,7 +724,7 @@ function renderEmpty() {
|
|
|
692
724
|
el(
|
|
693
725
|
"div",
|
|
694
726
|
{ class: "empty" },
|
|
695
|
-
el("p", { class: "
|
|
727
|
+
el("p", { class: "runhead" }, el("span", { class: "nobr", text: "Sub-Sub" }), el("span", { class: "mid", text: t("tagline") }), el("span", { class: "mode", text: t(mode()) })),
|
|
696
728
|
el("h1", { text: t("emptyTitle") }),
|
|
697
729
|
el("p", { class: "lede", text: t(mode() === "librarian" ? "emptyLibrarian" : "emptyResearcher") }),
|
|
698
730
|
setupList(),
|
|
@@ -732,7 +764,7 @@ function userMessage(text) {
|
|
|
732
764
|
if (text.length > 700) {
|
|
733
765
|
const first = text.split("\n").find((l) => l.trim()) ?? "";
|
|
734
766
|
bubble = el("details", { class: "bubble" }, el("summary", { text: first.length > 120 ? `${first.slice(0, 117)}...` : first }), el("div", { text }));
|
|
735
|
-
} else bubble = el("div", { class: "bubble", text });
|
|
767
|
+
} else bubble = el("div", { class: /^\/\S/.test(text) ? "bubble cmd" : "bubble", text });
|
|
736
768
|
log.append(el("div", { class: "msg user" }, bubble));
|
|
737
769
|
scrollDown(true);
|
|
738
770
|
}
|
|
@@ -1517,6 +1549,11 @@ $("theme-btn-side")?.addEventListener("click", () => {
|
|
|
1517
1549
|
closeSide();
|
|
1518
1550
|
toggleTheme();
|
|
1519
1551
|
});
|
|
1552
|
+
$("side").addEventListener("click", (e) => {
|
|
1553
|
+
const box = e.target.closest(".fold")?.closest("[data-fold]");
|
|
1554
|
+
if (box) setFold(box, !box.classList.contains("folded"));
|
|
1555
|
+
});
|
|
1556
|
+
for (const box of document.querySelectorAll(".side section[data-fold]")) setFold(box, folded.has(box.dataset.fold));
|
|
1520
1557
|
$("session-search")?.addEventListener("input", (e) => renderSessionsList(e.target.value));
|
|
1521
1558
|
$("library-search").addEventListener("input", searchLibrary);
|
|
1522
1559
|
$("notes-btn").addEventListener("click", () => ($("note-pane").hidden ? showNoteList() : showPane(false)));
|
package/web/i18n.js
CHANGED
|
@@ -84,6 +84,7 @@ export const STRINGS = {
|
|
|
84
84
|
saved: "Saved.",
|
|
85
85
|
busy: "Wait until Sub-Sub finishes.",
|
|
86
86
|
emptyTitle: "What would you like to do?",
|
|
87
|
+
tagline: "A research assistant for Zotero",
|
|
87
88
|
quit: "Close Sub-Sub",
|
|
88
89
|
quitDone: "Sub-Sub is closed. You can close this tab.",
|
|
89
90
|
earlier: "Earlier messages were summarised.",
|
|
@@ -226,6 +227,7 @@ export const STRINGS = {
|
|
|
226
227
|
saved: "Guardado.",
|
|
227
228
|
busy: "Espere que o Sub-Sub termine.",
|
|
228
229
|
emptyTitle: "O que quer fazer?",
|
|
230
|
+
tagline: "Um assistente de investigação para o Zotero",
|
|
229
231
|
quit: "Fechar o Sub-Sub",
|
|
230
232
|
quitDone: "O Sub-Sub está fechado. Pode fechar este separador.",
|
|
231
233
|
earlier: "As mensagens anteriores foram resumidas.",
|
package/web/index.html
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<button type="button" class="chip-btn" id="notes-btn" aria-controls="note-pane" aria-expanded="false" data-i18n="notes" data-i18n-title="notesHint">Notes</button>
|
|
25
25
|
<button type="button" class="chip-btn model-btn wide-only" id="model-btn"></button>
|
|
26
26
|
<button type="button" class="icon-btn wide-only" id="width-btn" aria-pressed="false"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12h16M4 12l3.5-3.5M4 12l3.5 3.5M20 12l-3.5-3.5M20 12l-3.5 3.5"/></svg><span class="visually-hidden" id="width-btn-text">Wide</span></button>
|
|
27
|
-
<button type="button" class="icon-btn" id="theme-btn"></button>
|
|
27
|
+
<button type="button" class="icon-btn wide-only" id="theme-btn"></button>
|
|
28
28
|
</div>
|
|
29
29
|
</header>
|
|
30
30
|
|
|
@@ -38,18 +38,18 @@
|
|
|
38
38
|
<button type="button" class="secondary" id="theme-btn-side"></button>
|
|
39
39
|
</label>
|
|
40
40
|
</section>
|
|
41
|
-
<section>
|
|
42
|
-
<h2 data-i18n="groupLibrary">Library</h2>
|
|
41
|
+
<section data-fold="library">
|
|
42
|
+
<h2><button type="button" class="fold" aria-expanded="true" data-i18n="groupLibrary">Library</button></h2>
|
|
43
43
|
<input type="search" id="library-search" class="search" data-i18n-placeholder="librarySearch" placeholder="Search your library" autocomplete="off">
|
|
44
44
|
<ul class="items" id="items" aria-live="polite"></ul>
|
|
45
45
|
</section>
|
|
46
|
-
<section>
|
|
47
|
-
<h2 data-i18n="actions">Actions</h2>
|
|
46
|
+
<section data-fold="actions">
|
|
47
|
+
<h2><button type="button" class="fold" aria-expanded="true" data-i18n="actions">Actions</button></h2>
|
|
48
48
|
<div class="actions" id="actions"></div>
|
|
49
49
|
</section>
|
|
50
|
-
<section class="grow">
|
|
50
|
+
<section class="grow" data-fold="conversations">
|
|
51
51
|
<div class="side-subhead">
|
|
52
|
-
<h2 data-i18n="conversations">Conversations</h2>
|
|
52
|
+
<h2><button type="button" class="fold" aria-expanded="true" data-i18n="conversations">Conversations</button></h2>
|
|
53
53
|
<button type="button" class="link-btn" id="export-btn" data-i18n="export" data-i18n-title="exportTitle">Export</button>
|
|
54
54
|
</div>
|
|
55
55
|
<input type="search" id="session-search" class="search" data-i18n-placeholder="searchConversations" placeholder="Search conversations" autocomplete="off">
|