dsh-weread-gather 1.1.2 → 1.1.4
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 +205 -55
- 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{position:relative;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}
|
|
@@ -607,10 +634,12 @@ window.__ModuleLoader__.load({
|
|
|
607
634
|
.wr-copy:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}
|
|
608
635
|
.wr-copy[data-done=true]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}
|
|
609
636
|
.wr-pager{display:flex;align-items:center;justify-content:center;gap:12px;margin-top:16px;font-size:12px;flex-wrap:wrap}
|
|
610
|
-
.wr-pick{position:absolute;right:
|
|
611
|
-
.wr-quote:hover .wr-pick,.wr-thought:hover .wr-pick,.wr-pick[data-on=true]{opacity:1}
|
|
612
|
-
.wr-pick
|
|
613
|
-
.wr-
|
|
637
|
+
.wr-pick{position:absolute;right:9px;top:9px;width:18px;height:18px;border-radius:50%;border:1px solid color-mix(in srgb, var(--wr-moss) 55%, transparent);background:var(--wr-paper-soft);color:transparent;font-size:11px;line-height:1;display:grid;place-items:center;cursor:pointer;padding:0;opacity:0;transition:opacity .12s ease,border-color .12s ease,background .12s ease;z-index:2}
|
|
638
|
+
.wr-quote:hover .wr-pick,.wr-thought:hover .wr-pick,.wr-pick:focus-visible,.wr-pick[data-on=true]{opacity:1}
|
|
639
|
+
.wr-pick:hover{border-color:var(--wr-moss)}
|
|
640
|
+
@media (hover:none){.wr-pick{opacity:1}}
|
|
641
|
+
.wr-pick[data-on=true]{background:var(--wr-moss);border-color:var(--wr-moss);color:#fff}
|
|
642
|
+
.wr-quote[data-picked=true],.wr-thought[data-picked=true]{border-color:var(--wr-moss);box-shadow:0 0 0 2px color-mix(in srgb, var(--wr-moss) 16%, transparent)}
|
|
614
643
|
.wr-mark{background:var(--wr-heat-1);color:var(--wr-ink);border-radius:3px;padding:0 2px}
|
|
615
644
|
.wr-export{width:min(760px,94vw);max-height:88vh;display:flex;flex-direction:column}
|
|
616
645
|
.wr-export-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
|
|
@@ -618,7 +647,7 @@ window.__ModuleLoader__.load({
|
|
|
618
647
|
.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
648
|
.wr-export-label{display:flex;justify-content:space-between;gap:10px;font-size:11px;color:var(--dsw-alias-label-secondary);margin-bottom:6px}
|
|
620
649
|
.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(--
|
|
650
|
+
.wr-stars{color:var(--wr-clay);font-size:10.5px;letter-spacing:1px}
|
|
622
651
|
.wr-detail-head{display:flex;gap:18px;align-items:flex-start;flex-wrap:wrap}
|
|
623
652
|
.wr-detail-cover{width:120px;flex:none;border-radius:12px;overflow:hidden;box-shadow:0 10px 24px -12px rgba(38,60,52,.55)}
|
|
624
653
|
.wr-detail-main{flex:1;min-width:220px;display:flex;flex-direction:column;gap:8px}
|
|
@@ -662,7 +691,7 @@ window.__ModuleLoader__.load({
|
|
|
662
691
|
'div',
|
|
663
692
|
{ className: 'wr-cover' },
|
|
664
693
|
book.cover && !failed
|
|
665
|
-
? h('img', { src: book.cover, alt: book.title || '', loading: 'lazy', referrerPolicy: 'no-referrer', onError: () => setFailed(true) })
|
|
694
|
+
? h('img', { src: book.cover, alt: book.title || '', loading: 'lazy', decoding: 'async', referrerPolicy: 'no-referrer', onError: () => setFailed(true) })
|
|
666
695
|
: h('div', { className: 'wr-cover-fallback' }, String(book.title || '书').slice(0, 1)),
|
|
667
696
|
)
|
|
668
697
|
}
|
|
@@ -860,9 +889,12 @@ window.__ModuleLoader__.load({
|
|
|
860
889
|
}
|
|
861
890
|
return 0
|
|
862
891
|
}
|
|
892
|
+
// Monthly ticks would crowd the ~53 columns, so only quarter starts get a
|
|
893
|
+
// label — enough to orient without turning the axis into a wall of text.
|
|
894
|
+
const labelledMonths = [1, 4, 7, 10]
|
|
863
895
|
const ticks = model.columns.map((column, index) => {
|
|
864
896
|
const month = monthOf(column)
|
|
865
|
-
if (month === 0) return ''
|
|
897
|
+
if (month === 0 || !labelledMonths.includes(month)) return ''
|
|
866
898
|
for (let back = index - 1; back >= 0; back -= 1) {
|
|
867
899
|
const previous = monthOf(model.columns[back])
|
|
868
900
|
if (previous > 0) return previous === month ? '' : monthLabel(month - 1)
|
|
@@ -893,8 +925,8 @@ window.__ModuleLoader__.load({
|
|
|
893
925
|
cells.push(h('div', { key: `e-${columnIndex}-${row}`, className: 'wr-heat-cell', style: { background: 'transparent' } }))
|
|
894
926
|
continue
|
|
895
927
|
}
|
|
896
|
-
// Days after today are
|
|
897
|
-
//
|
|
928
|
+
// Days after today are left blank: an outline still reads as a row of
|
|
929
|
+
// dots, and "not yet" needs no decoration at all.
|
|
898
930
|
const future = cell.iso > todayIso
|
|
899
931
|
cells.push(
|
|
900
932
|
h('div', {
|
|
@@ -903,7 +935,7 @@ window.__ModuleLoader__.load({
|
|
|
903
935
|
'data-on': cell.value > 0 ? 'true' : 'false',
|
|
904
936
|
'data-future': future ? 'true' : 'false',
|
|
905
937
|
title: future
|
|
906
|
-
?
|
|
938
|
+
? null
|
|
907
939
|
: `${cell.iso} · ${cell.value > 0 ? fmtDuration(cell.value) : t('dash.heat.none')}`,
|
|
908
940
|
style: { background: future ? 'transparent' : level(cell.value) },
|
|
909
941
|
}),
|
|
@@ -914,14 +946,6 @@ window.__ModuleLoader__.load({
|
|
|
914
946
|
return h(
|
|
915
947
|
'div',
|
|
916
948
|
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
949
|
h(
|
|
926
950
|
'div',
|
|
927
951
|
{ className: 'wr-heat-wrap' },
|
|
@@ -930,18 +954,36 @@ window.__ModuleLoader__.load({
|
|
|
930
954
|
'div',
|
|
931
955
|
{ style: { minWidth: 0 } },
|
|
932
956
|
h('div', { className: 'wr-heat-months' }, ticks.map((label, index) => h('span', { key: index }, label))),
|
|
933
|
-
h(
|
|
957
|
+
h(
|
|
958
|
+
'div',
|
|
959
|
+
{
|
|
960
|
+
className: 'wr-heat',
|
|
961
|
+
title: model.bestDate === '' ? null : t('dash.heat.best', { date: model.bestDate, time: fmtDuration(model.best) }),
|
|
962
|
+
},
|
|
963
|
+
cells,
|
|
964
|
+
),
|
|
934
965
|
),
|
|
935
966
|
),
|
|
967
|
+
// The numbers live under the grid and stay to one line: above it they
|
|
968
|
+
// pushed the calendar away from the card title.
|
|
936
969
|
h(
|
|
937
970
|
'div',
|
|
938
|
-
{ className: 'wr-heat-
|
|
939
|
-
h(
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
h('span', {
|
|
944
|
-
|
|
971
|
+
{ className: 'wr-heat-foot' },
|
|
972
|
+
h(
|
|
973
|
+
'div',
|
|
974
|
+
{ className: 'wr-heat-legend' },
|
|
975
|
+
h('span', null, t('dash.heat.none')),
|
|
976
|
+
h('span', { className: 'wr-heat-cell' }),
|
|
977
|
+
h('span', { style: { marginLeft: '6px' } }, t('dash.less')),
|
|
978
|
+
['var(--wr-heat-1)', 'var(--wr-heat-2)', 'var(--wr-heat-3)', 'var(--wr-heat-4)'].map((color, index) =>
|
|
979
|
+
h('span', { key: index, className: 'wr-heat-cell', style: { background: color } })),
|
|
980
|
+
h('span', null, t('dash.more')),
|
|
981
|
+
),
|
|
982
|
+
h(
|
|
983
|
+
'div',
|
|
984
|
+
{ className: 'wr-heat-foot-stats' },
|
|
985
|
+
`${t('dash.heat.active', { days: model.active })} · ${t('dash.heat.total', { time: fmtDuration(model.total) })}`,
|
|
986
|
+
),
|
|
945
987
|
),
|
|
946
988
|
)
|
|
947
989
|
}
|
|
@@ -987,16 +1029,22 @@ window.__ModuleLoader__.load({
|
|
|
987
1029
|
function MonthChart(props) {
|
|
988
1030
|
const values = Array.isArray(props.values) ? props.values : []
|
|
989
1031
|
const activeIndex = Number.isInteger(props.activeIndex) ? props.activeIndex : -1
|
|
1032
|
+
const year = Number(props.year) || new Date().getFullYear()
|
|
1033
|
+
const now = new Date()
|
|
1034
|
+
// Months that have not happened yet are drawn in the track colour instead
|
|
1035
|
+
// of vanishing, so a partial year reads as "not yet" rather than "no data".
|
|
1036
|
+
const futureFrom = year === now.getFullYear() ? now.getMonth() + 1 : year > now.getFullYear() ? 0 : 13
|
|
990
1037
|
const max = Math.max.apply(null, values.concat([1]))
|
|
991
1038
|
const cells = []
|
|
992
1039
|
for (let index = 0; index < 12; index += 1) {
|
|
993
1040
|
const value = values[index] || 0
|
|
994
|
-
const
|
|
1041
|
+
const future = index >= futureFrom
|
|
1042
|
+
const height = max <= 0 || future ? 0 : Math.max(value > 0 ? 4 : 0, Math.round((value / max) * 100))
|
|
995
1043
|
cells.push(
|
|
996
1044
|
h(
|
|
997
1045
|
'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}%` } }),
|
|
1046
|
+
{ className: 'wr-hour-col', key: index, title: future ? `${index + 1} · ${t('dash.heat.future')}` : `${index + 1} · ${fmtDuration(value)}` },
|
|
1047
|
+
h('div', { className: 'wr-hour-bar', 'data-active': index === activeIndex ? 'true' : 'false', 'data-future': future ? 'true' : 'false', style: { height: future ? '6%' : `${height}%` } }),
|
|
1000
1048
|
h('div', { className: 'wr-hour-label', 'data-active': index === activeIndex ? 'true' : 'false' }, String(index + 1)),
|
|
1001
1049
|
),
|
|
1002
1050
|
)
|
|
@@ -1196,7 +1244,7 @@ window.__ModuleLoader__.load({
|
|
|
1196
1244
|
h(
|
|
1197
1245
|
'div',
|
|
1198
1246
|
{ 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 })),
|
|
1247
|
+
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
1248
|
h('div', { className: 'wr-card' }, h('h3', { className: 'wr-section-title' }, t('dash.category')), h(BarList, { items: categories })),
|
|
1201
1249
|
),
|
|
1202
1250
|
h(
|
|
@@ -1487,15 +1535,42 @@ window.__ModuleLoader__.load({
|
|
|
1487
1535
|
)
|
|
1488
1536
|
}
|
|
1489
1537
|
|
|
1538
|
+
// A thought is the reader's own voice, so the card gives the text room:
|
|
1539
|
+
// wider line height, no clamped source label, and the left rail is a quiet
|
|
1540
|
+
// sage hairline instead of an accent bar. In the wall view the card has no
|
|
1541
|
+
// border at all and relies on spacing.
|
|
1542
|
+
const THOUGHT_CLAMP_CHARS = 220
|
|
1543
|
+
|
|
1490
1544
|
function ThoughtRow(props) {
|
|
1491
1545
|
const thought = props.thought
|
|
1492
1546
|
const open = typeof props.onOpen === 'function' ? props.onOpen : null
|
|
1493
1547
|
const pick = typeof props.onToggle === 'function' ? h(PickButton, { on: Boolean(props.selected), onChange: props.onToggle }) : null
|
|
1548
|
+
const content = String(thought.content || '')
|
|
1549
|
+
const clampable = Boolean(props.wall) && content.length > THOUGHT_CLAMP_CHARS
|
|
1550
|
+
const [expanded, setExpanded] = useState(false)
|
|
1551
|
+
const [threadOpen, setThreadOpen] = useState(false)
|
|
1552
|
+
const context = Array.isArray(props.context) ? props.context : []
|
|
1553
|
+
const slice = context.slice(0, 6)
|
|
1554
|
+
const showClamped = clampable && !expanded
|
|
1494
1555
|
return h(
|
|
1495
1556
|
'div',
|
|
1496
1557
|
{ className: 'wr-thought', 'data-clickable': open ? 'true' : 'false', 'data-picked': props.selected ? 'true' : 'false', title: open ? t('action.openBook') : undefined, onClick: open || undefined },
|
|
1497
1558
|
pick,
|
|
1498
|
-
h(MarkedText, { className: 'wr-quote-text', text:
|
|
1559
|
+
h(MarkedText, { className: showClamped ? 'wr-quote-text wr-clamp' : 'wr-quote-text', text: content, needle: props.needle }),
|
|
1560
|
+
clampable
|
|
1561
|
+
? h(
|
|
1562
|
+
'button',
|
|
1563
|
+
{
|
|
1564
|
+
className: 'wr-more',
|
|
1565
|
+
type: 'button',
|
|
1566
|
+
onClick: (event) => {
|
|
1567
|
+
if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
1568
|
+
setExpanded(!expanded)
|
|
1569
|
+
},
|
|
1570
|
+
},
|
|
1571
|
+
expanded ? t('thought.collapse') : t('thought.expand', { count: String(content.length - THOUGHT_CLAMP_CHARS) }),
|
|
1572
|
+
)
|
|
1573
|
+
: null,
|
|
1499
1574
|
h(
|
|
1500
1575
|
'div',
|
|
1501
1576
|
{ className: 'wr-quote-foot' },
|
|
@@ -1506,9 +1581,38 @@ window.__ModuleLoader__.load({
|
|
|
1506
1581
|
{ className: 'wr-quote-tail' },
|
|
1507
1582
|
thought.star >= 0 ? h('span', { className: 'wr-stars' }, '★'.repeat(Math.min(5, Math.max(0, thought.star)))) : null,
|
|
1508
1583
|
h('span', null, fmtDate(thought.createTime)),
|
|
1509
|
-
h(CopyButton, { text:
|
|
1584
|
+
h(CopyButton, { text: content }),
|
|
1510
1585
|
),
|
|
1511
1586
|
),
|
|
1587
|
+
// WeRead does not link a thought to the exact highlight it answers, so
|
|
1588
|
+
// this is chapter-level context only, labelled as such.
|
|
1589
|
+
slice.length === 0
|
|
1590
|
+
? null
|
|
1591
|
+
: threadOpen
|
|
1592
|
+
? h(
|
|
1593
|
+
'div',
|
|
1594
|
+
{ className: 'wr-thread' },
|
|
1595
|
+
h('div', { className: 'wr-thread-title' }, t('thought.contextTitle')),
|
|
1596
|
+
slice.map((note) =>
|
|
1597
|
+
h(
|
|
1598
|
+
'div',
|
|
1599
|
+
{ className: 'wr-thread-item', key: note.id },
|
|
1600
|
+
`> ${String(note.text || '').slice(0, 300)}`,
|
|
1601
|
+
h('span', { className: 'wr-thread-date' }, fmtDate(note.createTime)),
|
|
1602
|
+
)),
|
|
1603
|
+
)
|
|
1604
|
+
: h(
|
|
1605
|
+
'button',
|
|
1606
|
+
{
|
|
1607
|
+
className: 'wr-thread-toggle',
|
|
1608
|
+
type: 'button',
|
|
1609
|
+
onClick: (event) => {
|
|
1610
|
+
if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
1611
|
+
setThreadOpen(true)
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1614
|
+
t('thought.contextToggle', { count: String(context.length) }),
|
|
1615
|
+
),
|
|
1512
1616
|
)
|
|
1513
1617
|
}
|
|
1514
1618
|
|
|
@@ -1711,6 +1815,20 @@ window.__ModuleLoader__.load({
|
|
|
1711
1815
|
})
|
|
1712
1816
|
}, [source, query, bookFilter, kind])
|
|
1713
1817
|
|
|
1818
|
+
// Thoughts carry a chapter name, highlights a chapter title, and WeRead
|
|
1819
|
+
// does not link them; chapter-level context is the only honest pairing.
|
|
1820
|
+
const chapterIndex = useMemo(() => {
|
|
1821
|
+
const map = new Map()
|
|
1822
|
+
for (const note of data.notes || []) {
|
|
1823
|
+
const key = `${note.bookId}|${note.chapterTitle || ''}`
|
|
1824
|
+
const bucket = map.get(key)
|
|
1825
|
+
if (bucket) bucket.push(note)
|
|
1826
|
+
else map.set(key, [note])
|
|
1827
|
+
}
|
|
1828
|
+
return map
|
|
1829
|
+
}, [data.notes])
|
|
1830
|
+
const contextOf = (thought) => (thought.chapterName ? chapterIndex.get(`${thought.bookId}|${thought.chapterName}`) || [] : [])
|
|
1831
|
+
|
|
1714
1832
|
useEffect(() => {
|
|
1715
1833
|
setPage(0)
|
|
1716
1834
|
setSelected(new Set())
|
|
@@ -1814,7 +1932,16 @@ window.__ModuleLoader__.load({
|
|
|
1814
1932
|
})
|
|
1815
1933
|
return kind === 'note'
|
|
1816
1934
|
? 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, {
|
|
1935
|
+
: h(ThoughtRow, {
|
|
1936
|
+
key: item.id,
|
|
1937
|
+
thought: item,
|
|
1938
|
+
needle: query.trim(),
|
|
1939
|
+
wall: mode === 'wall',
|
|
1940
|
+
context: contextOf(item),
|
|
1941
|
+
selected: selected.has(item.id),
|
|
1942
|
+
onToggle: pick,
|
|
1943
|
+
onOpen: props.onOpenBook ? () => props.onOpenBook(item.bookId, kind) : null,
|
|
1944
|
+
})
|
|
1818
1945
|
}),
|
|
1819
1946
|
),
|
|
1820
1947
|
filtered.length > pageSize
|
|
@@ -1840,6 +1967,19 @@ window.__ModuleLoader__.load({
|
|
|
1840
1967
|
|
|
1841
1968
|
const notes = useMemo(() => (data.notes || []).filter((note) => note.bookId === book.bookId), [data.notes, book.bookId])
|
|
1842
1969
|
const thoughts = useMemo(() => (data.thoughts || []).filter((thought) => thought.bookId === book.bookId), [data.thoughts, book.bookId])
|
|
1970
|
+
// Highlights grouped by chapter: the only honest context a thought can
|
|
1971
|
+
// show, because WeRead never links a thought to one specific highlight.
|
|
1972
|
+
const chapterIndex = useMemo(() => {
|
|
1973
|
+
const map = new Map()
|
|
1974
|
+
for (const note of notes) {
|
|
1975
|
+
const key = note.chapterTitle || ''
|
|
1976
|
+
if (key === '') continue
|
|
1977
|
+
const bucket = map.get(key)
|
|
1978
|
+
if (bucket) bucket.push(note)
|
|
1979
|
+
else map.set(key, [note])
|
|
1980
|
+
}
|
|
1981
|
+
return map
|
|
1982
|
+
}, [notes])
|
|
1843
1983
|
const needle = query.trim().toLowerCase()
|
|
1844
1984
|
const visibleNotes = needle === '' ? notes : notes.filter((note) => contains(note.text, needle) || contains(note.chapterTitle, needle))
|
|
1845
1985
|
const visibleThoughts = needle === '' ? thoughts : thoughts.filter((thought) => contains(thought.content, needle) || contains(thought.chapterName, needle))
|
|
@@ -1911,7 +2051,9 @@ window.__ModuleLoader__.load({
|
|
|
1911
2051
|
'div',
|
|
1912
2052
|
{ className: 'wr-cols' },
|
|
1913
2053
|
pageItems.map((item) =>
|
|
1914
|
-
tab === 'notes'
|
|
2054
|
+
tab === 'notes'
|
|
2055
|
+
? h(NoteRow, { key: item.id, note: item, needle: needle })
|
|
2056
|
+
: h(ThoughtRow, { key: item.id, thought: item, needle: needle, wall: true, context: chapterIndex.get(item.chapterName || '') || [] }),
|
|
1915
2057
|
),
|
|
1916
2058
|
),
|
|
1917
2059
|
activeList.length > pageSize
|
|
@@ -1998,7 +2140,7 @@ window.__ModuleLoader__.load({
|
|
|
1998
2140
|
'div',
|
|
1999
2141
|
{ className: 'wr-section' },
|
|
2000
2142
|
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') }))),
|
|
2143
|
+
h('div', { className: 'wr-cols' }, groups.thoughts.map((thought) => h(ThoughtRow, { key: thought.id, thought, needle, wall: true, onOpen: () => openBookOf(thought.bookId, 'thoughts') }))),
|
|
2002
2144
|
)
|
|
2003
2145
|
: null,
|
|
2004
2146
|
)
|
|
@@ -2489,9 +2631,17 @@ window.__ModuleLoader__.load({
|
|
|
2489
2631
|
body = h(Overview, { data: data || {} })
|
|
2490
2632
|
}
|
|
2491
2633
|
|
|
2634
|
+
// Each room gets its own paper tint, and the tint follows the tab you are
|
|
2635
|
+
// standing in — a book's thought tab keeps the thought-room warmth.
|
|
2636
|
+
const room = query.trim() !== ''
|
|
2637
|
+
? 'search'
|
|
2638
|
+
: view === 'book'
|
|
2639
|
+
? (bookTab === 'thoughts' ? 'thoughts' : 'notes')
|
|
2640
|
+
: view
|
|
2641
|
+
|
|
2492
2642
|
return h(
|
|
2493
2643
|
'div',
|
|
2494
|
-
{ className: 'wr-root', 'data-dsh-weread-gather': '' },
|
|
2644
|
+
{ className: 'wr-root', 'data-view': room, 'data-dsh-weread-gather': '' },
|
|
2495
2645
|
h('style', { dangerouslySetInnerHTML: { __html: CSS } }),
|
|
2496
2646
|
head,
|
|
2497
2647
|
h(
|
|
@@ -2595,7 +2745,7 @@ window.__ModuleLoader__.load({
|
|
|
2595
2745
|
},
|
|
2596
2746
|
// Test-only handles: the render smoke test mounts these directly, because
|
|
2597
2747
|
// the export panel normally only appears after a click. Nothing else reads it.
|
|
2598
|
-
__smoke: { ExportPanel, MarkedText, PickButton, SyncBanner, clip },
|
|
2748
|
+
__smoke: { ExportPanel, MarkedText, PickButton, SyncBanner, clip, ThoughtRow, THOUGHT_CLAMP_CHARS },
|
|
2599
2749
|
}
|
|
2600
2750
|
},
|
|
2601
2751
|
})
|