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.
Files changed (2) hide show
  1. package/client.js +205 -55
  2. 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:#EDF2EF;--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}
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;box-shadow:0 1px 2px rgba(38,60,52,.04)}
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;box-shadow:0 1px 2px rgba(38,60,52,.03)}
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 3px 8px rgba(38,60,52,.10),0 14px 26px -18px rgba(38,60,52,.5);transition:transform .2s ease,box-shadow .2s ease}
531
- .wr-book-cover-wrap{position:relative;border-radius:8px;overflow:hidden;box-shadow:0 3px 8px rgba(38,60,52,.10),0 14px 26px -18px rgba(38,60,52,.5);transition:transform .2s ease,box-shadow .2s ease}
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:3px 3px 0 0;background:var(--wr-sage);min-height:2px}
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,13px);gap:3px;flex:none;font-size:9.5px;color:var(--dsw-alias-label-secondary);align-items:center}
562
- .wr-heat-months{display:grid;grid-auto-flow:column;grid-auto-columns:13px;gap:3px;font-size:9.5px;color:var(--dsw-alias-label-secondary);overflow:hidden;margin:0 0 4px 0}
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,13px);gap:3px;flex:none}
565
- .wr-heat-cell{width:13px;height:13px;border-radius:3px;background:var(--wr-heat-0);transition:transform .12s ease}
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;box-shadow:inset 0 0 0 1px var(--wr-line-2);opacity:.7}
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;margin-top:10px;font-size:11px;color:var(--dsw-alias-label-secondary)}
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;box-shadow:0 1px 2px rgba(38,60,52,.04)}
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:3px solid var(--wr-progress);border-radius:14px;padding:14px 16px;box-shadow:0 1px 2px rgba(38,60,52,.04)}
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:8px;top:8px;width:16px;height:16px;border-radius:50%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:transparent;font-size:10px;line-height:1;display:grid;place-items:center;cursor:pointer;padding:0;opacity:0;transition:opacity .12s ease;z-index:1}
611
- .wr-quote:hover .wr-pick,.wr-thought:hover .wr-pick,.wr-pick[data-on=true]{opacity:1}
612
- .wr-pick[data-on=true]{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-base)}
613
- .wr-quote[data-picked=true],.wr-thought[data-picked=true]{border-color:var(--dsw-alias-brand-primary)}
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(--dsw-alias-state-warn-primary);font-size:10.5px;letter-spacing:1px}
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 drawn as empty outlines, so "not yet" never
897
- // reads as "read nothing".
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
- ? `${cell.iso} · ${t('dash.heat.future')}`
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('div', { className: 'wr-heat' }, cells),
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-legend' },
939
- h('span', null, t('dash.heat.none')),
940
- h('span', { className: 'wr-heat-cell' }),
941
- h('span', { style: { marginLeft: '6px' } }, t('dash.less')),
942
- ['var(--wr-heat-1)', 'var(--wr-heat-2)', 'var(--wr-heat-3)', 'var(--wr-heat-4)'].map((color, index) =>
943
- h('span', { key: index, className: 'wr-heat-cell', style: { background: color } })),
944
- h('span', null, t('dash.more')),
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 height = max <= 0 ? 0 : Math.max(value > 0 ? 4 : 0, Math.round((value / max) * 100))
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: thought.content, needle: props.needle }),
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: thought.content }),
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, { key: item.id, thought: item, needle: query.trim(), selected: selected.has(item.id), onToggle: pick, onOpen: props.onOpenBook ? () => props.onOpenBook(item.bookId, kind) : null })
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' ? h(NoteRow, { key: item.id, note: item, needle: needle }) : h(ThoughtRow, { key: item.id, thought: item, needle: needle }),
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
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-weread-gather",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "数字书房 — 微信读书书架、笔记与想法的私人书房:Markdown 导出、备份恢复、增量同步。",
5
5
  "type": "module",
6
6
  "license": "MIT",