daily-question 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -117
- package/client.js +545 -322
- package/lib/exam-dates.js +10 -8
- package/lib/generate.js +3 -3
- package/lib/routes.js +1 -0
- package/lib/service.js +58 -14
- package/lib/store.js +3 -3
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -133,7 +133,7 @@ window.__ModuleLoader__.load({
|
|
|
133
133
|
window.MathJax = {
|
|
134
134
|
startup: { typeset: false },
|
|
135
135
|
// 每条公式自带 <defs>,注入 innerHTML 后不依赖全局字体缓存
|
|
136
|
-
svg: { fontCache: 'local' },
|
|
136
|
+
svg: { fontCache: 'local', scale: 1, matchFontHeight: false },
|
|
137
137
|
}
|
|
138
138
|
const script = document.createElement('script')
|
|
139
139
|
script.src = MATHJAX_SRC
|
|
@@ -543,239 +543,273 @@ window.__ModuleLoader__.load({
|
|
|
543
543
|
/** 由 apply 用 styles.insert 注入时置位;否则由组件渲染 <style> 兜底。 */
|
|
544
544
|
let styleInjected = false
|
|
545
545
|
const CSS = `
|
|
546
|
-
.kd-page {
|
|
547
|
-
|
|
546
|
+
.kd-page {
|
|
547
|
+
--kd-bg-base:var(--dsw-alias-bg-base, #ffffff);
|
|
548
|
+
--kd-bg-layer-1:var(--dsw-alias-bg-layer-1, #ffffff);
|
|
549
|
+
--kd-bg-layer-2:color-mix(in srgb, var(--kd-ink) 5%, var(--kd-surface));
|
|
550
|
+
--kd-bg-overlay:var(--dsw-alias-bg-overlay, #ffffff);
|
|
551
|
+
--kd-label-primary:var(--dsw-alias-label-primary, #24332f);
|
|
552
|
+
--kd-label-secondary:var(--dsw-alias-label-secondary, #66736e);
|
|
553
|
+
--kd-border-l1:color-mix(in srgb, var(--kd-ink) 9%, var(--kd-surface));
|
|
554
|
+
--kd-border-l2:color-mix(in srgb, var(--kd-ink) 17%, var(--kd-surface));
|
|
555
|
+
--kd-brand-primary:#4d6bfe;
|
|
556
|
+
--kd-state-success-primary:#078443;
|
|
557
|
+
--kd-state-error-primary:#db3144;
|
|
558
|
+
--kd-state-warn-primary:#95702b;
|
|
559
|
+
--kd-state-idle-primary:#85918b;
|
|
560
|
+
--kd-ink:var(--kd-label-primary); --kd-surface:var(--kd-bg-base);
|
|
561
|
+
font-family:inherit; isolation:isolate; height:100%; min-height:0; display:flex; flex-direction:column; position:relative;
|
|
562
|
+
background:color-mix(in srgb, var(--kd-brand-primary) 2%, var(--kd-surface)); color: var(--kd-label-primary);
|
|
548
563
|
font-size:14px; line-height:1.65; -webkit-font-smoothing:antialiased; box-sizing:border-box; }
|
|
549
564
|
.kd-page *, .kd-page *::before, .kd-page *::after { box-sizing:border-box; }
|
|
550
|
-
.kd-page :focus-visible { outline:2px solid var(--
|
|
551
|
-
.kd-header { padding:
|
|
565
|
+
.kd-page :focus-visible { outline:2px solid var(--kd-brand-primary); outline-offset:2px; }
|
|
566
|
+
.kd-header { padding:20px 28px 12px; background:var(--kd-surface); border-bottom:1px solid var(--kd-border-l1);
|
|
552
567
|
display:flex; flex-direction:column; gap:12px; }
|
|
553
|
-
.kd-header-top { display:flex; gap:
|
|
554
|
-
.kd-header-left { min-width:0; flex:1 1 320px; display:flex; gap:12px; align-items:
|
|
555
|
-
.kd-brand { flex:0 0
|
|
556
|
-
justify-content:center; border:
|
|
557
|
-
color:var(--
|
|
558
|
-
.kd-title { margin:0; font-size:
|
|
559
|
-
.kd-sub { margin-top:3px; font-size:12px; color:var(--
|
|
568
|
+
.kd-header-top { display:flex; gap:20px; justify-content:space-between; align-items:center; flex-wrap:wrap; }
|
|
569
|
+
.kd-header-left { min-width:0; flex:1 1 320px; display:flex; gap:12px; align-items:center; }
|
|
570
|
+
.kd-brand { flex:0 0 40px; width:40px; height:40px; border-radius:12px; display:flex; align-items:center;
|
|
571
|
+
justify-content:center; border:0; background:color-mix(in srgb, var(--kd-brand-primary) 10%, var(--kd-surface));
|
|
572
|
+
color:var(--kd-brand-primary); }
|
|
573
|
+
.kd-title { margin:0; font-size:19px; font-weight:650; letter-spacing:.01em; line-height:1.4; }
|
|
574
|
+
.kd-sub { margin-top:3px; font-size:12px; color:var(--kd-label-secondary); line-height:1.6; }
|
|
560
575
|
.kd-select { height:30px; padding:0 10px; border-radius:8px; font-size:13px; font-family:inherit;
|
|
561
|
-
border:1px solid var(--
|
|
562
|
-
color:var(--
|
|
563
|
-
.kd-select:hover { border-color:var(--
|
|
576
|
+
border:1px solid var(--kd-border-l2); background:var(--kd-bg-layer-1);
|
|
577
|
+
color:var(--kd-label-primary); transition:border-color .15s; }
|
|
578
|
+
.kd-select:hover { border-color:var(--kd-brand-primary); }
|
|
564
579
|
.kd-select-sm { height:26px; font-size:12px; padding:0 6px; }
|
|
565
580
|
.kd-cd-lines { min-width:0; flex:0 1 auto; display:flex; flex-direction:column; align-items:flex-end;
|
|
566
581
|
gap:2px; text-align:right; }
|
|
567
|
-
.kd-cd-primary { font-size:
|
|
568
|
-
.kd-cd-secondary { font-size:12px; color:var(--
|
|
569
|
-
.kd-cd-target { font-size:
|
|
570
|
-
.kd-exam-strip { display:flex; align-items:center; gap:6px 14px; flex-wrap:wrap; padding:8px
|
|
571
|
-
border:1px solid var(--
|
|
572
|
-
font-size:11.5px; color:var(--
|
|
582
|
+
.kd-cd-primary { font-size:21px; font-weight:650; color:var(--kd-brand-primary); font-variant-numeric:tabular-nums; letter-spacing:.01em; line-height:1.25; }
|
|
583
|
+
.kd-cd-secondary { font-size:12px; color:var(--kd-label-secondary); }
|
|
584
|
+
.kd-cd-target { font-size:11px; font-weight:400; color:var(--kd-label-secondary); }
|
|
585
|
+
.kd-exam-strip { display:flex; align-items:center; gap:6px 14px; flex-wrap:wrap; padding:8px 0 0;
|
|
586
|
+
border:0; border-top:1px solid var(--kd-border-l1); background:transparent;
|
|
587
|
+
font-size:11.5px; color:var(--kd-label-secondary); }
|
|
573
588
|
.kd-strip-item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
|
|
574
|
-
.kd-strip-warn { color:var(--
|
|
589
|
+
.kd-strip-warn { color:var(--kd-state-warn-primary); white-space:normal; text-align:left; }
|
|
575
590
|
.kd-shell { flex:1 1 auto; min-height:0; display:flex; }
|
|
576
|
-
.kd-nav { width:
|
|
577
|
-
padding:
|
|
591
|
+
.kd-nav { width:204px; flex:0 0 204px; background:var(--kd-surface); border-right:1px solid var(--kd-border-l1);
|
|
592
|
+
padding:24px 14px; overflow:auto; display:flex; flex-direction:column; gap:18px; }
|
|
578
593
|
.kd-nav-group { display:flex; flex-direction:column; gap:4px; }
|
|
579
|
-
.kd-nav-group-label { font-size:10.5px; letter-spacing:.1em; color:var(--
|
|
594
|
+
.kd-nav-group-label { font-size:10.5px; letter-spacing:.1em; color:var(--kd-label-secondary);
|
|
580
595
|
padding:0 8px 4px; }
|
|
581
|
-
.kd-nav-item { position:relative; display:flex; align-items:center; justify-content:space-between; gap:
|
|
582
|
-
width:100%; padding:
|
|
583
|
-
cursor:pointer; color:var(--
|
|
596
|
+
.kd-nav-item { position:relative; display:flex; align-items:center; justify-content:space-between; gap:7px; flex-direction:column; align-items:flex-start;
|
|
597
|
+
width:100%; padding:12px; border-radius:9px; border:1px solid transparent; background:transparent;
|
|
598
|
+
cursor:pointer; color:var(--kd-label-primary); font:inherit; font-size:13px; text-align:left;
|
|
584
599
|
transition:background-color .15s, border-color .15s, color .15s; }
|
|
585
|
-
.kd-nav-item:hover { background:var(--
|
|
586
|
-
.kd-nav-item > span:first-child { min-width:0; overflow
|
|
587
|
-
.kd-nav-item.is-active { background:var(--
|
|
588
|
-
|
|
600
|
+
.kd-nav-item:hover { background:var(--kd-bg-layer-2); }
|
|
601
|
+
.kd-nav-item > span:first-child { min-width:0; overflow-wrap:anywhere; line-height:1.6; }
|
|
602
|
+
.kd-nav-item.is-active { background:color-mix(in srgb, var(--kd-brand-primary) 9%, var(--kd-surface)); border-color:transparent;
|
|
603
|
+
color:var(--kd-brand-primary); font-weight:600; }
|
|
589
604
|
.kd-nav-item .kd-nav-count { flex:0 0 auto; white-space:nowrap; font-size:11px;
|
|
590
|
-
color:var(--
|
|
605
|
+
color:var(--kd-label-secondary); }
|
|
591
606
|
.kd-nav-status { flex:0 0 auto; display:inline-flex; align-items:center; gap:4px; white-space:nowrap;
|
|
592
|
-
font-size:10.5px; color:var(--
|
|
593
|
-
.kd-dot { width:6px; height:6px; border-radius:50%; background:var(--
|
|
594
|
-
.kd-dot-idle { background:var(--
|
|
607
|
+
font-size:10.5px; color:var(--kd-label-secondary); }
|
|
608
|
+
.kd-dot { width:6px; height:6px; border-radius:50%; background:var(--kd-state-success-primary); }
|
|
609
|
+
.kd-dot-idle { background:var(--kd-state-idle-primary); }
|
|
595
610
|
.kd-nav-spacer { flex:1 1 auto; }
|
|
596
|
-
.kd-main { flex:1 1 auto; min-width:0; overflow:
|
|
597
|
-
.kd-main-
|
|
598
|
-
.kd-
|
|
599
|
-
|
|
600
|
-
.kd-
|
|
611
|
+
.kd-main { flex:1 1 auto; min-width:0; overflow:hidden; display:flex; flex-direction:column; }
|
|
612
|
+
.kd-main-toolbar { flex:none; padding:24px 28px 20px; display:flex; align-items:center; gap:12px; }
|
|
613
|
+
.kd-main-toolbar > .kd-btn { flex:none; align-self:stretch; aspect-ratio:1; padding:0; min-height:0; white-space:nowrap; border-radius:12px; }
|
|
614
|
+
.kd-main-scroll { flex:1; min-height:0; overflow:auto; padding:0 28px 48px; scrollbar-gutter:stable; }
|
|
615
|
+
.kd-main-inner { width:100%; margin:0; }
|
|
616
|
+
.kd-thread { position:relative; width:clamp(280px,calc((100% - 204px) * var(--kd-thread-ratio, .33)),55%); flex:0 0 auto; border-left:1px solid var(--kd-border-l1);
|
|
617
|
+
display:flex; flex-direction:column; min-height:0; background:var(--kd-bg-layer-1); }
|
|
618
|
+
.kd-thread-resize { position:absolute; left:-5px; top:0; bottom:0; width:10px; cursor:col-resize; touch-action:none; z-index:2; }
|
|
619
|
+
.kd-thread-resize:hover, .kd-thread-resize:focus-visible { background:color-mix(in srgb,var(--kd-brand-primary) 20%,transparent); }
|
|
620
|
+
.kd-thread-head { padding:20px; border-bottom:1px solid var(--kd-border-l1);
|
|
601
621
|
display:flex; align-items:center; justify-content:space-between; gap:8px; }
|
|
602
622
|
.kd-thread-title { font-size:13px; font-weight:600; }
|
|
603
|
-
.kd-thread-hint { padding:
|
|
604
|
-
color:var(--
|
|
605
|
-
.kd-thread-scroll { flex:1 1 auto; min-height:0; overflow:auto; padding:
|
|
623
|
+
.kd-thread-hint { padding:12px 20px 0; font-size:11px; line-height:1.6;
|
|
624
|
+
color:var(--kd-label-secondary); }
|
|
625
|
+
.kd-thread-scroll { flex:1 1 auto; min-height:0; overflow:auto; padding:18px 20px; display:flex;
|
|
606
626
|
flex-direction:column; gap:12px; }
|
|
607
627
|
.kd-msg { max-width:100%; border-radius:12px; padding:10px 12px; font-size:13px; line-height:1.72; }
|
|
608
|
-
.kd-msg-user { align-self:flex-end; background:color-mix(in srgb, var(--
|
|
609
|
-
border:1px solid color-mix(in srgb, var(--
|
|
610
|
-
.kd-msg-assistant { align-self:stretch; border:
|
|
611
|
-
|
|
612
|
-
.kd-msg-
|
|
613
|
-
.kd-
|
|
614
|
-
.kd-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent); }
|
|
628
|
+
.kd-msg-user { align-self:flex-end; background:color-mix(in srgb, var(--kd-brand-primary) 9%, transparent);
|
|
629
|
+
border:1px solid color-mix(in srgb, var(--kd-brand-primary) 26%, transparent); }
|
|
630
|
+
.kd-msg-assistant { align-self:stretch; padding:0; border:0; background:transparent; }
|
|
631
|
+
.kd-msg-meta { font-size:10px; color:var(--kd-label-secondary); margin-top:6px; }
|
|
632
|
+
.kd-msg-failed { border-color:var(--kd-state-error-primary); }
|
|
633
|
+
.kd-thread-foot { border-top:1px solid var(--kd-border-l1); padding:12px 16px; }
|
|
634
|
+
.kd-composer { border:1px solid var(--kd-border-l2); border-radius:12px;
|
|
635
|
+
background:var(--kd-bg-base); padding:6px; transition:border-color .15s, box-shadow .15s; }
|
|
636
|
+
.kd-composer:focus-within { border-color:var(--kd-brand-primary);
|
|
637
|
+
box-shadow:0 0 0 3px color-mix(in srgb, var(--kd-brand-primary) 14%, transparent); }
|
|
619
638
|
.kd-textarea { width:100%; min-height:58px; max-height:180px; resize:vertical; padding:6px 8px;
|
|
620
|
-
border:0; background:transparent; color:var(--
|
|
639
|
+
border:0; background:transparent; color:var(--kd-label-primary); font:inherit; font-size:13px; }
|
|
621
640
|
.kd-textarea:focus { outline:none; }
|
|
622
|
-
.kd-tabs { display:flex; gap:
|
|
623
|
-
.kd-tab { position:relative; display:inline-flex; align-items:center; gap:6px; padding:
|
|
624
|
-
border:0; background:transparent; color:var(--
|
|
625
|
-
cursor:pointer; border-radius:8px
|
|
626
|
-
.kd-tab:hover { color:var(--
|
|
627
|
-
.kd-tab.is-active { color:var(--
|
|
628
|
-
|
|
629
|
-
border-radius:2px; background:var(--dsw-alias-brand-primary); }
|
|
641
|
+
.kd-tabs { display:flex; gap:4px; width:fit-content; max-width:100%; min-width:0; overflow-x:auto; padding:4px; border:1px solid var(--kd-border-l1); border-radius:12px; background:var(--kd-surface); }
|
|
642
|
+
.kd-tab { position:relative; display:inline-flex; align-items:center; gap:6px; padding:7px 14px; white-space:nowrap;
|
|
643
|
+
border:0; background:transparent; color:var(--kd-label-secondary); font:inherit; font-size:13px;
|
|
644
|
+
cursor:pointer; border-radius:8px; transition:color .15s, background-color .15s; }
|
|
645
|
+
.kd-tab:hover { color:var(--kd-label-primary); background:var(--kd-bg-layer-2); }
|
|
646
|
+
.kd-tab.is-active { color:var(--kd-brand-primary); font-weight:600; background:color-mix(in srgb, var(--kd-brand-primary) 10%, var(--kd-surface)); }
|
|
647
|
+
|
|
630
648
|
.kd-tab-count { min-width:17px; padding:0 5px; border-radius:999px; font-size:10.5px; line-height:17px;
|
|
631
|
-
text-align:center; background:var(--
|
|
632
|
-
.kd-tab.is-active .kd-tab-count { color:var(--
|
|
633
|
-
.kd-card { border:1px solid var(--
|
|
634
|
-
background:var(--
|
|
649
|
+
text-align:center; background:var(--kd-bg-layer-2); color:var(--kd-label-secondary); }
|
|
650
|
+
.kd-tab.is-active .kd-tab-count { color:var(--kd-brand-primary); }
|
|
651
|
+
.kd-card { border:1px solid var(--kd-border-l1); border-radius:14px;
|
|
652
|
+
background:var(--kd-bg-layer-1); padding:28px; box-shadow:0 4px 24px rgb(20 45 35 / 3%); }
|
|
635
653
|
.kd-card + .kd-card { margin-top:14px; }
|
|
636
654
|
.kd-meta { display:flex; gap:8px; flex-wrap:wrap; align-items:center; font-size:12px;
|
|
637
|
-
color:var(--
|
|
655
|
+
color:var(--kd-label-secondary); }
|
|
638
656
|
.kd-meta-dot::before { content:'·'; margin-right:8px; }
|
|
639
657
|
.kd-chips { display:flex; gap:6px; flex-wrap:wrap; align-items:center; }
|
|
640
658
|
.kd-tag { display:inline-flex; align-items:center; gap:5px; padding:2px 8px; border-radius:6px;
|
|
641
659
|
font-size:11.5px; line-height:1.7; white-space:nowrap;
|
|
642
|
-
color:var(--
|
|
643
|
-
.kd-tag-brand { color:var(--
|
|
644
|
-
background:color-mix(in srgb, var(--
|
|
660
|
+
color:var(--kd-label-secondary); background:var(--kd-bg-layer-2); }
|
|
661
|
+
.kd-tag-brand { color:var(--kd-brand-primary);
|
|
662
|
+
background:color-mix(in srgb, var(--kd-brand-primary) 10%, transparent); }
|
|
645
663
|
.kd-badge { display:inline-flex; align-items:center; gap:4px; padding:2px 8px; border-radius:999px;
|
|
646
|
-
font-size:11px; line-height:1.6; border:1px solid var(--
|
|
647
|
-
color:var(--
|
|
648
|
-
.kd-badge-ok { color:var(--
|
|
649
|
-
border-color:color-mix(in srgb, var(--
|
|
650
|
-
background:color-mix(in srgb, var(--
|
|
651
|
-
.kd-badge-bad { color:var(--
|
|
652
|
-
border-color:color-mix(in srgb, var(--
|
|
653
|
-
background:color-mix(in srgb, var(--
|
|
654
|
-
.kd-badge-warn { color:var(--
|
|
655
|
-
border-color:color-mix(in srgb, var(--
|
|
656
|
-
background:color-mix(in srgb, var(--
|
|
657
|
-
.kd-badge-brand { color:var(--
|
|
658
|
-
border-color:color-mix(in srgb, var(--
|
|
659
|
-
background:color-mix(in srgb, var(--
|
|
660
|
-
.kd-stem { margin:
|
|
664
|
+
font-size:11px; line-height:1.6; border:1px solid var(--kd-border-l2);
|
|
665
|
+
color:var(--kd-label-secondary); background:var(--kd-bg-layer-1); white-space:nowrap; }
|
|
666
|
+
.kd-badge-ok { color:var(--kd-state-success-primary);
|
|
667
|
+
border-color:color-mix(in srgb, var(--kd-state-success-primary) 34%, transparent);
|
|
668
|
+
background:color-mix(in srgb, var(--kd-state-success-primary) 10%, transparent); }
|
|
669
|
+
.kd-badge-bad { color:var(--kd-state-error-primary);
|
|
670
|
+
border-color:color-mix(in srgb, var(--kd-state-error-primary) 34%, transparent);
|
|
671
|
+
background:color-mix(in srgb, var(--kd-state-error-primary) 10%, transparent); }
|
|
672
|
+
.kd-badge-warn { color:var(--kd-state-warn-primary);
|
|
673
|
+
border-color:color-mix(in srgb, var(--kd-state-warn-primary) 34%, transparent);
|
|
674
|
+
background:color-mix(in srgb, var(--kd-state-warn-primary) 12%, transparent); }
|
|
675
|
+
.kd-badge-brand { color:var(--kd-brand-primary);
|
|
676
|
+
border-color:color-mix(in srgb, var(--kd-brand-primary) 34%, transparent);
|
|
677
|
+
background:color-mix(in srgb, var(--kd-brand-primary) 10%, transparent); }
|
|
678
|
+
.kd-stem { margin:24px 0; font-size:16px; line-height:1.85; }
|
|
661
679
|
.kd-options { display:flex; flex-direction:column; gap:8px; margin:0 0 4px; }
|
|
662
|
-
.kd-option { display:flex; gap:12px; align-items:flex-start; padding:
|
|
663
|
-
border:1px solid var(--
|
|
664
|
-
text-align:left; color:var(--
|
|
680
|
+
.kd-option { display:flex; gap:12px; align-items:flex-start; padding:14px 16px; border-radius:12px;
|
|
681
|
+
border:1px solid var(--kd-border-l1); background:var(--kd-bg-base); cursor:pointer;
|
|
682
|
+
text-align:left; color:var(--kd-label-primary); font:inherit; font-size:14px;
|
|
665
683
|
transition:background-color .15s, border-color .15s; }
|
|
666
|
-
.kd-option:hover:not(:disabled) { border-color:var(--
|
|
667
|
-
background:var(--
|
|
684
|
+
.kd-option:hover:not(:disabled) { border-color:var(--kd-border-l2);
|
|
685
|
+
background:var(--kd-bg-layer-2); }
|
|
668
686
|
.kd-option:disabled { cursor:default; }
|
|
669
|
-
.kd-option.is-selected { border-color:var(--
|
|
670
|
-
background:color-mix(in srgb, var(--
|
|
671
|
-
.kd-option.is-correct { border-color:var(--
|
|
672
|
-
background:color-mix(in srgb, var(--
|
|
673
|
-
.kd-option.is-wrong { border-color:var(--
|
|
674
|
-
background:color-mix(in srgb, var(--
|
|
675
|
-
.kd-option-key { flex:0 0 auto; width:24px; height:24px; border-radius:
|
|
687
|
+
.kd-option.is-selected { border-color:var(--kd-brand-primary);
|
|
688
|
+
background:color-mix(in srgb, var(--kd-brand-primary) 7%, transparent); }
|
|
689
|
+
.kd-option.is-correct { border-color:color-mix(in srgb, var(--kd-state-success-primary) 45%, var(--kd-surface));
|
|
690
|
+
background:color-mix(in srgb, var(--kd-state-success-primary) 8%, transparent); }
|
|
691
|
+
.kd-option.is-wrong { border-color:var(--kd-state-error-primary);
|
|
692
|
+
background:color-mix(in srgb, var(--kd-state-error-primary) 8%, transparent); }
|
|
693
|
+
.kd-option-key { flex:0 0 auto; width:24px; height:24px; border-radius:8px; display:inline-flex;
|
|
676
694
|
align-items:center; justify-content:center; font-size:12px; font-weight:600;
|
|
677
|
-
border:1px solid var(--
|
|
678
|
-
.kd-option.is-selected .kd-option-key { color:var(--
|
|
679
|
-
.kd-option.is-correct .kd-option-key { color:var(--
|
|
680
|
-
.kd-option.is-wrong .kd-option-key { color:var(--
|
|
695
|
+
border:1px solid var(--kd-border-l2); background:var(--kd-bg-base); }
|
|
696
|
+
.kd-option.is-selected .kd-option-key { color:var(--kd-brand-primary); border-color:var(--kd-brand-primary); }
|
|
697
|
+
.kd-option.is-correct .kd-option-key { color:var(--kd-state-success-primary); border-color:var(--kd-state-success-primary); }
|
|
698
|
+
.kd-option.is-wrong .kd-option-key { color:var(--kd-state-error-primary); border-color:var(--kd-state-error-primary); }
|
|
681
699
|
.kd-option-text { min-width:0; flex:1 1 auto; }
|
|
682
700
|
.kd-actions { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:16px; }
|
|
683
|
-
.kd-btn { display:inline-flex; align-items:center; justify-content:center; gap:6px; min-height:
|
|
701
|
+
.kd-btn { display:inline-flex; align-items:center; justify-content:center; gap:6px; min-height:36px;
|
|
684
702
|
padding:0 14px; border-radius:8px; font-family:inherit; font-size:13px; font-weight:500; cursor:pointer;
|
|
685
|
-
border:1px solid var(--
|
|
686
|
-
color:var(--
|
|
703
|
+
border:1px solid var(--kd-border-l2); background:var(--kd-bg-layer-1);
|
|
704
|
+
color:var(--kd-label-primary);
|
|
687
705
|
transition:background-color .15s, border-color .15s, color .15s; }
|
|
688
|
-
.kd-btn:hover:not(:disabled) { background:var(--
|
|
706
|
+
.kd-btn:hover:not(:disabled) { background:var(--kd-bg-layer-2); }
|
|
689
707
|
.kd-btn:disabled { opacity:.5; cursor:not-allowed; }
|
|
690
|
-
.kd-btn-primary { background:var(--
|
|
691
|
-
.kd-btn-primary:hover:not(:disabled) { background:var(--
|
|
692
|
-
.kd-btn-ghost { border-color:transparent; background:transparent; color:var(--
|
|
693
|
-
.kd-btn-ghost:hover:not(:disabled) { background:var(--
|
|
694
|
-
.kd-btn-danger { color:var(--
|
|
695
|
-
border-color:color-mix(in srgb, var(--
|
|
696
|
-
.kd-btn-danger:hover:not(:disabled) { background:color-mix(in srgb, var(--
|
|
708
|
+
.kd-btn-primary { background:var(--kd-brand-primary); border-color:var(--kd-brand-primary); color:#fff; }
|
|
709
|
+
.kd-btn-primary:hover:not(:disabled) { background:var(--kd-brand-primary); filter:brightness(1.07); }
|
|
710
|
+
.kd-btn-ghost { border-color:transparent; background:transparent; color:var(--kd-label-secondary); }
|
|
711
|
+
.kd-btn-ghost:hover:not(:disabled) { background:var(--kd-bg-layer-2); color:var(--kd-label-primary); }
|
|
712
|
+
.kd-btn-danger { color:var(--kd-state-error-primary); background:transparent;
|
|
713
|
+
border-color:color-mix(in srgb, var(--kd-state-error-primary) 40%, transparent); }
|
|
714
|
+
.kd-btn-danger:hover:not(:disabled) { background:color-mix(in srgb, var(--kd-state-error-primary) 10%, transparent); }
|
|
697
715
|
.kd-btn-sm { min-height:26px; padding:0 9px; font-size:12px; }
|
|
698
716
|
.kd-iconbtn { display:inline-flex; align-items:center; justify-content:center; width:26px; height:26px;
|
|
699
|
-
border-radius:6px; border:0; background:transparent; color:var(--
|
|
717
|
+
border-radius:6px; border:0; background:transparent; color:var(--kd-label-secondary); cursor:pointer;
|
|
700
718
|
transition:background-color .15s, color .15s; }
|
|
701
|
-
.kd-iconbtn:hover { background:var(--
|
|
719
|
+
.kd-iconbtn:hover { background:var(--kd-bg-layer-2); color:var(--kd-label-primary); }
|
|
702
720
|
.kd-answer { margin-top:6px; }
|
|
703
|
-
.kd-answer-panel { margin-top:18px; border:1px solid var(--
|
|
704
|
-
background:var(--
|
|
721
|
+
.kd-answer-panel { margin-top:18px; border:1px solid var(--kd-border-l1); border-radius:14px;
|
|
722
|
+
background:var(--kd-bg-layer-1); overflow:hidden; }
|
|
705
723
|
.kd-answer-panel-head { display:flex; align-items:center; justify-content:space-between; gap:10px;
|
|
706
|
-
flex-wrap:wrap; padding:11px 18px; border-bottom:1px solid var(--
|
|
707
|
-
background:var(--
|
|
708
|
-
.kd-answer-panel-head .kd-tag { background:var(--
|
|
724
|
+
flex-wrap:wrap; padding:11px 18px; border-bottom:1px solid var(--kd-border-l1);
|
|
725
|
+
background:color-mix(in srgb, var(--kd-brand-primary) 5%, var(--kd-surface)); font-size:13px; font-weight:600; }
|
|
726
|
+
.kd-answer-panel-head .kd-tag { background:var(--kd-bg-layer-1); font-weight:400; }
|
|
709
727
|
.kd-answer-panel-body { padding:18px; }
|
|
710
728
|
.kd-answer-block + .kd-answer-block { margin-top:18px; padding-top:16px;
|
|
711
|
-
border-top:1px solid var(--
|
|
712
|
-
.kd-answer-label { font-size:10.5px; letter-spacing:.1em; color:var(--
|
|
729
|
+
border-top:1px solid var(--kd-border-l1); }
|
|
730
|
+
.kd-answer-label { font-size:10.5px; letter-spacing:.1em; color:var(--kd-label-secondary);
|
|
713
731
|
margin-bottom:6px; }
|
|
714
732
|
.kd-verify { margin-top:14px; padding:11px 13px; border-radius:10px; font-size:12px; line-height:1.7;
|
|
715
|
-
border:1px solid var(--
|
|
716
|
-
color:var(--
|
|
733
|
+
border:1px solid var(--kd-border-l1); background:var(--kd-bg-layer-2);
|
|
734
|
+
color:var(--kd-label-secondary); }
|
|
717
735
|
.kd-verify ul { margin:6px 0 0; padding-left:18px; }
|
|
718
736
|
.kd-list-row { display:flex; gap:14px; align-items:flex-start; width:100%; text-align:left;
|
|
719
|
-
padding:14px 16px; border-radius:12px; border:1px solid var(--
|
|
720
|
-
background:var(--
|
|
737
|
+
padding:14px 16px; border-radius:12px; border:1px solid var(--kd-border-l1);
|
|
738
|
+
background:var(--kd-bg-layer-1); color:var(--kd-label-primary);
|
|
721
739
|
font:inherit; transition:background-color .15s, border-color .15s; }
|
|
740
|
+
.kd-list-row > .kd-btn { flex:none; white-space:nowrap; }
|
|
741
|
+
.kd-list-row[role=button] { cursor:pointer; }
|
|
742
|
+
.kd-list-open { flex:none; display:flex; align-items:center; gap:6px; font-size:12px; color:var(--kd-label-secondary); white-space:nowrap; }
|
|
722
743
|
.kd-list-row + .kd-list-row { margin-top:8px; }
|
|
723
|
-
.kd-list-row:hover { border-color:var(--
|
|
744
|
+
.kd-list-row:hover { border-color:var(--kd-border-l2); background:var(--kd-bg-layer-2); }
|
|
724
745
|
.kd-list-body { min-width:0; flex:1 1 auto; }
|
|
725
|
-
.kd-list-summary { font-size:
|
|
726
|
-
-
|
|
746
|
+
.kd-list-summary { --kd-preview-line-height:28px; font-size:14px; line-height:var(--kd-preview-line-height); min-width:0;
|
|
747
|
+
max-height:calc(2 * var(--kd-preview-line-height)); overflow:hidden; }
|
|
748
|
+
.kd-list-summary .kd-md { line-height:inherit; }
|
|
749
|
+
.kd-list-summary .kd-md > * { display:inline; }
|
|
750
|
+
.kd-list-summary .kd-p, .kd-list-summary .kd-h, .kd-list-summary .kd-mathblock { margin:0; }
|
|
751
|
+
.kd-list-summary .kd-p::after { content:' '; }
|
|
752
|
+
.kd-list-summary .kd-mathblock { display:inline; padding:0; text-align:left; vertical-align:baseline; }
|
|
753
|
+
.kd-list-summary mjx-container[display=true] { margin:0; }
|
|
754
|
+
.kd-list-summary .kd-code-wrap { margin:0; border:0; }
|
|
755
|
+
.kd-list-summary .kd-code-lang { display:none; }
|
|
756
|
+
.kd-list-summary .kd-code { display:inline; padding:0; line-height:inherit; white-space:pre-wrap; }
|
|
757
|
+
.kd-list-summary.is-truncated { mask-image:linear-gradient(#000,#000),linear-gradient(to right,#000 var(--kd-preview-fade-start),transparent var(--kd-preview-fade-end));
|
|
758
|
+
mask-size:100% var(--kd-preview-fade-top),100% calc(100% - var(--kd-preview-fade-top)); mask-position:top,bottom; mask-repeat:no-repeat; }
|
|
727
759
|
.kd-list-meta { margin-top:7px; display:flex; gap:6px; flex-wrap:wrap; font-size:11px;
|
|
728
|
-
color:var(--
|
|
729
|
-
.kd-empty { padding:56px 24px; text-align:center; color:var(--
|
|
760
|
+
color:var(--kd-label-secondary); align-items:center; }
|
|
761
|
+
.kd-empty { padding:56px 24px; text-align:center; color:var(--kd-label-secondary); }
|
|
730
762
|
.kd-empty-icon { width:44px; height:44px; margin:0 auto 14px; border-radius:50%; display:flex;
|
|
731
|
-
align-items:center; justify-content:center; border:1px solid var(--
|
|
732
|
-
background:var(--
|
|
733
|
-
.kd-empty-title { font-size:15px; font-weight:600; color:var(--
|
|
763
|
+
align-items:center; justify-content:center; border:1px solid var(--kd-border-l1);
|
|
764
|
+
background:var(--kd-bg-layer-1); color:var(--kd-label-secondary); }
|
|
765
|
+
.kd-empty-title { font-size:15px; font-weight:600; color:var(--kd-label-primary); margin-bottom:6px; }
|
|
734
766
|
.kd-empty-body { font-size:13px; max-width:460px; margin:0 auto; line-height:1.8; }
|
|
735
767
|
.kd-empty .kd-actions { justify-content:center; }
|
|
736
768
|
.kd-overlay { position:absolute; inset:0; background:rgba(0,0,0,.38); display:flex; align-items:center;
|
|
737
769
|
justify-content:center; padding:24px; z-index:40; backdrop-filter:blur(2px); }
|
|
738
770
|
.kd-modal { width:min(580px,100%); max-height:100%; display:flex; flex-direction:column;
|
|
739
|
-
background:var(--
|
|
740
|
-
border:1px solid var(--
|
|
771
|
+
background:var(--kd-bg-overlay); color:var(--kd-label-primary);
|
|
772
|
+
border:1px solid var(--kd-border-l2); border-radius:16px; overflow:hidden;
|
|
741
773
|
box-shadow:0 16px 48px rgba(0,0,0,.24); }
|
|
742
774
|
.kd-modal-wide { width:min(880px,100%); }
|
|
743
|
-
.kd-modal-head { padding:16px 20px; border-bottom:1px solid var(--
|
|
775
|
+
.kd-modal-head { padding:16px 20px; border-bottom:1px solid var(--kd-border-l1);
|
|
744
776
|
display:flex; align-items:center; justify-content:space-between; gap:10px; }
|
|
745
777
|
.kd-modal-title { font-size:15px; font-weight:650; letter-spacing:.01em; }
|
|
746
778
|
.kd-modal-body { padding:18px 20px; overflow:auto; min-height:0; }
|
|
747
|
-
.kd-modal-foot { padding:14px 20px; border-top:1px solid var(--
|
|
779
|
+
.kd-modal-foot { padding:14px 20px; border-top:1px solid var(--kd-border-l1);
|
|
748
780
|
display:flex; gap:8px; justify-content:flex-end; flex-wrap:wrap; }
|
|
749
781
|
.kd-field { display:flex; flex-direction:column; gap:6px; margin-bottom:14px; }
|
|
750
|
-
.kd-field-label { font-size:12px; font-weight:500; color:var(--
|
|
751
|
-
.kd-input { height:34px; padding:0 11px; border-radius:8px; border:1px solid var(--
|
|
752
|
-
background:var(--
|
|
782
|
+
.kd-field-label { font-size:12px; font-weight:500; color:var(--kd-label-secondary); }
|
|
783
|
+
.kd-input { height:34px; padding:0 11px; border-radius:8px; border:1px solid var(--kd-border-l2);
|
|
784
|
+
background:var(--kd-bg-base); color:var(--kd-label-primary); font:inherit; font-size:13px;
|
|
753
785
|
transition:border-color .15s, box-shadow .15s; }
|
|
754
|
-
.kd-input:focus { outline:none; border-color:var(--
|
|
755
|
-
box-shadow:0 0 0 3px color-mix(in srgb, var(--
|
|
756
|
-
.kd-input::placeholder { color:var(--
|
|
786
|
+
.kd-input:focus { outline:none; border-color:var(--kd-brand-primary);
|
|
787
|
+
box-shadow:0 0 0 3px color-mix(in srgb, var(--kd-brand-primary) 14%, transparent); }
|
|
788
|
+
.kd-input::placeholder { color:var(--kd-label-secondary); }
|
|
757
789
|
.kd-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
|
|
758
790
|
.kd-row-between { display:flex; gap:10px; align-items:center; justify-content:space-between; flex-wrap:wrap; }
|
|
759
|
-
.kd-divider { height:1px; background:var(--
|
|
760
|
-
.kd-preview-item { border:1px solid var(--
|
|
761
|
-
background:var(--
|
|
791
|
+
.kd-divider { height:1px; background:var(--kd-border-l1); margin:16px 0; }
|
|
792
|
+
.kd-preview-item { border:1px solid var(--kd-border-l1); border-radius:12px; padding:12px 14px;
|
|
793
|
+
background:var(--kd-bg-base); }
|
|
762
794
|
.kd-preview-item + .kd-preview-item { margin-top:8px; }
|
|
763
|
-
.kd-warn-text { color:var(--
|
|
764
|
-
.kd-error-text { color:var(--
|
|
795
|
+
.kd-warn-text { color:var(--kd-state-warn-primary); font-size:12px; line-height:1.7; }
|
|
796
|
+
.kd-error-text { color:var(--kd-state-error-primary); font-size:12px; line-height:1.7; }
|
|
765
797
|
.kd-toast { position:absolute; left:50%; bottom:24px; transform:translateX(-50%); z-index:60;
|
|
766
798
|
padding:9px 18px; border-radius:999px; font-size:13px; max-width:80%;
|
|
767
|
-
background:var(--
|
|
799
|
+
background:var(--kd-bg-overlay); border:1px solid var(--kd-border-l2);
|
|
768
800
|
box-shadow:0 8px 24px rgba(0,0,0,.18); }
|
|
769
|
-
.kd-toast-bad { color:var(--
|
|
770
|
-
.kd-toast-ok { color:var(--
|
|
771
|
-
.kd-toast-warn { color:var(--
|
|
772
|
-
.kd-loading { padding:48px 0; text-align:center; color:var(--
|
|
801
|
+
.kd-toast-bad { color:var(--kd-state-error-primary); }
|
|
802
|
+
.kd-toast-ok { color:var(--kd-state-success-primary); }
|
|
803
|
+
.kd-toast-warn { color:var(--kd-state-warn-primary); }
|
|
804
|
+
.kd-loading { padding:48px 0; text-align:center; color:var(--kd-label-secondary); font-size:13px; }
|
|
773
805
|
.kd-spinner { display:inline-block; width:14px; height:14px; border-radius:50%; vertical-align:-2px;
|
|
774
|
-
border:2px solid var(--
|
|
806
|
+
border:2px solid var(--kd-border-l2); border-top-color:var(--kd-brand-primary);
|
|
775
807
|
animation:kd-spin .8s linear infinite; margin-right:8px; }
|
|
776
808
|
@keyframes kd-spin { to { transform:rotate(360deg); } }
|
|
777
|
-
.kd-mobilebar { display:none; gap:8px; padding:10px 14px; border-bottom:1px solid var(--
|
|
809
|
+
.kd-mobilebar { display:none; gap:8px; padding:10px 14px; border-bottom:1px solid var(--kd-border-l1);
|
|
778
810
|
overflow:auto; }
|
|
811
|
+
.kd-mobilebar > * { flex:0 0 auto; white-space:nowrap; }
|
|
812
|
+
.kd-mobilebar .kd-select { max-width:180px; }
|
|
779
813
|
.kd-md { line-height:1.78; overflow-wrap:anywhere; }
|
|
780
814
|
.kd-md > *:first-child { margin-top:0; }
|
|
781
815
|
.kd-md > *:last-child { margin-bottom:0; }
|
|
@@ -785,57 +819,105 @@ window.__ModuleLoader__.load({
|
|
|
785
819
|
.kd-h4, .kd-h5, .kd-h6 { font-size:14px; }
|
|
786
820
|
.kd-list { margin:8px 0; padding-left:22px; }
|
|
787
821
|
.kd-list li { margin:3px 0; }
|
|
788
|
-
.kd-quote { margin:10px 0; padding:6px 12px; border-left:3px solid var(--
|
|
789
|
-
color:var(--
|
|
790
|
-
.kd-hr { border:0; border-top:1px solid var(--
|
|
791
|
-
.kd-link { color:var(--
|
|
822
|
+
.kd-quote { margin:10px 0; padding:6px 12px; border-left:3px solid var(--kd-border-l2);
|
|
823
|
+
color:var(--kd-label-secondary); }
|
|
824
|
+
.kd-hr { border:0; border-top:1px solid var(--kd-border-l1); margin:14px 0; }
|
|
825
|
+
.kd-link { color:var(--kd-brand-primary); text-decoration:underline; text-underline-offset:2px; }
|
|
792
826
|
.kd-inline-code { font-family:ui-monospace,SFMono-Regular,Consolas,monospace; font-size:.9em;
|
|
793
|
-
padding:1px 5px; border-radius:5px; background:var(--
|
|
794
|
-
border:1px solid var(--
|
|
795
|
-
.kd-code-wrap { margin:10px 0; border-radius:10px; overflow:hidden; border:1px solid var(--
|
|
796
|
-
background:var(--
|
|
797
|
-
.kd-code-lang { padding:3px 10px; font-size:11px; color:var(--
|
|
798
|
-
border-bottom:1px solid var(--
|
|
827
|
+
padding:1px 5px; border-radius:5px; background:var(--kd-bg-layer-2);
|
|
828
|
+
border:1px solid var(--kd-border-l1); }
|
|
829
|
+
.kd-code-wrap { margin:10px 0; border-radius:10px; overflow:hidden; border:1px solid var(--kd-border-l1);
|
|
830
|
+
background:var(--kd-bg-layer-2); }
|
|
831
|
+
.kd-code-lang { padding:3px 10px; font-size:11px; color:var(--kd-label-secondary);
|
|
832
|
+
border-bottom:1px solid var(--kd-border-l1); }
|
|
799
833
|
.kd-code { margin:0; padding:10px 12px; overflow:auto; font-family:ui-monospace,SFMono-Regular,Consolas,monospace;
|
|
800
834
|
font-size:12.5px; line-height:1.6; white-space:pre; tab-size:2; }
|
|
801
835
|
.kd-code code { font:inherit; }
|
|
802
836
|
.kd-table-wrap { overflow:auto; margin:10px 0; }
|
|
803
837
|
.kd-table { border-collapse:collapse; font-size:13px; min-width:0; }
|
|
804
|
-
.kd-table th, .kd-table td { border:1px solid var(--
|
|
805
|
-
.kd-table th { background:var(--
|
|
838
|
+
.kd-table th, .kd-table td { border:1px solid var(--kd-border-l1); padding:5px 10px; text-align:left; }
|
|
839
|
+
.kd-table th { background:var(--kd-bg-layer-2); font-weight:600; }
|
|
806
840
|
.kd-image { max-width:100%; height:auto; border-radius:8px; display:block; margin:8px 0; }
|
|
807
841
|
.kd-image-fallback { display:inline-block; font-family:ui-monospace,monospace; font-size:12px;
|
|
808
|
-
color:var(--
|
|
842
|
+
color:var(--kd-state-warn-primary); border:1px dashed var(--kd-state-warn-primary);
|
|
809
843
|
border-radius:6px; padding:2px 6px; }
|
|
810
|
-
.kd-math {
|
|
811
|
-
.kd-math
|
|
812
|
-
|
|
844
|
+
.kd-math, .kd-mathblock { font-size:17px; }
|
|
845
|
+
.kd-math { display:inline-flex; vertical-align:baseline; align-items:baseline; max-width:100%; padding:.25em .1em;
|
|
846
|
+
overflow-x:auto; overflow-y:hidden; }
|
|
847
|
+
.kd-math mjx-container { color:currentColor; flex:none; }
|
|
848
|
+
.kd-mathblock { display:block; max-width:100%; min-width:0; margin:10px 0; padding:.35em .2em; overflow-x:auto; overflow-y:hidden;
|
|
813
849
|
text-align:center; }
|
|
814
|
-
.kd-
|
|
850
|
+
.kd-page .kd-math mjx-container, .kd-page .kd-mathblock mjx-container {
|
|
851
|
+
display:inline-block; font-size:100% !important; max-width:none; }
|
|
852
|
+
.kd-page .kd-math svg, .kd-page .kd-mathblock svg { max-width:none !important; }
|
|
815
853
|
.kd-math-pending { font-family:ui-monospace,SFMono-Regular,Consolas,monospace; font-size:.9em;
|
|
816
854
|
opacity:.55; white-space:pre-wrap; }
|
|
817
|
-
.kd-math-error { color:var(--
|
|
855
|
+
.kd-math-error { color:var(--kd-state-error-primary);
|
|
818
856
|
font-family:ui-monospace,SFMono-Regular,Consolas,monospace; font-size:.9em; }
|
|
819
857
|
.kd-checkline { display:flex; gap:8px; align-items:flex-start; padding:5px 0; font-size:13px; }
|
|
820
858
|
.kd-chip { display:inline-flex; align-items:center; gap:2px; padding:0 2px 0 8px; border-radius:999px;
|
|
821
|
-
border:1px solid var(--
|
|
859
|
+
border:1px solid var(--kd-border-l2); background:var(--kd-bg-layer-1); font-size:12px; }
|
|
822
860
|
.kd-chip .kd-checkline { padding:0; }
|
|
823
861
|
.kd-chip .kd-iconbtn { width:20px; height:20px; }
|
|
862
|
+
.kd-option.is-correct { border-color:#22c55e; }
|
|
863
|
+
.kd-option.is-wrong { border-color:#f35464; }
|
|
864
|
+
.kd-page input[type=checkbox] { accent-color:var(--kd-brand-primary); width:16px; height:16px; flex:none; }
|
|
865
|
+
.kd-btn, .kd-iconbtn, .kd-tab, .kd-nav-item, .kd-option, .kd-list-row {
|
|
866
|
+
transition:background-color .16s, border-color .16s, color .16s, box-shadow .16s, transform .16s; }
|
|
867
|
+
.kd-btn:active:not(:disabled), .kd-iconbtn:active, .kd-tab:active, .kd-nav-item:active { transform:scale(.97); }
|
|
868
|
+
.kd-option:active:not(:disabled) { transform:translateY(1px); }
|
|
869
|
+
.kd-view { animation:kd-enter .2s ease-out; }
|
|
870
|
+
.kd-presence { display:contents; }
|
|
871
|
+
.kd-overlay { animation:kd-fade .16s ease-out; background:rgb(18 27 52 / 28%); backdrop-filter:blur(5px); }
|
|
872
|
+
.kd-modal { animation:kd-enter .18s ease-out; box-shadow:0 24px 80px rgb(12 25 60 / 18%); border-radius:20px; }
|
|
873
|
+
.kd-presence.is-leaving .kd-overlay { animation:kd-fade .16s ease-in reverse both; pointer-events:none; }
|
|
874
|
+
.kd-presence.is-leaving .kd-modal { animation:kd-enter .16s ease-in reverse both; }
|
|
875
|
+
.kd-thread { animation:kd-drawer .2s ease-out; }
|
|
876
|
+
.kd-presence.is-leaving .kd-thread { animation:kd-drawer .16s ease-in reverse both; pointer-events:none; }
|
|
877
|
+
.kd-modal-head { padding:22px 24px; }
|
|
878
|
+
.kd-modal-title { font-size:18px; }
|
|
879
|
+
.kd-modal-body { padding:20px 24px; background:color-mix(in srgb, var(--kd-brand-primary) 2%, var(--kd-surface)); }
|
|
880
|
+
.kd-modal-foot { padding:16px 24px; }
|
|
881
|
+
.kd-manage-card { padding:16px; border:1px solid var(--kd-border-l1); border-radius:14px; background:var(--kd-bg-base); margin-bottom:10px; }
|
|
882
|
+
.kd-manage-card.is-enabled { border-color:color-mix(in srgb, var(--kd-brand-primary) 25%, var(--kd-surface)); }
|
|
883
|
+
.kd-manage-card .kd-checkline { font-weight:550; }
|
|
884
|
+
.kd-manage-card .kd-msg-meta { font-weight:400; line-height:1.7; }
|
|
885
|
+
.kd-custom-subject { padding:20px; border-radius:14px; border:1px solid var(--kd-border-l1); background:var(--kd-bg-base); }
|
|
886
|
+
.kd-field-label { font-size:13px; }
|
|
887
|
+
.kd-input { height:38px; }
|
|
888
|
+
.kd-bulkbar { position:sticky; top:0; z-index:5; display:flex; flex-direction:column; gap:10px; padding:14px;
|
|
889
|
+
margin-bottom:14px; border-radius:14px; border:1px solid color-mix(in srgb, var(--kd-brand-primary) 25%, var(--kd-surface));
|
|
890
|
+
background:var(--kd-bg-base); box-shadow:0 5px 20px rgb(20 35 70 / 5%); animation:kd-enter .18s ease-out; }
|
|
891
|
+
.kd-list-row.is-selected { border-color:var(--kd-brand-primary); background:color-mix(in srgb, var(--kd-brand-primary) 5%, var(--kd-surface)); }
|
|
892
|
+
.kd-list-row input { margin-top:5px; }
|
|
893
|
+
@keyframes kd-fade { from { opacity:0; } to { opacity:1; } }
|
|
894
|
+
@keyframes kd-enter { from { opacity:0; transform:translateY(8px) scale(.99); } to { opacity:1; transform:none; } }
|
|
895
|
+
@keyframes kd-drawer { from { opacity:0; transform:translateX(16px); } to { opacity:1; transform:none; } }
|
|
824
896
|
@media (max-width: 1180px) {
|
|
825
|
-
.kd-thread { position:absolute; right:0; top:0; bottom:0; width:min(370px,92vw); z-index:30;
|
|
897
|
+
.kd-thread { position:absolute; right:0; top:0; bottom:0; width:min(370px,92vw); flex-basis:auto; z-index:30;
|
|
826
898
|
box-shadow:-12px 0 32px rgba(0,0,0,.22); }
|
|
899
|
+
.kd-thread-resize { display:none; }
|
|
827
900
|
}
|
|
828
901
|
@media (max-width: 860px) {
|
|
829
902
|
.kd-nav { display:none; }
|
|
830
903
|
.kd-mobilebar { display:flex; }
|
|
831
|
-
.kd-main { padding:16px 14px
|
|
904
|
+
.kd-main-toolbar { padding:16px 14px; }
|
|
905
|
+
.kd-main-scroll { padding:0 14px 40px; }
|
|
832
906
|
.kd-header { padding:14px 14px 12px; gap:10px; }
|
|
907
|
+
.kd-header-left { flex-basis:100%; }
|
|
833
908
|
.kd-cd-lines { align-items:flex-start; text-align:left; }
|
|
909
|
+
.kd-cd-primary { font-size:19px; }
|
|
910
|
+
.kd-tabs { flex:1; }
|
|
911
|
+
.kd-tab { flex:1 0 auto; justify-content:center; padding:7px 10px; }
|
|
834
912
|
.kd-strip-item { white-space:normal; }
|
|
835
913
|
.kd-card { padding:16px 14px; }
|
|
836
914
|
.kd-answer-panel-body { padding:14px; }
|
|
837
915
|
.kd-exam-strip { padding:8px 10px; }
|
|
838
916
|
}
|
|
917
|
+
@media (prefers-reduced-motion: reduce) {
|
|
918
|
+
.kd-page *, .kd-page *::before, .kd-page *::after { transition:none !important; animation:none !important; }
|
|
919
|
+
}
|
|
920
|
+
|
|
839
921
|
`
|
|
840
922
|
|
|
841
923
|
/* ============================== 接口 ============================== */
|
|
@@ -896,6 +978,7 @@ window.__ModuleLoader__.load({
|
|
|
896
978
|
check: 'M4 12.5l5 5L20 6.5',
|
|
897
979
|
x: 'M6 6l12 12M18 6L6 18',
|
|
898
980
|
chevron: 'M9 6l6 6-6 6',
|
|
981
|
+
'chevron-left': 'M15 6l-6 6 6 6',
|
|
899
982
|
send: 'M4 12l16-8-6 8 6 8z',
|
|
900
983
|
book: 'M4 5.5A2.5 2.5 0 0 1 6.5 3H20v16H6.5A2.5 2.5 0 0 0 4 21.5z',
|
|
901
984
|
plus: 'M12 5v14M5 12h14',
|
|
@@ -914,13 +997,14 @@ window.__ModuleLoader__.load({
|
|
|
914
997
|
}, h('path', { d: path }))
|
|
915
998
|
}
|
|
916
999
|
|
|
917
|
-
function Btn({ children, onClick, variant = 'default', disabled, title, small, type = 'button' }) {
|
|
1000
|
+
function Btn({ children, onClick, variant = 'default', disabled, title, small, type = 'button', ariaLabel }) {
|
|
918
1001
|
return h('button', {
|
|
919
1002
|
type,
|
|
920
1003
|
className: `kd-btn kd-btn-${variant}${small ? ' kd-btn-sm' : ''}`,
|
|
921
1004
|
onClick,
|
|
922
1005
|
disabled,
|
|
923
1006
|
title,
|
|
1007
|
+
'aria-label': ariaLabel,
|
|
924
1008
|
}, children)
|
|
925
1009
|
}
|
|
926
1010
|
|
|
@@ -937,6 +1021,17 @@ window.__ModuleLoader__.load({
|
|
|
937
1021
|
actions ? h('div', { className: 'kd-actions' }, actions) : null)
|
|
938
1022
|
}
|
|
939
1023
|
|
|
1024
|
+
function Presence({ children }) {
|
|
1025
|
+
const active = Array.isArray(children) ? children.some(Boolean) : Boolean(children)
|
|
1026
|
+
const [retained, setRetained] = useState(children)
|
|
1027
|
+
useEffect(() => {
|
|
1028
|
+
if (active) { setRetained(children); return undefined }
|
|
1029
|
+
const timer = setTimeout(() => setRetained([]), 160)
|
|
1030
|
+
return () => clearTimeout(timer)
|
|
1031
|
+
}, [children, active])
|
|
1032
|
+
return h('div', { className: 'kd-presence' + (active ? '' : ' is-leaving') }, active ? children : retained)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
940
1035
|
function Modal({ title, children, footer, onClose, wide }) {
|
|
941
1036
|
useEffect(() => {
|
|
942
1037
|
const onKey = (event) => {
|
|
@@ -976,12 +1071,12 @@ window.__ModuleLoader__.load({
|
|
|
976
1071
|
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
977
1072
|
}
|
|
978
1073
|
|
|
979
|
-
/**
|
|
1074
|
+
/** 剩余不超过 30 天时省略中间的时长行。 */
|
|
980
1075
|
function CountdownLines({ countdown }) {
|
|
981
1076
|
const lines = (countdown || {}).lines || {}
|
|
982
1077
|
return h('div', { className: 'kd-cd-lines' },
|
|
983
1078
|
h('div', { className: 'kd-cd-primary' }, lines.primary || ''),
|
|
984
|
-
h('div', { className: 'kd-cd-secondary' }, lines.secondary
|
|
1079
|
+
lines.secondary ? h('div', { className: 'kd-cd-secondary' }, lines.secondary) : null,
|
|
985
1080
|
h('div', { className: 'kd-cd-target' }, lines.target || ''))
|
|
986
1081
|
}
|
|
987
1082
|
|
|
@@ -1077,7 +1172,7 @@ window.__ModuleLoader__.load({
|
|
|
1077
1172
|
}, label, count ? h('span', { className: 'kd-tab-count' }, String(count)) : null)))
|
|
1078
1173
|
}
|
|
1079
1174
|
|
|
1080
|
-
function MobileBar({ boot, subjectId,
|
|
1175
|
+
function MobileBar({ boot, subjectId, onPick, onManage, onBanks }) {
|
|
1081
1176
|
const enabled = (boot.subjects || []).filter((subject) => subject.enabled)
|
|
1082
1177
|
return h('div', { className: 'kd-mobilebar' },
|
|
1083
1178
|
h('select', {
|
|
@@ -1086,13 +1181,6 @@ window.__ModuleLoader__.load({
|
|
|
1086
1181
|
'aria-label': '切换科目',
|
|
1087
1182
|
onChange: (event) => onPick(event.target.value),
|
|
1088
1183
|
}, enabled.map((subject) => h('option', { key: subject.id, value: subject.id }, subject.name))),
|
|
1089
|
-
['today', 'all', 'favorite', 'wrong'].map((value) => h('button', {
|
|
1090
|
-
key: value,
|
|
1091
|
-
type: 'button',
|
|
1092
|
-
className: `kd-nav-item${kind === value ? ' is-active' : ''}`,
|
|
1093
|
-
style: { width: 'auto' },
|
|
1094
|
-
onClick: () => onTab(value),
|
|
1095
|
-
}, T.tabs[value])),
|
|
1096
1184
|
// 窄屏隐藏了左侧栏,科目的增删与题库入口必须在这里保留。
|
|
1097
1185
|
h(Btn, { small: true, variant: 'ghost', onClick: onManage }, h(Icon, { name: 'plus', size: 13 }), T.manageSubjects),
|
|
1098
1186
|
h(Btn, { small: true, variant: 'ghost', onClick: onBanks }, h(Icon, { name: 'book', size: 13 }), T.manageBanks))
|
|
@@ -1184,7 +1272,7 @@ window.__ModuleLoader__.load({
|
|
|
1184
1272
|
|
|
1185
1273
|
function QuestionPane({
|
|
1186
1274
|
q, busy, choice, onSelect, onReveal, onJudge, onFavorite, onRedo, localRedo,
|
|
1187
|
-
onRevise, revising, onToggleThread, threadOpen,
|
|
1275
|
+
onRevise, revising, onToggleThread, threadOpen, onRemoveWrong,
|
|
1188
1276
|
}) {
|
|
1189
1277
|
if (!q) return null
|
|
1190
1278
|
const revealed = !localRedo && q.answerState === 'revealed' && q.attemptCount > 0
|
|
@@ -1255,9 +1343,7 @@ window.__ModuleLoader__.load({
|
|
|
1255
1343
|
revealed
|
|
1256
1344
|
? h(Btn, { variant: 'ghost', onClick: onRedo, disabled: busy === 'reveal' }, h(Icon, { name: 'refresh', size: 13 }), T.redo)
|
|
1257
1345
|
: null,
|
|
1258
|
-
|
|
1259
|
-
? h(Btn, { variant: 'ghost', onClick: onToggleThread }, h(Icon, { name: 'book' }), threadOpen ? '收起追问' : T.followup)
|
|
1260
|
-
: null),
|
|
1346
|
+
revealed ? h(Btn, { variant: 'ghost', onClick: onToggleThread }, h(Icon, { name: 'book' }), threadOpen ? '收起追问' : T.followup) : null),
|
|
1261
1347
|
revealed ? h(AnswerBlock, { question: q, onRevise, revising, onRemoveWrong }) : null,
|
|
1262
1348
|
revealed && last?.result === 'pending'
|
|
1263
1349
|
? h('div', { className: 'kd-actions', style: { marginTop: '12px' } },
|
|
@@ -1270,7 +1356,7 @@ window.__ModuleLoader__.load({
|
|
|
1270
1356
|
h(Badge, { tone: q.publishVerification.status === 'passed' ? 'ok' : 'warn' },
|
|
1271
1357
|
q.publishVerification.status === 'passed' ? '试题发布前已通过独立校验' : '试题发布状态:待核验'),
|
|
1272
1358
|
// 校验结论里通常写着答案与关键推导,提交答案前不能展示,否则等于泄题。
|
|
1273
|
-
|
|
1359
|
+
revealed
|
|
1274
1360
|
? ` ${q.publishVerification.summary}`
|
|
1275
1361
|
: ' 结论在提交答案后显示',
|
|
1276
1362
|
`(${methodLabel(q.publishVerification.method)}${q.publishVerification.at ? `,${formatDateTime(q.publishVerification.at)}` : ''})`)
|
|
@@ -1278,7 +1364,59 @@ window.__ModuleLoader__.load({
|
|
|
1278
1364
|
}
|
|
1279
1365
|
|
|
1280
1366
|
/* ============================== 列表 ============================== */
|
|
1281
|
-
function
|
|
1367
|
+
function previewFadePosition(element, lineHeight) {
|
|
1368
|
+
const bounds = element.getBoundingClientRect()
|
|
1369
|
+
const rects = []
|
|
1370
|
+
const document = element.ownerDocument
|
|
1371
|
+
const walker = document.createTreeWalker(element, 4) // 只测量正文文字,公式由 SVG 的外框参与定位。
|
|
1372
|
+
const range = document.createRange()
|
|
1373
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
1374
|
+
if (!node.textContent.trim() || node.parentElement.closest('mjx-container,.kd-code-lang')) continue
|
|
1375
|
+
range.setStart(node, 0)
|
|
1376
|
+
range.setEnd(node, node.textContent.trimEnd().length)
|
|
1377
|
+
rects.push(...range.getClientRects())
|
|
1378
|
+
}
|
|
1379
|
+
for (const node of element.querySelectorAll('svg,img')) rects.push(node.getBoundingClientRect())
|
|
1380
|
+
const visible = rects.filter((rect) => rect.width > 0 && rect.height > 0
|
|
1381
|
+
&& rect.top < bounds.bottom - 1 && rect.bottom > bounds.top
|
|
1382
|
+
&& rect.left < bounds.right && rect.right > bounds.left)
|
|
1383
|
+
if (!visible.length) return null
|
|
1384
|
+
const lineOf = (rect) => Math.max(0, Math.min(1, Math.floor(((rect.top + rect.bottom) / 2 - bounds.top) / lineHeight)))
|
|
1385
|
+
const lastLine = Math.max(...visible.map(lineOf))
|
|
1386
|
+
const end = Math.min(bounds.width, Math.max(...visible.filter((rect) => lineOf(rect) === lastLine).map((rect) => rect.right - bounds.left)))
|
|
1387
|
+
return { top: lastLine * lineHeight, start: Math.max(0, end - 72), end }
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function QuestionPreview({ text }) {
|
|
1391
|
+
const ref = useRef(null)
|
|
1392
|
+
const [truncated, setTruncated] = useState(false)
|
|
1393
|
+
const mathStatus = useMathJaxStatus()
|
|
1394
|
+
useEffect(() => {
|
|
1395
|
+
const element = ref.current
|
|
1396
|
+
if (!element) return undefined
|
|
1397
|
+
const measure = () => {
|
|
1398
|
+
let lineHeight = 28
|
|
1399
|
+
for (const svg of element.querySelectorAll('svg')) lineHeight = Math.max(lineHeight, Math.ceil(svg.getBoundingClientRect().height + 16))
|
|
1400
|
+
element.style.setProperty('--kd-preview-line-height', `${lineHeight}px`)
|
|
1401
|
+
const content = element.firstElementChild
|
|
1402
|
+
const clipped = element.scrollHeight > element.clientHeight + 1 || element.scrollWidth > element.clientWidth + 1
|
|
1403
|
+
|| (content && content.scrollHeight > content.clientHeight + 1)
|
|
1404
|
+
const fade = clipped && previewFadePosition(element, lineHeight)
|
|
1405
|
+
if (fade) {
|
|
1406
|
+
for (const [key, value] of Object.entries(fade)) element.style.setProperty(`--kd-preview-fade-${key}`, `${value}px`)
|
|
1407
|
+
}
|
|
1408
|
+
setTruncated(Boolean(fade))
|
|
1409
|
+
}
|
|
1410
|
+
measure()
|
|
1411
|
+
const observer = new ResizeObserver(measure)
|
|
1412
|
+
observer.observe(element)
|
|
1413
|
+
if (element.firstElementChild) observer.observe(element.firstElementChild)
|
|
1414
|
+
return () => observer.disconnect()
|
|
1415
|
+
}, [text, mathStatus])
|
|
1416
|
+
return h('div', { ref, className: 'kd-list-summary' + (truncated ? ' is-truncated' : '') }, h(Markdown, { text }))
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
function ListPane({ list, kind, subjectName, onOpen, managing, selected, onManage, onToggle, onSelectAll, onBulk, busy }) {
|
|
1282
1420
|
if (!list) return h('div', { className: 'kd-loading' }, h('span', { className: 'kd-spinner' }), '加载中…')
|
|
1283
1421
|
if (!list.items.length) {
|
|
1284
1422
|
const icon = kind === 'favorite' ? 'star' : kind === 'wrong' ? 'check' : kind === 'all' ? 'book' : 'spark'
|
|
@@ -1290,33 +1428,49 @@ window.__ModuleLoader__.load({
|
|
|
1290
1428
|
}
|
|
1291
1429
|
return h('div', null,
|
|
1292
1430
|
h('div', { className: 'kd-meta', style: { marginBottom: '10px' } },
|
|
1293
|
-
h('span', null, `${subjectName} · ${T.tabs[kind]} · 共 ${list.items.length} 道`)
|
|
1294
|
-
|
|
1431
|
+
h('span', null, `${subjectName} · ${T.tabs[kind]} · 共 ${list.items.length} 道`),
|
|
1432
|
+
h(Btn, { small: true, onClick: onManage, disabled: busy }, managing ? '完成管理' : '批量管理')),
|
|
1433
|
+
managing ? h('div', { className: 'kd-bulkbar', 'aria-label': '批量管理' },
|
|
1434
|
+
h('div', { className: 'kd-row' },
|
|
1435
|
+
h('strong', { 'aria-live': 'polite' }, `已选 ${selected.length} 道`),
|
|
1436
|
+
h(Btn, { small: true, disabled: busy, onClick: () => onSelectAll(true) }, '全选'),
|
|
1437
|
+
h(Btn, { small: true, disabled: busy || !selected.length, onClick: () => onSelectAll(false) }, '全不选')),
|
|
1438
|
+
h('div', { className: 'kd-row' },
|
|
1439
|
+
...[['favorite', '收藏'], ['unfavorite', '取消收藏'], ['wrong', '加入错题本'], ['unwrong', '移出错题本'], ['delete', '删除题目']].map(([action, label]) =>
|
|
1440
|
+
h(Btn, { key: action, small: true, variant: action === 'delete' ? 'danger' : 'default', disabled: busy || !selected.length, onClick: () => onBulk(action) }, label)))) : null,
|
|
1441
|
+
list.items.map((item) => h('div', {
|
|
1295
1442
|
key: item.id,
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1443
|
+
className: 'kd-list-row' + (managing && selected.includes(item.id) ? ' is-selected' : ''),
|
|
1444
|
+
role: managing ? undefined : 'button',
|
|
1445
|
+
tabIndex: managing ? undefined : 0,
|
|
1446
|
+
onClick: managing ? undefined : (event) => { if (!event.target.closest('a,input')) onOpen(item.id) },
|
|
1447
|
+
onKeyDown: managing ? undefined : (event) => {
|
|
1448
|
+
if (event.target === event.currentTarget && ['Enter', ' '].includes(event.key)) { event.preventDefault(); onOpen(item.id) }
|
|
1449
|
+
},
|
|
1299
1450
|
},
|
|
1451
|
+
managing ? h('input', { type: 'checkbox', checked: selected.includes(item.id), disabled: busy,
|
|
1452
|
+
'aria-label': '选择题目:' + (item.summary || item.id), onChange: () => onToggle(item.id) }) : null,
|
|
1300
1453
|
h('div', { className: 'kd-list-body' },
|
|
1301
|
-
h(
|
|
1454
|
+
h(QuestionPreview, { text: item.stem || item.summary || '(无题干)' }),
|
|
1302
1455
|
h('div', { className: 'kd-list-meta' },
|
|
1303
1456
|
h(Badge, null, item.typeLabel),
|
|
1304
1457
|
h('span', null, formatDate(item.assignedDate)),
|
|
1305
1458
|
h('span', null, `作答 ${item.attemptCount} 次`),
|
|
1306
1459
|
item.favorite ? h(Badge, { tone: 'brand' }, '收藏') : null,
|
|
1307
|
-
item.wrongEver ? h(Badge, { tone: 'bad' }, item.mastered ? '
|
|
1460
|
+
item.wrongEver ? h(Badge, { tone: 'bad' }, item.mastered ? '错题 · 已掌握' : '错题') : null,
|
|
1308
1461
|
item.answerStatus !== 'verified' ? h(Badge, { tone: 'warn' }, T.unverified) : null,
|
|
1309
1462
|
h('span', null, `难度 ${item.difficulty}/5`))),
|
|
1310
|
-
h('span', { className: 'kd-
|
|
1463
|
+
!managing ? h('span', { className: 'kd-list-open', 'aria-hidden': true }, '查看题目', h(Icon, { name: 'chevron' })) : null)))
|
|
1311
1464
|
}
|
|
1312
1465
|
|
|
1313
1466
|
/* ============================== 追问侧栏 ============================== */
|
|
1314
|
-
function ThreadPane({ thread, questionId, busy, onSend, onClear, onClose, narrow }) {
|
|
1467
|
+
function ThreadPane({ thread, questionId, busy, onSend, onClear, onClose, narrow, ratio = 33, onRatioChange }) {
|
|
1315
1468
|
const [draft, setDraft] = useState('')
|
|
1316
1469
|
const [sending, setSending] = useState(false)
|
|
1317
1470
|
const [localError, setLocalError] = useState('')
|
|
1318
1471
|
const [pending, setPending] = useState(null)
|
|
1319
1472
|
const scrollRef = useRef(null)
|
|
1473
|
+
const sendingRef = useRef(false)
|
|
1320
1474
|
const messages = thread?.messages || []
|
|
1321
1475
|
|
|
1322
1476
|
useEffect(() => {
|
|
@@ -1326,7 +1480,8 @@ window.__ModuleLoader__.load({
|
|
|
1326
1480
|
|
|
1327
1481
|
const send = useCallback(async (text, messageId) => {
|
|
1328
1482
|
const value = String(text || '').trim()
|
|
1329
|
-
if (!value) return
|
|
1483
|
+
if (!value || sendingRef.current) return
|
|
1484
|
+
sendingRef.current = true
|
|
1330
1485
|
setSending(true)
|
|
1331
1486
|
setLocalError('')
|
|
1332
1487
|
try {
|
|
@@ -1337,6 +1492,7 @@ window.__ModuleLoader__.load({
|
|
|
1337
1492
|
setLocalError(error.message)
|
|
1338
1493
|
setPending({ id: messageId, text: value })
|
|
1339
1494
|
} finally {
|
|
1495
|
+
sendingRef.current = false
|
|
1340
1496
|
setSending(false)
|
|
1341
1497
|
}
|
|
1342
1498
|
}, [onSend])
|
|
@@ -1372,11 +1528,30 @@ window.__ModuleLoader__.load({
|
|
|
1372
1528
|
h('span', { className: 'kd-spinner' }), T.sending))
|
|
1373
1529
|
}
|
|
1374
1530
|
|
|
1375
|
-
|
|
1531
|
+
const resize = (value) => onRatioChange(Math.max(20, Math.min(55, value)))
|
|
1532
|
+
return h('aside', { className: 'kd-thread', 'aria-label': T.followup, style: { '--kd-thread-ratio': ratio / 100 } },
|
|
1533
|
+
!narrow ? h('div', { className: 'kd-thread-resize', role: 'separator', tabIndex: 0,
|
|
1534
|
+
'aria-label': '调整题目与追问的宽度比例', 'aria-orientation': 'vertical',
|
|
1535
|
+
'aria-valuemin': 20, 'aria-valuemax': 55, 'aria-valuenow': Math.round(ratio),
|
|
1536
|
+
onPointerDown: (event) => { event.preventDefault(); event.currentTarget.setPointerCapture(event.pointerId) },
|
|
1537
|
+
onPointerMove: (event) => {
|
|
1538
|
+
if (!event.currentTarget.hasPointerCapture(event.pointerId)) return
|
|
1539
|
+
const pane = event.currentTarget.parentElement
|
|
1540
|
+
const left = pane.closest('.kd-shell').querySelector('.kd-main').getBoundingClientRect().left
|
|
1541
|
+
const right = pane.getBoundingClientRect().right
|
|
1542
|
+
resize((right - event.clientX) / (right - left) * 100)
|
|
1543
|
+
},
|
|
1544
|
+
onPointerUp: (event) => event.currentTarget.releasePointerCapture(event.pointerId),
|
|
1545
|
+
onPointerCancel: (event) => event.currentTarget.releasePointerCapture(event.pointerId),
|
|
1546
|
+
onKeyDown: (event) => {
|
|
1547
|
+
const value = { ArrowLeft: ratio + 2, ArrowRight: ratio - 2, Home: 20, End: 55 }[event.key]
|
|
1548
|
+
if (value !== undefined) { event.preventDefault(); resize(value) }
|
|
1549
|
+
},
|
|
1550
|
+
}) : null,
|
|
1376
1551
|
h('div', { className: 'kd-thread-head' },
|
|
1377
1552
|
h('div', null,
|
|
1378
1553
|
h('div', { className: 'kd-thread-title' }, T.followup),
|
|
1379
|
-
h('div', { className: 'kd-msg-meta' },
|
|
1554
|
+
h('div', { className: 'kd-msg-meta' }, '围绕当前题目,一起理清思路')),
|
|
1380
1555
|
h('div', { className: 'kd-row' },
|
|
1381
1556
|
h(Btn, {
|
|
1382
1557
|
small: true,
|
|
@@ -1385,9 +1560,7 @@ window.__ModuleLoader__.load({
|
|
|
1385
1560
|
onClick: onClear,
|
|
1386
1561
|
title: '只清空本题的追问记录',
|
|
1387
1562
|
}, T.clearThread),
|
|
1388
|
-
|
|
1389
|
-
? h('button', { className: 'kd-iconbtn', onClick: onClose, 'aria-label': T.close }, h(Icon, { name: 'close' }))
|
|
1390
|
-
: null)),
|
|
1563
|
+
h('button', { className: 'kd-iconbtn', onClick: onClose, 'aria-label': T.close }, h(Icon, { name: 'close' })))),
|
|
1391
1564
|
h('div', { className: 'kd-thread-hint' }, T.followupHint),
|
|
1392
1565
|
h('div', { className: 'kd-thread-scroll', ref: scrollRef },
|
|
1393
1566
|
rows.length ? rows : h('div', { className: 'kd-empty-body' }, '还没有追问。可以问某一步推导为什么成立、某个知识点怎么理解,或有没有别的解法。')),
|
|
@@ -1397,13 +1570,13 @@ window.__ModuleLoader__.load({
|
|
|
1397
1570
|
h('textarea', {
|
|
1398
1571
|
className: 'kd-textarea',
|
|
1399
1572
|
value: draft,
|
|
1400
|
-
placeholder: '围绕这道题继续追问…(
|
|
1573
|
+
placeholder: '围绕这道题继续追问…(Enter 发送,Shift+Enter 换行)',
|
|
1401
1574
|
disabled: sending,
|
|
1402
1575
|
onChange: (event) => setDraft(event.target.value),
|
|
1403
1576
|
onKeyDown: (event) => {
|
|
1404
|
-
if (
|
|
1577
|
+
if (event.key === 'Enter' && !event.shiftKey && !event.isComposing && !event.nativeEvent?.isComposing && event.keyCode !== 229) {
|
|
1405
1578
|
event.preventDefault()
|
|
1406
|
-
send(draft, newAttemptId())
|
|
1579
|
+
if (!event.repeat) send(draft, newAttemptId())
|
|
1407
1580
|
}
|
|
1408
1581
|
},
|
|
1409
1582
|
}),
|
|
@@ -1417,12 +1590,22 @@ window.__ModuleLoader__.load({
|
|
|
1417
1590
|
}
|
|
1418
1591
|
|
|
1419
1592
|
/* ============================== 弹窗 ============================== */
|
|
1420
|
-
function SubjectsModal({ boot, onClose,
|
|
1593
|
+
function SubjectsModal({ boot, onClose, onDone }) {
|
|
1594
|
+
const [draft, setDraft] = useState(() => ({ subjects: [...boot.settings.subjects],
|
|
1595
|
+
subjectSource: { ...boot.settings.subjectSource }, customSubjects: structuredClone(boot.customSubjects || []) }))
|
|
1596
|
+
const [saving, setSaving] = useState(false)
|
|
1597
|
+
const [saveError, setSaveError] = useState('')
|
|
1421
1598
|
const [name, setName] = useState('')
|
|
1422
1599
|
const [types, setTypes] = useState([])
|
|
1423
1600
|
const [customTypes, setCustomTypes] = useState([])
|
|
1424
1601
|
const [typeDraft, setTypeDraft] = useState('')
|
|
1425
|
-
const enabled = new Set(
|
|
1602
|
+
const enabled = new Set(draft.subjects)
|
|
1603
|
+
const subjects = [...(boot.subjects || []), ...draft.customSubjects.filter((item) => !(boot.subjects || []).some((s) => s.id === item.id)).map((item) => ({
|
|
1604
|
+
...item, typeLabels: item.types.map((type) => ({ single: '单项选择题', multiple: '多项选择题', fill: '填空题', solution: '解答题' })[type] || type), bankItemCount: 0,
|
|
1605
|
+
}))]
|
|
1606
|
+
const close = () => { if (!saving) onClose() }
|
|
1607
|
+
const toggleSubject = (id) => setDraft((current) => ({ ...current,
|
|
1608
|
+
subjects: current.subjects.includes(id) ? current.subjects.filter((value) => value !== id) : [...current.subjects, id] }))
|
|
1426
1609
|
const toggleType = (type) => setTypes((current) => current.includes(type)
|
|
1427
1610
|
? current.filter((item) => item !== type)
|
|
1428
1611
|
: [...current, type])
|
|
@@ -1437,10 +1620,28 @@ window.__ModuleLoader__.load({
|
|
|
1437
1620
|
setCustomTypes((current) => current.filter((item) => item !== value))
|
|
1438
1621
|
setTypes((current) => current.filter((item) => item !== value))
|
|
1439
1622
|
}
|
|
1440
|
-
const
|
|
1623
|
+
const selectedTypes = [...new Set([...types, ...(typeDraft.trim() ? [typeDraft.trim().slice(0, 24)] : [])])]
|
|
1624
|
+
const canAdd = Boolean(name.trim()) && selectedTypes.length > 0
|
|
1625
|
+
const withCustomSubject = (current) => {
|
|
1626
|
+
const subject = { id: 'custom_' + newAttemptId(), name: name.trim(), types: selectedTypes }
|
|
1627
|
+
return { ...current, subjects: [...current.subjects, subject.id], customSubjects: [...current.customSubjects, subject] }
|
|
1628
|
+
}
|
|
1629
|
+
const finish = async () => {
|
|
1630
|
+
if (saving) return
|
|
1631
|
+
if ((name.trim() || types.length || typeDraft.trim()) && !canAdd) {
|
|
1632
|
+
setSaveError('请填写自定义科目名称并至少选择一个题型,或清空未完成的输入。')
|
|
1633
|
+
return
|
|
1634
|
+
}
|
|
1635
|
+
const next = canAdd ? withCustomSubject(draft) : draft
|
|
1636
|
+
setSaving(true)
|
|
1637
|
+
setSaveError('')
|
|
1638
|
+
try { await onDone(next) }
|
|
1639
|
+
catch (error) { setSaveError(error.message || '保存失败,请重试') }
|
|
1640
|
+
finally { setSaving(false) }
|
|
1641
|
+
}
|
|
1441
1642
|
const submit = () => {
|
|
1442
1643
|
if (!canAdd) return
|
|
1443
|
-
|
|
1644
|
+
setDraft(withCustomSubject)
|
|
1444
1645
|
setName('')
|
|
1445
1646
|
setTypes([])
|
|
1446
1647
|
setCustomTypes([])
|
|
@@ -1448,24 +1649,26 @@ window.__ModuleLoader__.load({
|
|
|
1448
1649
|
}
|
|
1449
1650
|
return h(Modal, {
|
|
1450
1651
|
title: T.manageSubjects,
|
|
1451
|
-
onClose,
|
|
1652
|
+
onClose: close,
|
|
1452
1653
|
wide: true,
|
|
1453
1654
|
footer: [
|
|
1454
|
-
h(Btn, { key: '
|
|
1655
|
+
h(Btn, { key: 'cancel', onClick: close, disabled: saving }, T.cancel),
|
|
1656
|
+
h(Btn, { key: 'done', variant: 'primary', onClick: finish, disabled: saving }, saving ? '保存中…' : '完成'),
|
|
1455
1657
|
],
|
|
1456
1658
|
},
|
|
1659
|
+
saveError ? h('div', { className: 'kd-error-text', role: 'alert' }, saveError) : null,
|
|
1660
|
+
h('fieldset', { disabled: saving, style: { border: 0, padding: 0, margin: 0, minWidth: 0 } },
|
|
1457
1661
|
h('div', { className: 'kd-meta', style: { marginBottom: '12px' } },
|
|
1458
|
-
'
|
|
1459
|
-
|
|
1662
|
+
'修改后点击右下角「完成」统一保存;取消或关闭会放弃修改。停用科目不会删除历史题目、作答、收藏和追问。'),
|
|
1663
|
+
subjects.map((subject) => h('div', {
|
|
1460
1664
|
key: subject.id,
|
|
1461
|
-
className: 'kd-row-between',
|
|
1462
|
-
style: { padding: '8px 0', borderBottom: '1px solid var(--dsw-alias-border-l1)' },
|
|
1665
|
+
className: 'kd-row-between kd-manage-card' + (enabled.has(subject.id) ? ' is-enabled' : ''),
|
|
1463
1666
|
},
|
|
1464
1667
|
h('label', { className: 'kd-checkline', style: { flex: '1 1 240px', cursor: 'pointer' } },
|
|
1465
1668
|
h('input', {
|
|
1466
1669
|
type: 'checkbox',
|
|
1467
1670
|
checked: enabled.has(subject.id),
|
|
1468
|
-
onChange: () => (
|
|
1671
|
+
onChange: () => toggleSubject(subject.id),
|
|
1469
1672
|
}),
|
|
1470
1673
|
h('span', null,
|
|
1471
1674
|
h('span', null, subject.name),
|
|
@@ -1475,24 +1678,19 @@ window.__ModuleLoader__.load({
|
|
|
1475
1678
|
? h('div', { className: 'kd-row' },
|
|
1476
1679
|
h('select', {
|
|
1477
1680
|
className: 'kd-select',
|
|
1478
|
-
value: subject.
|
|
1681
|
+
value: draft.subjectSource[subject.id] || 'auto',
|
|
1479
1682
|
'aria-label': `${subject.name} 出题来源`,
|
|
1480
|
-
onChange: (event) =>
|
|
1683
|
+
onChange: (event) => { const source = event.target.value; setDraft((current) => ({ ...current, subjectSource: { ...current.subjectSource, [subject.id]: source } })) },
|
|
1481
1684
|
},
|
|
1482
1685
|
h('option', { value: 'auto' }, '自动(优先题库)'),
|
|
1483
1686
|
h('option', { value: 'online' }, '在线出题'),
|
|
1484
1687
|
h('option', { value: 'bank' }, '题库出题')),
|
|
1485
1688
|
subject.bankItemCount
|
|
1486
1689
|
? h('span', { className: 'kd-msg-meta' }, `题库 ${subject.bankItemCount} 题`)
|
|
1487
|
-
: h('span', { className: 'kd-msg-meta' }, '题库为空')
|
|
1488
|
-
h(Btn, {
|
|
1489
|
-
small: true,
|
|
1490
|
-
variant: 'ghost',
|
|
1491
|
-
onClick: () => { onClose(); onPick(subject.id) },
|
|
1492
|
-
}, '查看'))
|
|
1690
|
+
: h('span', { className: 'kd-msg-meta' }, '题库为空'))
|
|
1493
1691
|
: null)),
|
|
1494
1692
|
h('div', { className: 'kd-divider' }),
|
|
1495
|
-
h('div', { className: 'kd-field' },
|
|
1693
|
+
h('div', { className: 'kd-field kd-custom-subject' },
|
|
1496
1694
|
h('div', { className: 'kd-field-label' }, '添加自定义科目'),
|
|
1497
1695
|
h('div', { className: 'kd-row' },
|
|
1498
1696
|
h('input', {
|
|
@@ -1543,7 +1741,7 @@ window.__ModuleLoader__.load({
|
|
|
1543
1741
|
}),
|
|
1544
1742
|
h(Btn, { small: true, disabled: !typeDraft.trim(), onClick: addCustomType }, '添加题型')),
|
|
1545
1743
|
h('div', { className: 'kd-msg-meta', style: { marginTop: '6px' } },
|
|
1546
|
-
canAdd ? `将添加:${name.trim()}(${types.join(' / ')})` : '填写科目名称并至少选择一个题型后即可添加')))
|
|
1744
|
+
canAdd ? `将添加:${name.trim()}(${types.join(' / ')})` : '填写科目名称并至少选择一个题型后即可添加'))))
|
|
1547
1745
|
}
|
|
1548
1746
|
|
|
1549
1747
|
function BanksModal({ boot, importResult, onClose, onPickFile, onDelete, onCommit, onCancelImport }) {
|
|
@@ -1601,8 +1799,7 @@ window.__ModuleLoader__.load({
|
|
|
1601
1799
|
(boot.banks || []).length
|
|
1602
1800
|
? boot.banks.map((bank) => h('div', {
|
|
1603
1801
|
key: bank.id,
|
|
1604
|
-
className: 'kd-row-between',
|
|
1605
|
-
style: { padding: '9px 0', borderBottom: '1px solid var(--dsw-alias-border-l1)' },
|
|
1802
|
+
className: 'kd-row-between kd-manage-card',
|
|
1606
1803
|
},
|
|
1607
1804
|
h('div', { style: { minWidth: 0 } },
|
|
1608
1805
|
h('div', null, bank.name),
|
|
@@ -1647,13 +1844,13 @@ window.__ModuleLoader__.load({
|
|
|
1647
1844
|
h('div', { className: 'kd-msg-meta' }, '确认后才会替换正式版本;旧版本会保留在版本历史中。'))
|
|
1648
1845
|
}
|
|
1649
1846
|
|
|
1650
|
-
function ConfirmModal({ title, message, confirmLabel, onConfirm, onClose, danger }) {
|
|
1847
|
+
function ConfirmModal({ title, message, confirmLabel, onConfirm, onClose, danger, busy }) {
|
|
1651
1848
|
return h(Modal, {
|
|
1652
1849
|
title,
|
|
1653
1850
|
onClose,
|
|
1654
1851
|
footer: [
|
|
1655
|
-
h(Btn, { key: 'cancel', onClick: onClose }, T.cancel),
|
|
1656
|
-
h(Btn, { key: 'ok', variant: danger ? 'danger' : 'primary', onClick: onConfirm }, confirmLabel || T.confirm),
|
|
1852
|
+
h(Btn, { key: 'cancel', onClick: onClose, disabled: busy }, T.cancel),
|
|
1853
|
+
h(Btn, { key: 'ok', variant: danger ? 'danger' : 'primary', onClick: onConfirm, disabled: busy }, busy ? '正在处理…' : (confirmLabel || T.confirm)),
|
|
1657
1854
|
],
|
|
1658
1855
|
}, h('div', { className: 'kd-md' }, message))
|
|
1659
1856
|
}
|
|
@@ -1670,15 +1867,27 @@ window.__ModuleLoader__.load({
|
|
|
1670
1867
|
const [thread, setThread] = useState(null)
|
|
1671
1868
|
const [list, setList] = useState(null)
|
|
1672
1869
|
const [busy, setBusy] = useState('')
|
|
1870
|
+
const [managing, setManaging] = useState(false)
|
|
1871
|
+
const [selected, setSelected] = useState([])
|
|
1872
|
+
const bulkLock = useRef(false)
|
|
1673
1873
|
const [fail, setFail] = useState('')
|
|
1674
1874
|
const [toast, setToast] = useState(null)
|
|
1675
1875
|
const [modal, setModal] = useState(null)
|
|
1676
1876
|
const [choice, setChoice] = useState('')
|
|
1677
1877
|
const [localRedo, setLocalRedo] = useState(false)
|
|
1678
1878
|
const [threadOpen, setThreadOpen] = useState(false)
|
|
1879
|
+
const [threadRatio, setThreadRatio] = useState(33)
|
|
1679
1880
|
const fileRef = useRef(null)
|
|
1680
1881
|
const attemptRef = useRef(newAttemptId())
|
|
1681
1882
|
const toastTimer = useRef(null)
|
|
1883
|
+
const mainScroll = useRef(null)
|
|
1884
|
+
const listScroll = useRef(0)
|
|
1885
|
+
const redoQuestions = useRef(new Set())
|
|
1886
|
+
|
|
1887
|
+
useEffect(() => {
|
|
1888
|
+
if (view === 'list' && list && mainScroll.current) mainScroll.current.scrollTop = listScroll.current
|
|
1889
|
+
if (view === 'detail' && mainScroll.current) mainScroll.current.scrollTop = 0
|
|
1890
|
+
}, [view, list])
|
|
1682
1891
|
|
|
1683
1892
|
const notify = useCallback((message, tone = 'ok') => {
|
|
1684
1893
|
setToast({ message, tone })
|
|
@@ -1702,6 +1911,7 @@ window.__ModuleLoader__.load({
|
|
|
1702
1911
|
if (!sid) return
|
|
1703
1912
|
// 高亮必须跟着视图走:从「全部 / 收藏 / 错题本」切回今日题目时要一起复位
|
|
1704
1913
|
setKind('today')
|
|
1914
|
+
setThreadOpen(false)
|
|
1705
1915
|
setView('detail')
|
|
1706
1916
|
setBusy('daily')
|
|
1707
1917
|
setFail('')
|
|
@@ -1713,6 +1923,7 @@ window.__ModuleLoader__.load({
|
|
|
1713
1923
|
try {
|
|
1714
1924
|
const result = await rpc('ensureDaily', { subjectId: sid })
|
|
1715
1925
|
setQuestion(result.question)
|
|
1926
|
+
setLocalRedo(redoQuestions.current.has(result.question.id))
|
|
1716
1927
|
setThread(result.thread || null)
|
|
1717
1928
|
} catch (issue) {
|
|
1718
1929
|
setFail(issue.message)
|
|
@@ -1723,7 +1934,9 @@ window.__ModuleLoader__.load({
|
|
|
1723
1934
|
|
|
1724
1935
|
const loadList = useCallback(async (sid, nextKind) => {
|
|
1725
1936
|
if (!sid) return
|
|
1937
|
+
listScroll.current = 0
|
|
1726
1938
|
setKind(nextKind)
|
|
1939
|
+
setThreadOpen(false)
|
|
1727
1940
|
setView('list')
|
|
1728
1941
|
setBusy('list')
|
|
1729
1942
|
setList(null)
|
|
@@ -1737,6 +1950,8 @@ window.__ModuleLoader__.load({
|
|
|
1737
1950
|
}
|
|
1738
1951
|
}, [notify])
|
|
1739
1952
|
|
|
1953
|
+
useEffect(() => { setSelected([]); setManaging(false) }, [subjectId, kind, view])
|
|
1954
|
+
|
|
1740
1955
|
const pickSubject = useCallback((sid) => {
|
|
1741
1956
|
if (!sid || sid === subjectId) return
|
|
1742
1957
|
setSubjectId(sid)
|
|
@@ -1746,13 +1961,17 @@ window.__ModuleLoader__.load({
|
|
|
1746
1961
|
}, [subjectId, loadToday])
|
|
1747
1962
|
|
|
1748
1963
|
const openQuestion = useCallback(async (qid) => {
|
|
1964
|
+
listScroll.current = mainScroll.current?.scrollTop || 0
|
|
1749
1965
|
setBusy('question')
|
|
1750
1966
|
try {
|
|
1751
1967
|
const result = await rpc('getQuestion', { qid })
|
|
1968
|
+
const canResume = result.isToday && result.question.answerState === 'revealed'
|
|
1969
|
+
&& result.question.attemptCount > 0 && !redoQuestions.current.has(qid)
|
|
1970
|
+
if (!canResume) redoQuestions.current.add(qid)
|
|
1752
1971
|
setQuestion(result.question)
|
|
1753
1972
|
setThread(result.thread || null)
|
|
1754
1973
|
setView('detail')
|
|
1755
|
-
setLocalRedo(
|
|
1974
|
+
setLocalRedo(!canResume)
|
|
1756
1975
|
setChoice('')
|
|
1757
1976
|
setThreadOpen(false)
|
|
1758
1977
|
attemptRef.current = newAttemptId()
|
|
@@ -1763,6 +1982,15 @@ window.__ModuleLoader__.load({
|
|
|
1763
1982
|
}
|
|
1764
1983
|
}, [notify])
|
|
1765
1984
|
|
|
1985
|
+
const backToList = async () => {
|
|
1986
|
+
setThreadOpen(false)
|
|
1987
|
+
setView('list')
|
|
1988
|
+
try {
|
|
1989
|
+
const result = await rpc('listQuestions', { subjectId, kind })
|
|
1990
|
+
setList(result)
|
|
1991
|
+
} catch (issue) { notify(issue.message, 'bad') }
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1766
1994
|
useEffect(() => {
|
|
1767
1995
|
let cancelled = false
|
|
1768
1996
|
rpc('bootstrap').then((result) => {
|
|
@@ -1798,6 +2026,7 @@ window.__ModuleLoader__.load({
|
|
|
1798
2026
|
attemptId: attemptRef.current,
|
|
1799
2027
|
})
|
|
1800
2028
|
setQuestion(result.question)
|
|
2029
|
+
redoQuestions.current.delete(question.id)
|
|
1801
2030
|
setLocalRedo(false)
|
|
1802
2031
|
refreshBoot()
|
|
1803
2032
|
} catch (issue) {
|
|
@@ -1938,67 +2167,18 @@ window.__ModuleLoader__.load({
|
|
|
1938
2167
|
}
|
|
1939
2168
|
}, [boot, notify, refreshBoot])
|
|
1940
2169
|
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
}],
|
|
1952
|
-
})
|
|
1953
|
-
}
|
|
1954
|
-
const result = await rpc('addSubject', { subjectId: targetId })
|
|
1955
|
-
const fresh = await refreshBoot()
|
|
1956
|
-
notify(`${subject.name} 已启用,系统会为它分配今天的题目`)
|
|
1957
|
-
if (fresh && result) {
|
|
1958
|
-
setSubjectId(targetId)
|
|
1959
|
-
loadToday(targetId)
|
|
1960
|
-
}
|
|
1961
|
-
setModal(null)
|
|
1962
|
-
} catch (issue) {
|
|
1963
|
-
notify(issue.message, 'bad')
|
|
1964
|
-
}
|
|
1965
|
-
}, [boot, notify, refreshBoot, loadToday])
|
|
1966
|
-
|
|
1967
|
-
const removeSubject = useCallback((subject) => {
|
|
1968
|
-
setModal({
|
|
1969
|
-
kind: 'confirm',
|
|
1970
|
-
title: `${T.deleteSubject}:${subject.name}`,
|
|
1971
|
-
message: `确认把「${subject.name}」移出每日出题吗?\n\n- 该科目已出过的题目、收藏、错题与作答记录都会保留在“全部”里。\n- 今天已经分配的题目不会丢失,重新启用后当天仍会复用同一道题。`,
|
|
1972
|
-
confirmLabel: T.deleteSubject,
|
|
1973
|
-
danger: true,
|
|
1974
|
-
onConfirm: async () => {
|
|
1975
|
-
setModal(null)
|
|
1976
|
-
try {
|
|
1977
|
-
const result = await rpc('removeSubject', { subjectId: subject.id })
|
|
1978
|
-
const fresh = await refreshBoot()
|
|
1979
|
-
notify(result.message || '已移出每日出题')
|
|
1980
|
-
if (fresh) {
|
|
1981
|
-
const next = fresh.settings.subjects[0] || null
|
|
1982
|
-
setSubjectId(next)
|
|
1983
|
-
if (next) loadToday(next)
|
|
1984
|
-
else { setQuestion(null); setView('empty') }
|
|
1985
|
-
}
|
|
1986
|
-
} catch (issue) {
|
|
1987
|
-
notify(issue.message, 'bad')
|
|
1988
|
-
}
|
|
1989
|
-
},
|
|
1990
|
-
})
|
|
1991
|
-
}, [notify, refreshBoot, loadToday])
|
|
1992
|
-
|
|
1993
|
-
const changeSource = useCallback(async (id, source) => {
|
|
1994
|
-
try {
|
|
1995
|
-
await rpc('saveSettings', { subjectSource: { [id]: source } })
|
|
1996
|
-
await refreshBoot()
|
|
1997
|
-
notify('出题来源已更新')
|
|
1998
|
-
} catch (issue) {
|
|
1999
|
-
notify(issue.message, 'bad')
|
|
2170
|
+
const saveSubjects = async (draft) => {
|
|
2171
|
+
const next = draft.subjects.includes(subjectId) ? subjectId : (draft.subjects[0] || null)
|
|
2172
|
+
const result = await rpc('saveSettings', { ...draft, activeSubject: next })
|
|
2173
|
+
setBoot((current) => ({ ...current, ...result, customSubjects: draft.customSubjects }))
|
|
2174
|
+
setModal(null)
|
|
2175
|
+
notify('科目设置已保存')
|
|
2176
|
+
if (next !== subjectId) {
|
|
2177
|
+
setSubjectId(next)
|
|
2178
|
+
if (next) loadToday(next)
|
|
2179
|
+
else { setQuestion(null); setThread(null); setThreadOpen(false); setView('empty') }
|
|
2000
2180
|
}
|
|
2001
|
-
}
|
|
2181
|
+
}
|
|
2002
2182
|
|
|
2003
2183
|
const pickFile = useCallback(() => {
|
|
2004
2184
|
if (fileRef.current) {
|
|
@@ -2063,6 +2243,38 @@ window.__ModuleLoader__.load({
|
|
|
2063
2243
|
})
|
|
2064
2244
|
}, [notify, refreshBoot])
|
|
2065
2245
|
|
|
2246
|
+
const bulkQuestions = async (action) => {
|
|
2247
|
+
if (bulkLock.current || !selected.length) return
|
|
2248
|
+
const qids = [...selected]
|
|
2249
|
+
const execute = async (confirmProtected = false) => {
|
|
2250
|
+
if (bulkLock.current) return
|
|
2251
|
+
bulkLock.current = true
|
|
2252
|
+
setBusy('bulk')
|
|
2253
|
+
try {
|
|
2254
|
+
const result = await rpc('bulkQuestions', { qids, action, confirmProtected })
|
|
2255
|
+
if (result.requiresConfirmation) {
|
|
2256
|
+
setModal({ kind: 'confirm', title: '再次确认删除收藏或错题', danger: true,
|
|
2257
|
+
message: `选中的题目中有 ${result.protectedCount} 道已收藏或在错题本中。继续将永久删除所选 ${qids.length} 道题及其作答和追问,无法恢复。确定仍要删除吗?`,
|
|
2258
|
+
confirmLabel: '仍要永久删除', onConfirm: () => execute(true) })
|
|
2259
|
+
return
|
|
2260
|
+
}
|
|
2261
|
+
setModal(null)
|
|
2262
|
+
setSelected([])
|
|
2263
|
+
await loadList(subjectId, kind)
|
|
2264
|
+
await refreshBoot()
|
|
2265
|
+
notify(`已处理 ${result.affected} 道题目`)
|
|
2266
|
+
} catch (issue) {
|
|
2267
|
+
setModal(null)
|
|
2268
|
+
notify(issue.message, 'bad')
|
|
2269
|
+
} finally { bulkLock.current = false; setBusy('') }
|
|
2270
|
+
}
|
|
2271
|
+
if (action === 'delete') {
|
|
2272
|
+
setModal({ kind: 'confirm', title: '删除所选题目', danger: true,
|
|
2273
|
+
message: `将永久删除 ${qids.length} 道题及其作答、收藏、错题标记和追问,无法恢复。题库原文件仍会保留,未来可能重新抽到其中的题目。`,
|
|
2274
|
+
confirmLabel: '确认删除', onConfirm: () => execute() })
|
|
2275
|
+
} else await execute()
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2066
2278
|
const counts = subjectId ? boot?.counts?.[subjectId] : null
|
|
2067
2279
|
const narrow = useMediaQuery('(max-width: 1180px)')
|
|
2068
2280
|
|
|
@@ -2127,6 +2339,11 @@ window.__ModuleLoader__.load({
|
|
|
2127
2339
|
kind,
|
|
2128
2340
|
subjectName: currentSubject?.name || '',
|
|
2129
2341
|
onOpen: openQuestion,
|
|
2342
|
+
managing, selected, busy: busy === 'bulk',
|
|
2343
|
+
onManage: () => { setManaging(!managing); setSelected([]) },
|
|
2344
|
+
onToggle: (id) => setSelected((ids) => ids.includes(id) ? ids.filter((value) => value !== id) : [...ids, id]),
|
|
2345
|
+
onSelectAll: (value) => setSelected(value ? list.items.map((item) => item.id) : []),
|
|
2346
|
+
onBulk: bulkQuestions,
|
|
2130
2347
|
})
|
|
2131
2348
|
}
|
|
2132
2349
|
if (view === 'detail' && question) {
|
|
@@ -2135,7 +2352,6 @@ window.__ModuleLoader__.load({
|
|
|
2135
2352
|
busy,
|
|
2136
2353
|
choice,
|
|
2137
2354
|
localRedo,
|
|
2138
|
-
narrow,
|
|
2139
2355
|
threadOpen,
|
|
2140
2356
|
onSelect: (key) => {
|
|
2141
2357
|
setChoice((current) => {
|
|
@@ -2150,7 +2366,9 @@ window.__ModuleLoader__.load({
|
|
|
2150
2366
|
onJudge: judge,
|
|
2151
2367
|
onFavorite: favorite,
|
|
2152
2368
|
onRedo: () => {
|
|
2369
|
+
redoQuestions.current.add(question.id)
|
|
2153
2370
|
setLocalRedo(true)
|
|
2371
|
+
setThreadOpen(false)
|
|
2154
2372
|
setChoice('')
|
|
2155
2373
|
attemptRef.current = newAttemptId()
|
|
2156
2374
|
},
|
|
@@ -2177,7 +2395,13 @@ window.__ModuleLoader__.load({
|
|
|
2177
2395
|
})
|
|
2178
2396
|
})()
|
|
2179
2397
|
|
|
2180
|
-
const
|
|
2398
|
+
const pickTab = (value) => {
|
|
2399
|
+
if (value === kind && (value === 'today' || view === 'list')) return
|
|
2400
|
+
if (value === 'today') loadToday(subjectId)
|
|
2401
|
+
else loadList(subjectId, value)
|
|
2402
|
+
}
|
|
2403
|
+
const showThread = view === 'detail' && question && threadOpen && !localRedo
|
|
2404
|
+
&& question.answerState === 'revealed' && question.attemptCount > 0
|
|
2181
2405
|
|
|
2182
2406
|
return h('div', { className: 'kd-page' },
|
|
2183
2407
|
styleInjected ? null : h('style', null, CSS),
|
|
@@ -2187,10 +2411,10 @@ window.__ModuleLoader__.load({
|
|
|
2187
2411
|
h('div', { className: 'kd-brand' }, h(SidebarIcon, { size: 18 })),
|
|
2188
2412
|
h('div', { style: { minWidth: 0 } },
|
|
2189
2413
|
h('h1', { className: 'kd-title' }, T.title),
|
|
2190
|
-
h('div', { className: 'kd-sub' },
|
|
2414
|
+
h('div', { className: 'kd-sub', title: T.subtitle },
|
|
2191
2415
|
boot?.today ? `${formatDate(boot.today)} · ` : '',
|
|
2192
2416
|
boot ? `已启用 ${boot.settings.subjects.length} 个科目 · ` : '',
|
|
2193
|
-
|
|
2417
|
+
'每天一道,稳步向前'))),
|
|
2194
2418
|
boot ? h(CountdownLines, { countdown: boot.countdown }) : null),
|
|
2195
2419
|
boot ? h(ExamStrip, {
|
|
2196
2420
|
boot,
|
|
@@ -2201,14 +2425,9 @@ window.__ModuleLoader__.load({
|
|
|
2201
2425
|
boot ? h(MobileBar, {
|
|
2202
2426
|
boot,
|
|
2203
2427
|
subjectId,
|
|
2204
|
-
kind,
|
|
2205
2428
|
onPick: pickSubject,
|
|
2206
2429
|
onManage: () => setModal({ kind: 'subjects' }),
|
|
2207
2430
|
onBanks: () => setModal({ kind: 'banks' }),
|
|
2208
|
-
onTab: (value) => {
|
|
2209
|
-
if (value === 'today') loadToday(subjectId)
|
|
2210
|
-
else loadList(subjectId, value)
|
|
2211
|
-
},
|
|
2212
2431
|
}) : null,
|
|
2213
2432
|
h('div', { className: 'kd-shell' },
|
|
2214
2433
|
boot ? h(SubjectNav, {
|
|
@@ -2219,27 +2438,30 @@ window.__ModuleLoader__.load({
|
|
|
2219
2438
|
onBanks: () => setModal({ kind: 'banks' }),
|
|
2220
2439
|
}) : null,
|
|
2221
2440
|
h('main', { className: 'kd-main' },
|
|
2222
|
-
h('div', { className: 'kd-main-
|
|
2441
|
+
h('div', { className: 'kd-main-toolbar' },
|
|
2223
2442
|
boot ? h(TabBar, {
|
|
2224
2443
|
kind,
|
|
2225
2444
|
counts,
|
|
2226
|
-
onPick:
|
|
2227
|
-
if (value === 'today') loadToday(subjectId)
|
|
2228
|
-
else loadList(subjectId, value)
|
|
2229
|
-
},
|
|
2445
|
+
onPick: pickTab,
|
|
2230
2446
|
}) : null,
|
|
2231
|
-
|
|
2232
|
-
|
|
2447
|
+
kind !== 'today' && view === 'detail'
|
|
2448
|
+
? h(Btn, { small: true, onClick: backToList, title: '返回列表', ariaLabel: '返回列表' }, h(Icon, { name: 'chevron-left', size: 16 })) : null),
|
|
2449
|
+
h('div', { className: 'kd-main-scroll', ref: mainScroll },
|
|
2450
|
+
h('div', { className: 'kd-main-inner' },
|
|
2451
|
+
h('div', { className: 'kd-view', key: `${subjectId}|${kind}|${view}|${question?.id || ''}` }, mainContent)))),
|
|
2452
|
+
h(Presence, null, showThread
|
|
2233
2453
|
? h(ThreadPane, {
|
|
2234
2454
|
thread,
|
|
2235
2455
|
questionId: question.id,
|
|
2236
2456
|
busy,
|
|
2237
2457
|
narrow,
|
|
2458
|
+
ratio: threadRatio,
|
|
2459
|
+
onRatioChange: setThreadRatio,
|
|
2238
2460
|
onSend: sendFollowup,
|
|
2239
2461
|
onClear: clearThread,
|
|
2240
2462
|
onClose: () => setThreadOpen(false),
|
|
2241
2463
|
})
|
|
2242
|
-
: null),
|
|
2464
|
+
: null)),
|
|
2243
2465
|
h('input', {
|
|
2244
2466
|
ref: fileRef,
|
|
2245
2467
|
type: 'file',
|
|
@@ -2247,14 +2469,12 @@ window.__ModuleLoader__.load({
|
|
|
2247
2469
|
style: { display: 'none' },
|
|
2248
2470
|
onChange: onFile,
|
|
2249
2471
|
}),
|
|
2472
|
+
h(Presence, null,
|
|
2250
2473
|
modal?.kind === 'subjects' && boot
|
|
2251
2474
|
? h(SubjectsModal, {
|
|
2252
2475
|
boot,
|
|
2253
2476
|
onClose: () => setModal(null),
|
|
2254
|
-
|
|
2255
|
-
onRemove: removeSubject,
|
|
2256
|
-
onSource: changeSource,
|
|
2257
|
-
onPick: (id) => { setModal(null); pickSubject(id) },
|
|
2477
|
+
onDone: saveSubjects,
|
|
2258
2478
|
})
|
|
2259
2479
|
: null,
|
|
2260
2480
|
modal?.kind === 'banks' && boot
|
|
@@ -2276,13 +2496,16 @@ window.__ModuleLoader__.load({
|
|
|
2276
2496
|
onDiscard: discardRevision,
|
|
2277
2497
|
}) : null,
|
|
2278
2498
|
modal?.kind === 'confirm' ? h(ConfirmModal, {
|
|
2499
|
+
key: modal.title,
|
|
2279
2500
|
title: modal.title,
|
|
2280
2501
|
message: modal.message,
|
|
2281
2502
|
confirmLabel: modal.confirmLabel,
|
|
2282
2503
|
danger: modal.danger,
|
|
2504
|
+
busy: busy === 'bulk',
|
|
2283
2505
|
onConfirm: modal.onConfirm,
|
|
2284
|
-
onClose: () => setModal(null),
|
|
2506
|
+
onClose: () => { if (!bulkLock.current) setModal(null) },
|
|
2285
2507
|
}) : null,
|
|
2508
|
+
),
|
|
2286
2509
|
toast ? h('div', { className: `kd-toast kd-toast-${toast.tone}` }, toast.message) : null,
|
|
2287
2510
|
busy === 'import' || busy === 'commit'
|
|
2288
2511
|
? h('div', { className: 'kd-loading', style: { position: 'absolute', inset: 'auto 0 60px 0' } },
|