dsh-weread-gather 1.1.2 → 1.1.3
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/client.js +199 -51
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -136,6 +136,10 @@ window.__ModuleLoader__.load({
|
|
|
136
136
|
'note.search': '搜索划线原文、书名、章节…',
|
|
137
137
|
'thought.title': '想法墙',
|
|
138
138
|
'thought.search': '搜索想法内容、书名…',
|
|
139
|
+
'thought.expand': '展开全文(还有 {count} 字)',
|
|
140
|
+
'thought.collapse': '收起',
|
|
141
|
+
'thought.contextToggle': '同章划线 {count}',
|
|
142
|
+
'thought.contextTitle': '同章的划线(供参考)',
|
|
139
143
|
'view.list': '列表',
|
|
140
144
|
'view.wall': '墙',
|
|
141
145
|
'filter.allBooks': '全部书籍',
|
|
@@ -321,6 +325,10 @@ window.__ModuleLoader__.load({
|
|
|
321
325
|
'note.search': 'Search highlight text, book, chapter…',
|
|
322
326
|
'thought.title': 'Thoughts',
|
|
323
327
|
'thought.search': 'Search thoughts and books…',
|
|
328
|
+
'thought.expand': 'Show all ({count} more characters)',
|
|
329
|
+
'thought.collapse': 'Collapse',
|
|
330
|
+
'thought.contextToggle': '{count} highlight(s) in the same chapter',
|
|
331
|
+
'thought.contextTitle': 'Highlights in the same chapter (for context)',
|
|
324
332
|
'view.list': 'List',
|
|
325
333
|
'view.wall': 'Wall',
|
|
326
334
|
'filter.allBooks': 'All books',
|
|
@@ -469,7 +477,9 @@ window.__ModuleLoader__.load({
|
|
|
469
477
|
|
|
470
478
|
// ----------------------------------------------------------------- styles
|
|
471
479
|
const CSS = `
|
|
472
|
-
.wr-root{--wr-paper:#F6F8F7;--wr-paper-soft:#FFFFFF;--wr-wash:#F0F4F1;--wr-ink:#263C34;--wr-ink-soft:#6B7F74;--wr-ink-mute:#93A49B;--wr-sage:#A6CCB5;--wr-moss:#4F9274;--wr-clay:#A57F5B;--wr-progress:#73AD8A;--wr-track:#F0F4F1;--wr-line:#E7EEEA;--wr-line-2:#DCE6E0;--wr-heat-0:#
|
|
480
|
+
.wr-root{--wr-paper:#F6F8F7;--wr-paper-soft:#FFFFFF;--wr-wash:#F0F4F1;--wr-ink:#263C34;--wr-ink-soft:#6B7F74;--wr-ink-mute:#93A49B;--wr-sage:#A6CCB5;--wr-moss:#4F9274;--wr-clay:#A57F5B;--wr-progress:#73AD8A;--wr-track:#F0F4F1;--wr-line:#E7EEEA;--wr-line-2:#DCE6E0;--wr-card-line:#EDF1EE;--wr-divider:#EEF3F0;--wr-surface:#FBFCFB;--wr-heat-0:#F3F4F2;--wr-heat-1:#D5E9DD;--wr-heat-2:#A9D3BA;--wr-heat-3:#74B28E;--wr-heat-4:#438765;--dsw-alias-bg-base:var(--wr-paper);--dsw-alias-bg-layer-1:var(--wr-paper-soft);--dsw-alias-bg-layer-2:var(--wr-wash);--dsw-alias-label-primary:var(--wr-ink);--dsw-alias-label-secondary:var(--wr-ink-soft);--dsw-alias-border-l1:var(--wr-line);--dsw-alias-border-l2:var(--wr-line-2);--dsw-alias-brand-primary:var(--wr-moss);--dsw-alias-state-success-primary:#4F7A5B;--dsw-alias-state-warn-primary:#B98A57;--dsw-alias-state-error-primary:#A8574C;display:flex;flex-direction:column;height:100%;min-height:0;width:100%;box-sizing:border-box;background:radial-gradient(900px 420px at 50% -18%,#FFFFFF 0%,transparent 70%),var(--wr-paper);color:var(--wr-ink);font-family:inherit}
|
|
481
|
+
.wr-root[data-view=shelf]{--wr-paper:#EDF2EE;--wr-paper-soft:#FFFFFF}
|
|
482
|
+
.wr-root[data-view=notes],.wr-root[data-view=thoughts]{--wr-paper:#F8FAF8;--wr-paper-soft:#FFFFFF}
|
|
473
483
|
.wr-head{flex:none;display:flex;align-items:center;gap:16px;padding:14px 26px;border-bottom:1px solid var(--dsw-alias-border-l1);background:rgba(255,255,255,.86);backdrop-filter:blur(10px);flex-wrap:wrap}
|
|
474
484
|
.wr-brand{display:flex;flex-direction:column;gap:1px;min-width:0}
|
|
475
485
|
.wr-brand-title{font-size:15.5px;font-weight:700;letter-spacing:.07em;color:var(--wr-ink)}
|
|
@@ -494,7 +504,7 @@ window.__ModuleLoader__.load({
|
|
|
494
504
|
.wr-btn-ghost:hover{background:var(--wr-wash);border-color:var(--wr-sage)}
|
|
495
505
|
.wr-body{flex:1;min-height:0;overflow:auto;padding:26px 30px 64px;scrollbar-width:thin;scrollbar-color:#CFD8C6 transparent}
|
|
496
506
|
.wr-body-inner{max-width:1240px;margin:0 auto}
|
|
497
|
-
.wr-card{background:var(--wr-paper-soft);border:1px solid var(--wr-line);border-radius:16px;padding:20px 22px
|
|
507
|
+
.wr-card{background:var(--wr-paper-soft);border:1px solid var(--wr-card-line);border-radius:16px;padding:20px 22px}
|
|
498
508
|
.wr-section{margin-top:22px}
|
|
499
509
|
.wr-section:first-child{margin-top:0}
|
|
500
510
|
.wr-section-title{display:flex;align-items:baseline;gap:8px;margin:0 0 12px;font-size:13.5px;font-weight:700;letter-spacing:.03em}
|
|
@@ -503,7 +513,7 @@ window.__ModuleLoader__.load({
|
|
|
503
513
|
.wr-empty{padding:36px 10px;text-align:center;color:var(--dsw-alias-label-secondary);font-size:13px}
|
|
504
514
|
.wr-empty strong{display:block;color:var(--dsw-alias-label-primary);font-size:14px;margin-bottom:6px}
|
|
505
515
|
.wr-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}
|
|
506
|
-
.wr-stat{background:var(--wr-paper-soft);border:1px solid var(--wr-line);border-radius:16px;padding:16px 18px
|
|
516
|
+
.wr-stat{background:var(--wr-paper-soft);border:1px solid var(--wr-card-line);border-radius:16px;padding:16px 18px}
|
|
507
517
|
.wr-stat-label{font-size:11.5px;color:var(--dsw-alias-label-secondary);letter-spacing:.04em}
|
|
508
518
|
.wr-stat-value{font-size:22px;font-weight:700;margin-top:6px;font-variant-numeric:tabular-nums;line-height:1.15}
|
|
509
519
|
.wr-stat-hint{font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-top:6px;display:flex;gap:10px;flex-wrap:wrap}
|
|
@@ -526,10 +536,9 @@ window.__ModuleLoader__.load({
|
|
|
526
536
|
@media (max-width:1240px){.wr-grid{grid-template-columns:repeat(8,minmax(0,1fr))}}
|
|
527
537
|
@media (max-width:920px){.wr-grid{grid-template-columns:repeat(6,minmax(0,1fr))}}
|
|
528
538
|
@media (max-width:640px){.wr-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
|
|
529
|
-
.wr-book{appearance:none;border:0;background:transparent;padding:0;margin:0;text-align:left;cursor:pointer;display:flex;flex-direction:column;gap:6px;font:inherit;color:inherit}
|
|
530
|
-
.wr-book-cover-wrap{position:relative;border-radius:8px;overflow:hidden;box-shadow:0
|
|
531
|
-
.wr-book-cover-wrap{
|
|
532
|
-
.wr-book:hover .wr-book-cover-wrap{transform:translateY(-3px);box-shadow:0 8px 16px rgba(38,60,52,.14),0 22px 34px -20px rgba(38,60,52,.55)}
|
|
539
|
+
.wr-book{appearance:none;border:0;background:transparent;padding:0;margin:0;text-align:left;cursor:pointer;display:flex;flex-direction:column;gap:6px;font:inherit;color:inherit;content-visibility:auto;contain-intrinsic-size:0 220px}
|
|
540
|
+
.wr-book-cover-wrap{position:relative;border-radius:8px;overflow:hidden;box-shadow:0 2px 6px rgba(38,60,52,.08);transition:transform .2s ease,box-shadow .2s ease}
|
|
541
|
+
.wr-book:hover .wr-book-cover-wrap{transform:translateY(-3px);box-shadow:0 6px 14px rgba(38,60,52,.12)}
|
|
533
542
|
.wr-cover{aspect-ratio:3/4;width:100%;background:var(--dsw-alias-bg-layer-2);display:block;overflow:hidden;position:relative}
|
|
534
543
|
.wr-cover img{width:100%;height:100%;object-fit:cover;display:block}
|
|
535
544
|
.wr-cover-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:30px;font-weight:700;color:rgba(255,255,255,.94);background:linear-gradient(150deg,#A8B9A3,#5F7562)}
|
|
@@ -543,32 +552,34 @@ window.__ModuleLoader__.load({
|
|
|
543
552
|
.wr-progress span{display:block;height:100%;background:var(--dsw-alias-brand-primary);border-radius:2px}
|
|
544
553
|
.wr-hour{display:grid;grid-template-columns:repeat(24,minmax(0,1fr));gap:3px;align-items:end;height:120px}
|
|
545
554
|
.wr-hour-col{display:flex;flex-direction:column;justify-content:flex-end;align-items:center;gap:4px;height:100%}
|
|
546
|
-
.wr-hour-bar{width:100%;border-radius:
|
|
555
|
+
.wr-hour-bar{width:100%;border-radius:4px 4px 0 0;background:var(--wr-sage);min-height:2px}
|
|
556
|
+
.wr-hour-bar[data-future=true]{background:var(--wr-track)}
|
|
547
557
|
.wr-hour-col:hover .wr-hour-bar,.wr-hour-bar[data-active=true]{background:var(--wr-moss)}
|
|
558
|
+
.wr-hour-bar[data-future=true],.wr-hour-col:hover .wr-hour-bar[data-future=true]{background:var(--wr-track)}
|
|
548
559
|
.wr-hour-label[data-active=true]{color:var(--wr-ink);font-weight:600}
|
|
549
560
|
.wr-hour-label{font-size:9px;color:var(--dsw-alias-label-secondary);white-space:nowrap}
|
|
550
561
|
.wr-months{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:6px;align-items:end;height:132px}
|
|
551
562
|
.wr-months .wr-hour-label{font-size:10px}
|
|
552
563
|
.wr-heat-top{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px}
|
|
553
|
-
.wr-heat-kpi{display:flex;gap:14px;flex-wrap:wrap;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:10px}
|
|
554
|
-
.wr-heat-kpi b{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-weight:600}
|
|
555
564
|
.wr-yearbar{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;padding:2px;background:var(--dsw-alias-bg-layer-1)}
|
|
556
565
|
.wr-yearbar button{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:1;padding:4px 7px;border-radius:999px;cursor:pointer}
|
|
557
566
|
.wr-yearbar button:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
558
567
|
.wr-yearbar button:disabled{opacity:.35;cursor:default}
|
|
559
568
|
.wr-yearbar strong{font-size:12.5px;font-variant-numeric:tabular-nums;padding:0 4px;min-width:42px;text-align:center}
|
|
560
569
|
.wr-heat-wrap{display:flex;gap:6px;overflow-x:auto;padding-bottom:4px}
|
|
561
|
-
.wr-heat-days{display:grid;grid-template-rows:repeat(7,
|
|
562
|
-
.wr-heat-months{display:grid;grid-auto-flow:column;grid-auto-columns:
|
|
570
|
+
.wr-heat-days{display:grid;grid-template-rows:repeat(7,12px);gap:3px;flex:none;font-size:9.5px;color:var(--dsw-alias-label-secondary);align-items:center}
|
|
571
|
+
.wr-heat-months{display:grid;grid-auto-flow:column;grid-auto-columns:12px;gap:3px;font-size:9.5px;color:var(--dsw-alias-label-secondary);overflow:hidden;margin:0 0 4px 0}
|
|
563
572
|
.wr-heat-months span{white-space:nowrap;overflow:visible}
|
|
564
|
-
.wr-heat{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,
|
|
565
|
-
.wr-heat-cell{width:
|
|
573
|
+
.wr-heat{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,12px);gap:3px;flex:none}
|
|
574
|
+
.wr-heat-cell{width:12px;height:12px;border-radius:2.5px;background:var(--wr-heat-0);transition:transform .12s ease}
|
|
566
575
|
.wr-heat-cell[data-on=true]:hover{transform:scale(1.3)}
|
|
567
|
-
.wr-heat-cell[data-future=true]{background:transparent
|
|
576
|
+
.wr-heat-cell[data-future=true]{background:transparent}
|
|
577
|
+
.wr-heat-foot{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:10px}
|
|
578
|
+
.wr-heat-foot-stats{margin-left:auto;font-size:11px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
568
579
|
.wr-banner-close{border:none;background:transparent;color:inherit;font-size:14px;line-height:1;cursor:pointer;padding:2px 4px;border-radius:6px;opacity:.55;flex:none;margin-left:auto}
|
|
569
580
|
.wr-banner-close:hover{opacity:1;background:var(--wr-wash)}
|
|
570
581
|
.wr-banner-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
571
|
-
.wr-heat-legend{display:flex;align-items:center;gap:5px;
|
|
582
|
+
.wr-heat-legend{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--dsw-alias-label-secondary)}
|
|
572
583
|
.wr-bars{display:flex;flex-direction:column;gap:8px}
|
|
573
584
|
.wr-bar-row{display:grid;grid-template-columns:104px 1fr 54px;gap:10px;align-items:center;font-size:12px}
|
|
574
585
|
.wr-bar-track{height:6px;border-radius:999px;background:var(--wr-track);overflow:hidden}
|
|
@@ -592,13 +603,29 @@ window.__ModuleLoader__.load({
|
|
|
592
603
|
.wr-cols{column-count:2;column-gap:14px}
|
|
593
604
|
@media (min-width:900px){.wr-cols{column-count:3}}
|
|
594
605
|
@media (min-width:1300px){.wr-cols{column-count:4}}
|
|
595
|
-
.wr-quote{break-inside:avoid;margin:0 0 14px;background:var(--wr-paper-soft);border:1px solid var(--wr-line);border-radius:14px;padding:14px 16px;position:relative
|
|
606
|
+
.wr-quote{break-inside:avoid;margin:0 0 14px;background:var(--wr-paper-soft);border:1px solid var(--wr-card-line);border-radius:14px;padding:14px 16px;position:relative}
|
|
596
607
|
.wr-quote:before{content:'“';position:absolute;left:9px;top:3px;font-size:26px;color:#C3D2BC;font-family:Georgia,serif;line-height:1}
|
|
597
608
|
.wr-quote-text{font-size:12.8px;line-height:1.62;white-space:pre-wrap;word-break:break-word;padding-left:12px;margin:0}
|
|
598
609
|
.wr-quote-foot{display:flex;align-items:center;gap:6px;margin-top:9px;padding-left:12px;font-size:11px;color:var(--dsw-alias-label-secondary);min-width:0}
|
|
599
610
|
.wr-quote-book{font-weight:600;color:var(--dsw-alias-label-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:60%}
|
|
600
611
|
.wr-quote-tail{margin-left:auto;flex:none;display:flex;gap:6px;align-items:center}
|
|
601
|
-
.wr-thought{break-inside:avoid;margin:0 0 14px;background:var(--wr-paper-soft);border:1px solid var(--wr-line);border-left:
|
|
612
|
+
.wr-thought{break-inside:avoid;margin:0 0 14px;background:var(--wr-paper-soft);border:1px solid var(--wr-card-line);border-left:2px solid color-mix(in srgb, var(--wr-sage) 70%, transparent);border-radius:14px;padding:15px 17px}
|
|
613
|
+
.wr-cols .wr-thought{margin-bottom:20px;background:var(--wr-paper-soft);border:1px solid var(--wr-line);border-left:2px solid color-mix(in srgb, var(--wr-sage) 70%, transparent);box-shadow:0 1px 2px rgba(38,60,52,.05)}
|
|
614
|
+
.wr-cols .wr-thought[data-clickable=true]:hover{background:var(--wr-paper-soft);border-color:var(--wr-sage);box-shadow:0 4px 12px rgba(38,60,52,.09)}
|
|
615
|
+
.wr-cols .wr-thought[data-picked=true]{border-color:var(--dsw-alias-brand-primary)}
|
|
616
|
+
.wr-thought .wr-quote-text{font-size:13.5px;line-height:1.75;text-wrap:pretty}
|
|
617
|
+
.wr-thought .wr-quote-foot{flex-wrap:wrap;gap:4px 8px;margin-top:12px;padding-top:9px;border-top:1px solid var(--wr-divider)}
|
|
618
|
+
.wr-thought .wr-quote-book{white-space:normal;overflow:visible;max-width:none;text-overflow:clip}
|
|
619
|
+
.wr-clamp{display:-webkit-box;-webkit-line-clamp:9;-webkit-box-orient:vertical;overflow:hidden}
|
|
620
|
+
.wr-more{appearance:none;border:0;background:transparent;color:var(--wr-moss);font:inherit;font-size:11.5px;line-height:1.5;padding:6px 0 0;cursor:pointer;text-align:left}
|
|
621
|
+
.wr-more:hover{text-decoration:underline}
|
|
622
|
+
.wr-thread-toggle{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;line-height:1.5;padding:8px 0 0;cursor:pointer}
|
|
623
|
+
.wr-thread-toggle:hover{color:var(--wr-ink)}
|
|
624
|
+
.wr-thread{margin-top:10px;padding:10px 12px;border-radius:10px;background:var(--wr-surface);border:1px solid var(--wr-divider)}
|
|
625
|
+
.wr-thread-title{font-size:11px;color:var(--wr-ink-mute);letter-spacing:.03em;margin-bottom:4px}
|
|
626
|
+
.wr-thread-item{font-size:12.5px;line-height:1.7;padding:6px 0;border-top:1px dashed var(--wr-divider)}
|
|
627
|
+
.wr-thread-item:first-of-type{border-top:0;padding-top:0}
|
|
628
|
+
.wr-thread-date{display:block;font-size:10.5px;color:var(--wr-ink-mute);margin-top:2px}
|
|
602
629
|
.wr-quote[data-clickable=true],.wr-thought[data-clickable=true]{cursor:pointer;transition:border-color .12s ease}
|
|
603
630
|
.wr-quote[data-clickable=true]:hover,.wr-thought[data-clickable=true]:hover{border-color:var(--dsw-alias-border-l2)}
|
|
604
631
|
.wr-copy{appearance:none;border:1px solid var(--wr-line);background:var(--wr-paper);color:var(--wr-ink-soft);font:inherit;font-size:10.5px;line-height:1;padding:4px 9px;border-radius:999px;cursor:pointer;flex:none;opacity:0;transition:opacity .14s ease,color .14s ease,border-color .14s ease}
|
|
@@ -618,7 +645,7 @@ window.__ModuleLoader__.load({
|
|
|
618
645
|
.wr-export-body{flex:1 1 auto;min-height:120px;max-height:46vh;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 12px;background:var(--dsw-alias-bg-layer-1)}
|
|
619
646
|
.wr-export-label{display:flex;justify-content:space-between;gap:10px;font-size:11px;color:var(--dsw-alias-label-secondary);margin-bottom:6px}
|
|
620
647
|
.wr-export-preview{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.6;white-space:pre-wrap;word-break:break-word}
|
|
621
|
-
.wr-stars{color:var(--
|
|
648
|
+
.wr-stars{color:var(--wr-clay);font-size:10.5px;letter-spacing:1px}
|
|
622
649
|
.wr-detail-head{display:flex;gap:18px;align-items:flex-start;flex-wrap:wrap}
|
|
623
650
|
.wr-detail-cover{width:120px;flex:none;border-radius:12px;overflow:hidden;box-shadow:0 10px 24px -12px rgba(38,60,52,.55)}
|
|
624
651
|
.wr-detail-main{flex:1;min-width:220px;display:flex;flex-direction:column;gap:8px}
|
|
@@ -662,7 +689,7 @@ window.__ModuleLoader__.load({
|
|
|
662
689
|
'div',
|
|
663
690
|
{ className: 'wr-cover' },
|
|
664
691
|
book.cover && !failed
|
|
665
|
-
? h('img', { src: book.cover, alt: book.title || '', loading: 'lazy', referrerPolicy: 'no-referrer', onError: () => setFailed(true) })
|
|
692
|
+
? h('img', { src: book.cover, alt: book.title || '', loading: 'lazy', decoding: 'async', referrerPolicy: 'no-referrer', onError: () => setFailed(true) })
|
|
666
693
|
: h('div', { className: 'wr-cover-fallback' }, String(book.title || '书').slice(0, 1)),
|
|
667
694
|
)
|
|
668
695
|
}
|
|
@@ -860,9 +887,12 @@ window.__ModuleLoader__.load({
|
|
|
860
887
|
}
|
|
861
888
|
return 0
|
|
862
889
|
}
|
|
890
|
+
// Monthly ticks would crowd the ~53 columns, so only quarter starts get a
|
|
891
|
+
// label — enough to orient without turning the axis into a wall of text.
|
|
892
|
+
const labelledMonths = [1, 4, 7, 10]
|
|
863
893
|
const ticks = model.columns.map((column, index) => {
|
|
864
894
|
const month = monthOf(column)
|
|
865
|
-
if (month === 0) return ''
|
|
895
|
+
if (month === 0 || !labelledMonths.includes(month)) return ''
|
|
866
896
|
for (let back = index - 1; back >= 0; back -= 1) {
|
|
867
897
|
const previous = monthOf(model.columns[back])
|
|
868
898
|
if (previous > 0) return previous === month ? '' : monthLabel(month - 1)
|
|
@@ -893,8 +923,8 @@ window.__ModuleLoader__.load({
|
|
|
893
923
|
cells.push(h('div', { key: `e-${columnIndex}-${row}`, className: 'wr-heat-cell', style: { background: 'transparent' } }))
|
|
894
924
|
continue
|
|
895
925
|
}
|
|
896
|
-
// Days after today are
|
|
897
|
-
//
|
|
926
|
+
// Days after today are left blank: an outline still reads as a row of
|
|
927
|
+
// dots, and "not yet" needs no decoration at all.
|
|
898
928
|
const future = cell.iso > todayIso
|
|
899
929
|
cells.push(
|
|
900
930
|
h('div', {
|
|
@@ -903,7 +933,7 @@ window.__ModuleLoader__.load({
|
|
|
903
933
|
'data-on': cell.value > 0 ? 'true' : 'false',
|
|
904
934
|
'data-future': future ? 'true' : 'false',
|
|
905
935
|
title: future
|
|
906
|
-
?
|
|
936
|
+
? null
|
|
907
937
|
: `${cell.iso} · ${cell.value > 0 ? fmtDuration(cell.value) : t('dash.heat.none')}`,
|
|
908
938
|
style: { background: future ? 'transparent' : level(cell.value) },
|
|
909
939
|
}),
|
|
@@ -914,14 +944,6 @@ window.__ModuleLoader__.load({
|
|
|
914
944
|
return h(
|
|
915
945
|
'div',
|
|
916
946
|
null,
|
|
917
|
-
h(
|
|
918
|
-
'div',
|
|
919
|
-
{ className: 'wr-heat-kpi' },
|
|
920
|
-
h('span', null, t('dash.heat.active', { days: model.active })),
|
|
921
|
-
h('span', null, t('dash.heat.streak', { days: model.streak })),
|
|
922
|
-
h('span', null, t('dash.heat.total', { time: fmtDuration(model.total) })),
|
|
923
|
-
model.bestDate === '' ? null : h('span', null, t('dash.heat.best', { date: model.bestDate, time: fmtDuration(model.best) })),
|
|
924
|
-
),
|
|
925
947
|
h(
|
|
926
948
|
'div',
|
|
927
949
|
{ className: 'wr-heat-wrap' },
|
|
@@ -930,18 +952,36 @@ window.__ModuleLoader__.load({
|
|
|
930
952
|
'div',
|
|
931
953
|
{ style: { minWidth: 0 } },
|
|
932
954
|
h('div', { className: 'wr-heat-months' }, ticks.map((label, index) => h('span', { key: index }, label))),
|
|
933
|
-
h(
|
|
955
|
+
h(
|
|
956
|
+
'div',
|
|
957
|
+
{
|
|
958
|
+
className: 'wr-heat',
|
|
959
|
+
title: model.bestDate === '' ? null : t('dash.heat.best', { date: model.bestDate, time: fmtDuration(model.best) }),
|
|
960
|
+
},
|
|
961
|
+
cells,
|
|
962
|
+
),
|
|
934
963
|
),
|
|
935
964
|
),
|
|
965
|
+
// The numbers live under the grid and stay to one line: above it they
|
|
966
|
+
// pushed the calendar away from the card title.
|
|
936
967
|
h(
|
|
937
968
|
'div',
|
|
938
|
-
{ className: 'wr-heat-
|
|
939
|
-
h(
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
h('span', {
|
|
944
|
-
|
|
969
|
+
{ className: 'wr-heat-foot' },
|
|
970
|
+
h(
|
|
971
|
+
'div',
|
|
972
|
+
{ className: 'wr-heat-legend' },
|
|
973
|
+
h('span', null, t('dash.heat.none')),
|
|
974
|
+
h('span', { className: 'wr-heat-cell' }),
|
|
975
|
+
h('span', { style: { marginLeft: '6px' } }, t('dash.less')),
|
|
976
|
+
['var(--wr-heat-1)', 'var(--wr-heat-2)', 'var(--wr-heat-3)', 'var(--wr-heat-4)'].map((color, index) =>
|
|
977
|
+
h('span', { key: index, className: 'wr-heat-cell', style: { background: color } })),
|
|
978
|
+
h('span', null, t('dash.more')),
|
|
979
|
+
),
|
|
980
|
+
h(
|
|
981
|
+
'div',
|
|
982
|
+
{ className: 'wr-heat-foot-stats' },
|
|
983
|
+
`${t('dash.heat.active', { days: model.active })} · ${t('dash.heat.total', { time: fmtDuration(model.total) })}`,
|
|
984
|
+
),
|
|
945
985
|
),
|
|
946
986
|
)
|
|
947
987
|
}
|
|
@@ -987,16 +1027,22 @@ window.__ModuleLoader__.load({
|
|
|
987
1027
|
function MonthChart(props) {
|
|
988
1028
|
const values = Array.isArray(props.values) ? props.values : []
|
|
989
1029
|
const activeIndex = Number.isInteger(props.activeIndex) ? props.activeIndex : -1
|
|
1030
|
+
const year = Number(props.year) || new Date().getFullYear()
|
|
1031
|
+
const now = new Date()
|
|
1032
|
+
// Months that have not happened yet are drawn in the track colour instead
|
|
1033
|
+
// of vanishing, so a partial year reads as "not yet" rather than "no data".
|
|
1034
|
+
const futureFrom = year === now.getFullYear() ? now.getMonth() + 1 : year > now.getFullYear() ? 0 : 13
|
|
990
1035
|
const max = Math.max.apply(null, values.concat([1]))
|
|
991
1036
|
const cells = []
|
|
992
1037
|
for (let index = 0; index < 12; index += 1) {
|
|
993
1038
|
const value = values[index] || 0
|
|
994
|
-
const
|
|
1039
|
+
const future = index >= futureFrom
|
|
1040
|
+
const height = max <= 0 || future ? 0 : Math.max(value > 0 ? 4 : 0, Math.round((value / max) * 100))
|
|
995
1041
|
cells.push(
|
|
996
1042
|
h(
|
|
997
1043
|
'div',
|
|
998
|
-
{ className: 'wr-hour-col', key: index, title: `${index + 1} · ${fmtDuration(value)}` },
|
|
999
|
-
h('div', { className: 'wr-hour-bar', 'data-active': index === activeIndex ? 'true' : 'false', style: { height: `${height}%` } }),
|
|
1044
|
+
{ className: 'wr-hour-col', key: index, title: future ? `${index + 1} · ${t('dash.heat.future')}` : `${index + 1} · ${fmtDuration(value)}` },
|
|
1045
|
+
h('div', { className: 'wr-hour-bar', 'data-active': index === activeIndex ? 'true' : 'false', 'data-future': future ? 'true' : 'false', style: { height: future ? '6%' : `${height}%` } }),
|
|
1000
1046
|
h('div', { className: 'wr-hour-label', 'data-active': index === activeIndex ? 'true' : 'false' }, String(index + 1)),
|
|
1001
1047
|
),
|
|
1002
1048
|
)
|
|
@@ -1196,7 +1242,7 @@ window.__ModuleLoader__.load({
|
|
|
1196
1242
|
h(
|
|
1197
1243
|
'div',
|
|
1198
1244
|
{ className: 'wr-split wr-section' },
|
|
1199
|
-
h('div', { className: 'wr-card' }, h('h3', { className: 'wr-section-title' }, t('dash.month')), h(MonthChart, { values: monthly, activeIndex: isCurrent ? new Date().getMonth() : -1 })),
|
|
1245
|
+
h('div', { className: 'wr-card' }, h('h3', { className: 'wr-section-title' }, t('dash.month')), h(MonthChart, { values: monthly, year, activeIndex: isCurrent ? new Date().getMonth() : -1 })),
|
|
1200
1246
|
h('div', { className: 'wr-card' }, h('h3', { className: 'wr-section-title' }, t('dash.category')), h(BarList, { items: categories })),
|
|
1201
1247
|
),
|
|
1202
1248
|
h(
|
|
@@ -1487,15 +1533,42 @@ window.__ModuleLoader__.load({
|
|
|
1487
1533
|
)
|
|
1488
1534
|
}
|
|
1489
1535
|
|
|
1536
|
+
// A thought is the reader's own voice, so the card gives the text room:
|
|
1537
|
+
// wider line height, no clamped source label, and the left rail is a quiet
|
|
1538
|
+
// sage hairline instead of an accent bar. In the wall view the card has no
|
|
1539
|
+
// border at all and relies on spacing.
|
|
1540
|
+
const THOUGHT_CLAMP_CHARS = 220
|
|
1541
|
+
|
|
1490
1542
|
function ThoughtRow(props) {
|
|
1491
1543
|
const thought = props.thought
|
|
1492
1544
|
const open = typeof props.onOpen === 'function' ? props.onOpen : null
|
|
1493
1545
|
const pick = typeof props.onToggle === 'function' ? h(PickButton, { on: Boolean(props.selected), onChange: props.onToggle }) : null
|
|
1546
|
+
const content = String(thought.content || '')
|
|
1547
|
+
const clampable = Boolean(props.wall) && content.length > THOUGHT_CLAMP_CHARS
|
|
1548
|
+
const [expanded, setExpanded] = useState(false)
|
|
1549
|
+
const [threadOpen, setThreadOpen] = useState(false)
|
|
1550
|
+
const context = Array.isArray(props.context) ? props.context : []
|
|
1551
|
+
const slice = context.slice(0, 6)
|
|
1552
|
+
const showClamped = clampable && !expanded
|
|
1494
1553
|
return h(
|
|
1495
1554
|
'div',
|
|
1496
1555
|
{ className: 'wr-thought', 'data-clickable': open ? 'true' : 'false', 'data-picked': props.selected ? 'true' : 'false', title: open ? t('action.openBook') : undefined, onClick: open || undefined },
|
|
1497
1556
|
pick,
|
|
1498
|
-
h(MarkedText, { className: 'wr-quote-text', text:
|
|
1557
|
+
h(MarkedText, { className: showClamped ? 'wr-quote-text wr-clamp' : 'wr-quote-text', text: content, needle: props.needle }),
|
|
1558
|
+
clampable
|
|
1559
|
+
? h(
|
|
1560
|
+
'button',
|
|
1561
|
+
{
|
|
1562
|
+
className: 'wr-more',
|
|
1563
|
+
type: 'button',
|
|
1564
|
+
onClick: (event) => {
|
|
1565
|
+
if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
1566
|
+
setExpanded(!expanded)
|
|
1567
|
+
},
|
|
1568
|
+
},
|
|
1569
|
+
expanded ? t('thought.collapse') : t('thought.expand', { count: String(content.length - THOUGHT_CLAMP_CHARS) }),
|
|
1570
|
+
)
|
|
1571
|
+
: null,
|
|
1499
1572
|
h(
|
|
1500
1573
|
'div',
|
|
1501
1574
|
{ className: 'wr-quote-foot' },
|
|
@@ -1506,9 +1579,38 @@ window.__ModuleLoader__.load({
|
|
|
1506
1579
|
{ className: 'wr-quote-tail' },
|
|
1507
1580
|
thought.star >= 0 ? h('span', { className: 'wr-stars' }, '★'.repeat(Math.min(5, Math.max(0, thought.star)))) : null,
|
|
1508
1581
|
h('span', null, fmtDate(thought.createTime)),
|
|
1509
|
-
h(CopyButton, { text:
|
|
1582
|
+
h(CopyButton, { text: content }),
|
|
1510
1583
|
),
|
|
1511
1584
|
),
|
|
1585
|
+
// WeRead does not link a thought to the exact highlight it answers, so
|
|
1586
|
+
// this is chapter-level context only, labelled as such.
|
|
1587
|
+
slice.length === 0
|
|
1588
|
+
? null
|
|
1589
|
+
: threadOpen
|
|
1590
|
+
? h(
|
|
1591
|
+
'div',
|
|
1592
|
+
{ className: 'wr-thread' },
|
|
1593
|
+
h('div', { className: 'wr-thread-title' }, t('thought.contextTitle')),
|
|
1594
|
+
slice.map((note) =>
|
|
1595
|
+
h(
|
|
1596
|
+
'div',
|
|
1597
|
+
{ className: 'wr-thread-item', key: note.id },
|
|
1598
|
+
`> ${String(note.text || '').slice(0, 300)}`,
|
|
1599
|
+
h('span', { className: 'wr-thread-date' }, fmtDate(note.createTime)),
|
|
1600
|
+
)),
|
|
1601
|
+
)
|
|
1602
|
+
: h(
|
|
1603
|
+
'button',
|
|
1604
|
+
{
|
|
1605
|
+
className: 'wr-thread-toggle',
|
|
1606
|
+
type: 'button',
|
|
1607
|
+
onClick: (event) => {
|
|
1608
|
+
if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
1609
|
+
setThreadOpen(true)
|
|
1610
|
+
},
|
|
1611
|
+
},
|
|
1612
|
+
t('thought.contextToggle', { count: String(context.length) }),
|
|
1613
|
+
),
|
|
1512
1614
|
)
|
|
1513
1615
|
}
|
|
1514
1616
|
|
|
@@ -1711,6 +1813,20 @@ window.__ModuleLoader__.load({
|
|
|
1711
1813
|
})
|
|
1712
1814
|
}, [source, query, bookFilter, kind])
|
|
1713
1815
|
|
|
1816
|
+
// Thoughts carry a chapter name, highlights a chapter title, and WeRead
|
|
1817
|
+
// does not link them; chapter-level context is the only honest pairing.
|
|
1818
|
+
const chapterIndex = useMemo(() => {
|
|
1819
|
+
const map = new Map()
|
|
1820
|
+
for (const note of data.notes || []) {
|
|
1821
|
+
const key = `${note.bookId}|${note.chapterTitle || ''}`
|
|
1822
|
+
const bucket = map.get(key)
|
|
1823
|
+
if (bucket) bucket.push(note)
|
|
1824
|
+
else map.set(key, [note])
|
|
1825
|
+
}
|
|
1826
|
+
return map
|
|
1827
|
+
}, [data.notes])
|
|
1828
|
+
const contextOf = (thought) => (thought.chapterName ? chapterIndex.get(`${thought.bookId}|${thought.chapterName}`) || [] : [])
|
|
1829
|
+
|
|
1714
1830
|
useEffect(() => {
|
|
1715
1831
|
setPage(0)
|
|
1716
1832
|
setSelected(new Set())
|
|
@@ -1814,7 +1930,16 @@ window.__ModuleLoader__.load({
|
|
|
1814
1930
|
})
|
|
1815
1931
|
return kind === 'note'
|
|
1816
1932
|
? h(NoteRow, { key: item.id, note: item, needle: query.trim(), selected: selected.has(item.id), onToggle: pick, onOpen: props.onOpenBook ? () => props.onOpenBook(item.bookId, kind) : null })
|
|
1817
|
-
: h(ThoughtRow, {
|
|
1933
|
+
: h(ThoughtRow, {
|
|
1934
|
+
key: item.id,
|
|
1935
|
+
thought: item,
|
|
1936
|
+
needle: query.trim(),
|
|
1937
|
+
wall: mode === 'wall',
|
|
1938
|
+
context: contextOf(item),
|
|
1939
|
+
selected: selected.has(item.id),
|
|
1940
|
+
onToggle: pick,
|
|
1941
|
+
onOpen: props.onOpenBook ? () => props.onOpenBook(item.bookId, kind) : null,
|
|
1942
|
+
})
|
|
1818
1943
|
}),
|
|
1819
1944
|
),
|
|
1820
1945
|
filtered.length > pageSize
|
|
@@ -1840,6 +1965,19 @@ window.__ModuleLoader__.load({
|
|
|
1840
1965
|
|
|
1841
1966
|
const notes = useMemo(() => (data.notes || []).filter((note) => note.bookId === book.bookId), [data.notes, book.bookId])
|
|
1842
1967
|
const thoughts = useMemo(() => (data.thoughts || []).filter((thought) => thought.bookId === book.bookId), [data.thoughts, book.bookId])
|
|
1968
|
+
// Highlights grouped by chapter: the only honest context a thought can
|
|
1969
|
+
// show, because WeRead never links a thought to one specific highlight.
|
|
1970
|
+
const chapterIndex = useMemo(() => {
|
|
1971
|
+
const map = new Map()
|
|
1972
|
+
for (const note of notes) {
|
|
1973
|
+
const key = note.chapterTitle || ''
|
|
1974
|
+
if (key === '') continue
|
|
1975
|
+
const bucket = map.get(key)
|
|
1976
|
+
if (bucket) bucket.push(note)
|
|
1977
|
+
else map.set(key, [note])
|
|
1978
|
+
}
|
|
1979
|
+
return map
|
|
1980
|
+
}, [notes])
|
|
1843
1981
|
const needle = query.trim().toLowerCase()
|
|
1844
1982
|
const visibleNotes = needle === '' ? notes : notes.filter((note) => contains(note.text, needle) || contains(note.chapterTitle, needle))
|
|
1845
1983
|
const visibleThoughts = needle === '' ? thoughts : thoughts.filter((thought) => contains(thought.content, needle) || contains(thought.chapterName, needle))
|
|
@@ -1911,7 +2049,9 @@ window.__ModuleLoader__.load({
|
|
|
1911
2049
|
'div',
|
|
1912
2050
|
{ className: 'wr-cols' },
|
|
1913
2051
|
pageItems.map((item) =>
|
|
1914
|
-
tab === 'notes'
|
|
2052
|
+
tab === 'notes'
|
|
2053
|
+
? h(NoteRow, { key: item.id, note: item, needle: needle })
|
|
2054
|
+
: h(ThoughtRow, { key: item.id, thought: item, needle: needle, wall: true, context: chapterIndex.get(item.chapterName || '') || [] }),
|
|
1915
2055
|
),
|
|
1916
2056
|
),
|
|
1917
2057
|
activeList.length > pageSize
|
|
@@ -1998,7 +2138,7 @@ window.__ModuleLoader__.load({
|
|
|
1998
2138
|
'div',
|
|
1999
2139
|
{ className: 'wr-section' },
|
|
2000
2140
|
h('h3', { className: 'wr-section-title' }, t('search.thoughts'), h('span', { className: 'wr-section-hint' }, String(groups.thoughts.length))),
|
|
2001
|
-
h('div', { className: 'wr-cols' }, groups.thoughts.map((thought) => h(ThoughtRow, { key: thought.id, thought, needle, onOpen: () => openBookOf(thought.bookId, 'thoughts') }))),
|
|
2141
|
+
h('div', { className: 'wr-cols' }, groups.thoughts.map((thought) => h(ThoughtRow, { key: thought.id, thought, needle, wall: true, onOpen: () => openBookOf(thought.bookId, 'thoughts') }))),
|
|
2002
2142
|
)
|
|
2003
2143
|
: null,
|
|
2004
2144
|
)
|
|
@@ -2489,9 +2629,17 @@ window.__ModuleLoader__.load({
|
|
|
2489
2629
|
body = h(Overview, { data: data || {} })
|
|
2490
2630
|
}
|
|
2491
2631
|
|
|
2632
|
+
// Each room gets its own paper tint, and the tint follows the tab you are
|
|
2633
|
+
// standing in — a book's thought tab keeps the thought-room warmth.
|
|
2634
|
+
const room = query.trim() !== ''
|
|
2635
|
+
? 'search'
|
|
2636
|
+
: view === 'book'
|
|
2637
|
+
? (bookTab === 'thoughts' ? 'thoughts' : 'notes')
|
|
2638
|
+
: view
|
|
2639
|
+
|
|
2492
2640
|
return h(
|
|
2493
2641
|
'div',
|
|
2494
|
-
{ className: 'wr-root', 'data-dsh-weread-gather': '' },
|
|
2642
|
+
{ className: 'wr-root', 'data-view': room, 'data-dsh-weread-gather': '' },
|
|
2495
2643
|
h('style', { dangerouslySetInnerHTML: { __html: CSS } }),
|
|
2496
2644
|
head,
|
|
2497
2645
|
h(
|
|
@@ -2595,7 +2743,7 @@ window.__ModuleLoader__.load({
|
|
|
2595
2743
|
},
|
|
2596
2744
|
// Test-only handles: the render smoke test mounts these directly, because
|
|
2597
2745
|
// the export panel normally only appears after a click. Nothing else reads it.
|
|
2598
|
-
__smoke: { ExportPanel, MarkedText, PickButton, SyncBanner, clip },
|
|
2746
|
+
__smoke: { ExportPanel, MarkedText, PickButton, SyncBanner, clip, ThoughtRow, THOUGHT_CLAMP_CHARS },
|
|
2599
2747
|
}
|
|
2600
2748
|
},
|
|
2601
2749
|
})
|