@lancc/cmark 1.1.0 → 1.2.1

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 (139) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -21
  3. package/README.md +54 -82
  4. package/assets/abnfDiagram-WEYOIA3C-CKV_7Upb.js +1 -0
  5. package/assets/architectureDiagram-ZHVYKT53-BvLudDvk.js +1 -0
  6. package/assets/blockDiagram-RJAE7QA3--gzkMGi2.js +1 -0
  7. package/assets/c4Diagram-XDPPPE54-ByfRiJGR.js +1 -0
  8. package/assets/chunk-2E7WHVMD-TfbC_dmh.js +1 -0
  9. package/assets/chunk-2Q5K7J3B-0YJgChVo.js +1 -0
  10. package/assets/chunk-4EMNKR2T-DHoYW7g5.js +1 -0
  11. package/assets/chunk-55XCU76K-Cq25Yw_G.js +1 -0
  12. package/assets/chunk-5VM5RSS4-CJaRXFHK.js +1 -0
  13. package/assets/chunk-JWPE2WC7-BP2iXBhe.js +1 -0
  14. package/assets/chunk-VQ5W62QI-B95zur4F.js +1 -0
  15. package/assets/chunk-XXDRQBXY-Ct0MhvQo.js +1 -0
  16. package/assets/chunk-ZCHMQSOR-C5VhW-wL.js +2 -0
  17. package/assets/classDiagram-v2-4MYWR3UA-C1G-Vkj0.js +1 -0
  18. package/assets/cose-bilkent-JH36ORCC-qIQoIBv0.js +1 -0
  19. package/assets/cynefin-EF2NZ3EQ-CR1E3YiZ.js +1 -0
  20. package/assets/cynefinDiagram-WF24UDRP-DaJFvZ3b.js +1 -0
  21. package/assets/cytoscape.esm-SjxgSg6I.js +9 -0
  22. package/assets/dagre-NK4GIZVX-BFRQhpii.js +1 -0
  23. package/assets/diagram-CBK3MWTC-DlueL6XZ.js +1 -0
  24. package/assets/diagram-I5XIIV2D-bITyafYX.js +1 -0
  25. package/assets/diagram-ODIL2UCW-Dlb4hg_-.js +1 -0
  26. package/assets/diagram-PFBEJUXE-CRfen0O7.js +1 -0
  27. package/assets/diagram-RJA3I4VV-CK-FlhGV.js +1 -0
  28. package/assets/diagram-TYUNFSNR-DmgVsxqa.js +1 -0
  29. package/assets/ebnfDiagram-V4P3TTPT-B9KGI9eQ.js +1 -0
  30. package/assets/echarts-CytsvCdp.js +25 -0
  31. package/assets/elk-QONTBCTQ-VgDftvsU.js +1 -0
  32. package/assets/erDiagram-KYQIGQAA-DkpP7sS4.js +1 -0
  33. package/assets/flowDiagram-IUGLLWBU-CRXa9Kxy.js +1 -0
  34. package/assets/ganttDiagram-R7GPNTJY-Cu2I55Wb.js +1 -0
  35. package/assets/gitGraphDiagram-PDYL7XHY-CwSo663I.js +1 -0
  36. package/assets/graph-Dz7AM1Q5.js +1 -0
  37. package/assets/index-BJ-NdQc4.js +100 -0
  38. package/assets/index-eB_q_VA3.css +1 -0
  39. package/assets/infoDiagram-MVDRKAX5-D3Qknzuo.js +1 -0
  40. package/assets/ishikawaDiagram-HPEHV2GE-9qYT75tk.js +1 -0
  41. package/assets/journeyDiagram-BOSHW7O7-DeSrJg9o.js +1 -0
  42. package/assets/kanban-definition-FNSA4HC5-DINkB6G7.js +1 -0
  43. package/assets/katex-DN8cTsI3.js +1 -0
  44. package/assets/markdown-S2mWvoE-.js +5 -0
  45. package/assets/mermaid-T_6Ze_cT.js +7 -0
  46. package/assets/mindmap-definition-6OY57UG3-DW5hqiAe.js +1 -0
  47. package/assets/pegDiagram-ECIUJNBX-BWy_tkzy.js +1 -0
  48. package/assets/pieDiagram-GNMPY74T-CniJbr6l.js +1 -0
  49. package/assets/quadrantDiagram-D57K4MC3-BVnDJkCv.js +1 -0
  50. package/assets/railroadDiagram-Y72NRBL7-BNKZpJJb.js +1 -0
  51. package/assets/requirementDiagram-EQYSW5RA-BETipHj0.js +1 -0
  52. package/assets/sankeyDiagram-SY24JHO5-ByEYqT7T.js +1 -0
  53. package/assets/sequenceDiagram-BLEHNHQU-BqCJ2tKG.js +1 -0
  54. package/assets/sizeCapture-INFHLROL-DlhrWFj-.js +1 -0
  55. package/assets/stateDiagram-v2-A6IESKWZ-DJUBWLTP.js +1 -0
  56. package/assets/swimlanes-B3ENRKFQ-D7k7tF5U.js +1 -0
  57. package/assets/swimlanesDiagram-AYQRV4AK-DoQBgB9o.js +1 -0
  58. package/assets/timeline-definition-JXYKGLGB-BCNhwnn6.js +1 -0
  59. package/assets/usecaseDiagram-WPOLBNNX-DRa_Rgl3.js +1 -0
  60. package/assets/vennDiagram-Q3Y3IDCW-Ba2eaO68.js +1 -0
  61. package/assets/wardleyDiagram-IVAIDKWA-W_i9vzY-.js +1 -0
  62. package/assets/xychartDiagram-4URNHDU4-ByfaGjvc.js +1 -0
  63. package/export-file/github-markdown.css +549 -549
  64. package/export-file/index.html +32 -32
  65. package/index.html +4 -4
  66. package/logo.png +0 -0
  67. package/package.json +1 -1
  68. package/plugin.json +8 -218
  69. package/preload/branch-bar.js +44 -2
  70. package/preload/export-css.js +59 -0
  71. package/preload/git-panel.js +102 -23
  72. package/preload/git-service.js +99 -6
  73. package/preload/host.js +130 -0
  74. package/preload/remote-setup.js +425 -0
  75. package/preload/remote-wizard.js +330 -0
  76. package/preload/rename-note.js +4 -5
  77. package/preload/services.js +6 -15
  78. package/preload/settings-panel.js +468 -356
  79. package/preload/ws-prefs.js +55 -0
  80. package/preload.js +291 -6
  81. package/assets/abnfDiagram-WEYOIA3C-BgXsTF7a.js +0 -1
  82. package/assets/architectureDiagram-ZHVYKT53-BsCWa_qC.js +0 -36
  83. package/assets/blockDiagram-RJAE7QA3-wbIxEiYN.js +0 -140
  84. package/assets/c4Diagram-XDPPPE54-5zdyxFvA.js +0 -38
  85. package/assets/chunk-2E7WHVMD-F85Lca-i.js +0 -88
  86. package/assets/chunk-2Q5K7J3B-CjpAZ0OI.js +0 -1
  87. package/assets/chunk-4EMNKR2T-C8Kbh83c.js +0 -1
  88. package/assets/chunk-55XCU76K-BINcFiLN.js +0 -1
  89. package/assets/chunk-5VM5RSS4-Diy5h2LS.js +0 -15
  90. package/assets/chunk-JWPE2WC7-BXnjDdh1.js +0 -1
  91. package/assets/chunk-VQ5W62QI-BM7ZZo1I.js +0 -1
  92. package/assets/chunk-XXDRQBXY-CW8CjB1w.js +0 -1
  93. package/assets/chunk-ZCHMQSOR-C7ATz1L1.js +0 -2
  94. package/assets/classDiagram-v2-4MYWR3UA-CMoqNk-b.js +0 -217
  95. package/assets/cose-bilkent-JH36ORCC-6rqR5iJk.js +0 -1
  96. package/assets/cynefin-EF2NZ3EQ-DypLIJxl.js +0 -141
  97. package/assets/cynefinDiagram-WF24UDRP-8b2CEBlR.js +0 -62
  98. package/assets/cytoscape.esm-CjX6Hyah.js +0 -331
  99. package/assets/dagre-NK4GIZVX-Dv6vuUbR.js +0 -4
  100. package/assets/diagram-CBK3MWTC-DJgMDeYd.js +0 -24
  101. package/assets/diagram-I5XIIV2D-BB9cMoRW.js +0 -24
  102. package/assets/diagram-ODIL2UCW-BVcRtsdy.js +0 -30
  103. package/assets/diagram-PFBEJUXE-BbGSOtJx.js +0 -3
  104. package/assets/diagram-RJA3I4VV-CzkY_8Fo.js +0 -41
  105. package/assets/diagram-TYUNFSNR-YTl5QdfL.js +0 -200
  106. package/assets/ebnfDiagram-V4P3TTPT-COVjPAjE.js +0 -1
  107. package/assets/echarts-BAPDkhe3.js +0 -61
  108. package/assets/elk-QONTBCTQ-DHsjY8Z_.js +0 -27
  109. package/assets/erDiagram-KYQIGQAA-CUAbTUl1.js +0 -99
  110. package/assets/flowDiagram-IUGLLWBU-CWsiqTQC.js +0 -213
  111. package/assets/ganttDiagram-R7GPNTJY-DetBW0Wm.js +0 -292
  112. package/assets/gitGraphDiagram-PDYL7XHY-ByrdgJmw.js +0 -106
  113. package/assets/graph-D8UmR1vK.js +0 -1
  114. package/assets/index-BLONSItV.css +0 -1
  115. package/assets/index-Cz6qaZbV.js +0 -744
  116. package/assets/infoDiagram-MVDRKAX5-BM48fjhU.js +0 -2
  117. package/assets/ishikawaDiagram-HPEHV2GE-DtHYywHZ.js +0 -70
  118. package/assets/journeyDiagram-BOSHW7O7-DJ1LiRh3.js +0 -139
  119. package/assets/kanban-definition-FNSA4HC5-DPPt8pwJ.js +0 -89
  120. package/assets/katex-CQL9J12G.js +0 -257
  121. package/assets/markdown-DVehI1Sg.js +0 -314
  122. package/assets/mermaid-C_q9pug0.js +0 -312
  123. package/assets/mindmap-definition-6OY57UG3-zbqYOEY6.js +0 -98
  124. package/assets/pegDiagram-ECIUJNBX-e7QG7TMq.js +0 -1
  125. package/assets/pieDiagram-GNMPY74T-Duf6YP0N.js +0 -39
  126. package/assets/quadrantDiagram-D57K4MC3-B2eYoUUy.js +0 -7
  127. package/assets/railroadDiagram-Y72NRBL7-BWCzPcI_.js +0 -1
  128. package/assets/requirementDiagram-EQYSW5RA-HhPMEqB1.js +0 -84
  129. package/assets/sankeyDiagram-SY24JHO5-CMUTwFBB.js +0 -40
  130. package/assets/sequenceDiagram-BLEHNHQU-C9wR2ll7.js +0 -169
  131. package/assets/sizeCapture-INFHLROL-DXfz37S6.js +0 -1
  132. package/assets/stateDiagram-v2-A6IESKWZ-D_gtonhE.js +0 -291
  133. package/assets/swimlanes-B3ENRKFQ-DdJOuuMV.js +0 -1
  134. package/assets/swimlanesDiagram-AYQRV4AK-BWt3HxHV.js +0 -8
  135. package/assets/timeline-definition-JXYKGLGB-DQsdwJe-.js +0 -120
  136. package/assets/usecaseDiagram-WPOLBNNX-DTRyFq7d.js +0 -370
  137. package/assets/vennDiagram-Q3Y3IDCW-dihYszm0.js +0 -34
  138. package/assets/wardleyDiagram-IVAIDKWA-BoPpgYfK.js +0 -78
  139. package/assets/xychartDiagram-4URNHDU4-DoLjYxeB.js +0 -7
@@ -42,8 +42,7 @@ module.exports = function installGitPanel(win, WS) {
42
42
  --zgp-hunk:#ddf4ff; --zgp-hunk-fg:#0969da;
43
43
  --zgp-shadow:0 8px 32px rgba(31,35,40,.14),0 2px 8px rgba(31,35,40,.08);
44
44
  }
45
- @media (prefers-color-scheme:dark){
46
- :root{
45
+ :root[data-theme='dark']{
47
46
  --zgp-bg:#161b22; --zgp-bg2:#1c2128; --zgp-bg3:#22272e;
48
47
  --zgp-fg:#e6edf3; --zgp-fg2:#9aa4b2; --zgp-fg3:#6e7681;
49
48
  --zgp-bd:#30363d; --zgp-bd2:#3d444d;
@@ -53,7 +52,6 @@ module.exports = function installGitPanel(win, WS) {
53
52
  --zgp-del:#2d1214; --zgp-del-fg:#f85149;
54
53
  --zgp-hunk:#0c2d4a; --zgp-hunk-fg:#58a6ff;
55
54
  --zgp-shadow:0 8px 32px rgba(0,0,0,.6),0 2px 8px rgba(0,0,0,.4);
56
- }
57
55
  }
58
56
  #zgp-btn{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:500;padding:3px 10px;
59
57
  border:1px solid var(--zgp-bd2);border-radius:6px;background:var(--zgp-bg);color:var(--zgp-fg2);
@@ -128,7 +126,7 @@ module.exports = function installGitPanel(win, WS) {
128
126
  .zgp-st.D{background:var(--zgp-del);color:var(--zgp-del-fg);}
129
127
  .zgp-st.Q{background:var(--zgp-bg3);color:var(--zgp-fg3);}
130
128
  .zgp-st.U{background:var(--zgp-del);color:var(--zgp-del-fg);}
131
- @media (prefers-color-scheme:dark){.zgp-st.M{background:#3a2d00;color:#d29922;}}
129
+ :root[data-theme='dark'] .zgp-st.M{background:#3a2d00;color:#d29922;}
132
130
  .zgp-fp{flex:1;min-width:0;font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
133
131
  font-family:Consolas,Menlo,monospace;}
134
132
  .zgp-fp span{unicode-bidi:normal;}
@@ -219,7 +217,8 @@ module.exports = function installGitPanel(win, WS) {
219
217
  .zgp-jumpbar button{padding:3px 10px;font-size:11.5px;border-radius:6px;cursor:pointer;
220
218
  border:1px solid var(--zgp-bd2);background:var(--zgp-bg);color:var(--zgp-fg2);}
221
219
  .zgp-jumpbar button:hover{background:var(--zgp-acc-bg);border-color:var(--zgp-acc);color:var(--zgp-acc);}
222
- .zgp-dl.zgp-flash{outline:2px solid var(--zgp-acc);outline-offset:-2px;}
220
+ .zgp-dl.zgp-flash{outline:none !important;border:none !important;background:rgba(9,105,218,.16) !important;}
221
+ :root[data-theme='dark'] .zgp-dl.zgp-flash{background:rgba(88,166,255,.30) !important;}
223
222
 
224
223
  .zgp-cf-ov{position:absolute;inset:0;background:rgba(0,0,0,.38);display:flex;align-items:center;
225
224
  justify-content:center;z-index:20;padding:20px;}
@@ -458,6 +457,38 @@ module.exports = function installGitPanel(win, WS) {
458
457
  })
459
458
  }
460
459
 
460
+ /** 找到元素最近的可滚动祖先(弹窗是 vbody 自己滚,内嵌是面板 body 滚) */
461
+ function scrollHostOf (el) {
462
+ var n = el
463
+ while (n && n !== doc.body) {
464
+ var cs = win.getComputedStyle(n)
465
+ if (/(auto|scroll)/.test(cs.overflowY) && n.scrollHeight > n.clientHeight + 4) return n
466
+ n = n.parentElement
467
+ }
468
+ return el
469
+ }
470
+
471
+ /**
472
+ * diff 打开后默认落到第一处改动:把第一行 add/del 滚到视口约 1/3 高度并闪一下,
473
+ * 免得长文件每次都要自己往下翻。
474
+ */
475
+ function focusFirstChange (body) {
476
+ if (!body) return
477
+ var first = body.querySelector('.zgp-dl.add, .zgp-dl.del')
478
+ if (!first) return
479
+ var host = scrollHostOf(body)
480
+ try {
481
+ var hr = host.getBoundingClientRect()
482
+ var er = first.getBoundingClientRect()
483
+ var delta = er.top - hr.top
484
+ host.scrollTop = Math.max(0, host.scrollTop + delta - Math.round(host.clientHeight / 3))
485
+ } catch (e) {
486
+ try { first.scrollIntoView({ block: 'center' }) } catch (e2) {}
487
+ }
488
+ first.classList.add('zgp-flash')
489
+ win.setTimeout(function () { first.classList.remove('zgp-flash') }, 1200)
490
+ }
491
+
461
492
  // diff 渲染:带行号
462
493
  function renderDiffLines (lines) {
463
494
  let oldNo = 0, newNo = 0
@@ -512,7 +543,12 @@ module.exports = function installGitPanel(win, WS) {
512
543
  body.innerHTML = '<span class="zgp-dl">' + esc(content || '(空)') + '</span>'
513
544
  }
514
545
  view.classList.add('on')
515
- body.scrollTop = 0
546
+ if (isDiff) {
547
+ // 必须先让弹窗可见再算位置,隐藏时 rect 全是 0
548
+ focusFirstChange(body)
549
+ } else {
550
+ body.scrollTop = 0
551
+ }
516
552
  }
517
553
 
518
554
  // ============ 当前笔记 ============
@@ -531,7 +567,12 @@ module.exports = function installGitPanel(win, WS) {
531
567
  ensureStatusContainers()
532
568
  const box = $('zgp-status')
533
569
  if (!st || !st.isRepo) {
534
- box.innerHTML = '<span class="zgp-chip warn">尚未初始化 git 仓库</span>'
570
+ // 只换 chips 的内容,绝不整体覆盖 .zgp-status:
571
+ // 整体覆盖会把 chips/acts/fileact 三个容器一起删掉,之后自愈重建时
572
+ // 散落在状态条里的旧节点会留下来,表现为「工作区变更记录」里混着
573
+ // 单个文档的 chip(两套状态条叠在一起)
574
+ const chipsBox0 = box.querySelector('#zgp-chips')
575
+ if (chipsBox0) chipsBox0.innerHTML = '<span class="zgp-chip warn">尚未初始化 git 仓库</span>'
535
576
  const foot0 = doc.querySelector('.zgp-acts')
536
577
  const oldE = doc.getElementById('zgp-foot-extra')
537
578
  if (oldE) oldE.remove()
@@ -574,7 +615,7 @@ module.exports = function installGitPanel(win, WS) {
574
615
  if (!ok) return
575
616
  const r = await G.abortInProgress()
576
617
  toast(r.ok ? ('已中止 ' + st.inProgress) : ('中止失败:' + r.message), r.ok ? 'ok' : 'err')
577
- refresh()
618
+ refreshAfterAction()
578
619
  }
579
620
  wrap.appendChild(b)
580
621
  }
@@ -585,7 +626,7 @@ module.exports = function installGitPanel(win, WS) {
585
626
  b2.onclick = async () => {
586
627
  const r = await G.clearStaleLock()
587
628
  toast(r.ok ? '已清理陈旧 index.lock' : ('清理失败:' + r.message), r.ok ? 'ok' : 'err')
588
- refresh()
629
+ refreshAfterAction()
589
630
  }
590
631
  wrap.appendChild(b2)
591
632
  }
@@ -664,7 +705,7 @@ module.exports = function installGitPanel(win, WS) {
664
705
  if (!rel) {
665
706
  const chips0 = box.querySelector('#zgp-chips')
666
707
  if (chips0) chips0.innerHTML = '<span class="zgp-chip warn">没有打开的笔记</span>'
667
- else box.innerHTML = '<span class="zgp-chip warn">没有打开的笔记</span>'
708
+ else ensureStatusContainers()
668
709
  const pane = $('zgp-pane-changes')
669
710
  if (pane) pane.innerHTML = '<div class="zgp-empty"><b>没有打开的笔记</b>打开一篇笔记后,这里显示它的改动与历史</div>'
670
711
  const hp = $('zgp-pane-history')
@@ -677,10 +718,11 @@ module.exports = function installGitPanel(win, WS) {
677
718
  const chipsHtml = '<span class="zgp-chip acc">' + esc(displayPath(rel)) + '</span>'
678
719
  + (file ? '<span class="zgp-chip warn">有未提交改动</span>' : '<span class="zgp-chip ok">与最近提交一致</span>')
679
720
  + (readErr ? '<span class="zgp-chip err">' + esc(readErr.slice(0, 40)) + '</span>' : '')
680
- // 只更新 chips,不要覆盖整个状态条(否则会删掉右侧操作区与容器)
721
+ // 只更新 chips,不要覆盖整个状态条(否则会删掉右侧操作区与容器)。
722
+ // 容器缺失时先自愈重建,而不是退回「直接写 .zgp-status」那种会留残骸的兜底。
723
+ ensureStatusContainers()
681
724
  const chipsBoxF = box.querySelector('#zgp-chips')
682
725
  if (chipsBoxF) chipsBoxF.innerHTML = chipsHtml
683
- else box.innerHTML = chipsHtml
684
726
  renderFileChanges(rel, file)
685
727
  if (activeTab === 'history') renderHistory(true)
686
728
  }
@@ -720,7 +762,7 @@ module.exports = function installGitPanel(win, WS) {
720
762
  if (r.ok) {
721
763
  toast('已推送:' + (r.data.steps.join(' · ') || '无改动'), 'ok')
722
764
  logCache = null
723
- refresh()
765
+ refreshAfterAction()
724
766
  } else toast('推送失败:' + hintOf(r), 'err')
725
767
  }
726
768
  }
@@ -732,7 +774,7 @@ module.exports = function installGitPanel(win, WS) {
732
774
  toast('已还原到最近一次提交', 'ok')
733
775
  logCache = null
734
776
  try { if (win.__znotesReloadNote) win.__znotesReloadNote(rel) } catch (e) {}
735
- refresh()
777
+ refreshAfterAction()
736
778
  } else if (rr.kind !== 'cancelled') {
737
779
  toast('还原失败:' + hintOf(rr), 'err')
738
780
  }
@@ -758,6 +800,8 @@ module.exports = function installGitPanel(win, WS) {
758
800
  const lines = stripDiffHeader(diff)
759
801
  body.innerHTML = renderDiffLines(lines)
760
802
  mountJumpBar(body)
803
+ // 首次渲染才跳;后续轮询刷新(内容没变)不打断用户当前阅读位置
804
+ if (!existedBody) focusFirstChange(body)
761
805
  }
762
806
 
763
807
  function renderChanges(st) {
@@ -806,7 +850,7 @@ module.exports = function installGitPanel(win, WS) {
806
850
  // 编辑器内部可能残留旧内容并触发自动保存,把还原结果又写回磁盘;
807
851
  // 整页刷新是唯一可靠的做法(还原属低频操作)
808
852
  try { if (win.__znotesReloadNote) win.__znotesReloadNote(f.path) } catch (err) {}
809
- refresh()
853
+ refreshAfterAction()
810
854
  } else if (r.kind !== 'cancelled') {
811
855
  toast('还原失败:' + hintOf(r), 'err')
812
856
  }
@@ -938,7 +982,7 @@ module.exports = function installGitPanel(win, WS) {
938
982
  + '<button class="zgp-ic" data-cmpprev="' + i + '" title="与提交前比对:这一次提交本身对这篇笔记做了什么改动">' + ICON.cmpPrev + '</button>'
939
983
  + '<button class="zgp-ic" data-view="' + i + '" title="查看该版本:显示这次提交时这篇笔记的完整内容">' + ICON.eye + '</button>'
940
984
  + '<span style="flex:1"></span>'
941
- + '<button class="zgp-ic danger" data-back="' + i + '" title="回溯到此版本:把这篇笔记恢复成这个版本的内容(改动前自动备份)">' + ICON.undo + '</button>'
985
+ + '<button class="zgp-ic danger" data-back="' + i + '" title="回溯到此版本:把这篇笔记恢复成这个版本的内容">' + ICON.undo + '</button>'
942
986
  + '</div>'
943
987
  : ''
944
988
  return '<div class="zgp-cm" data-c="' + i + '">'
@@ -987,7 +1031,7 @@ module.exports = function installGitPanel(win, WS) {
987
1031
  toast('已回溯到 ' + c.short, 'ok')
988
1032
  logCache = null
989
1033
  try { if (win.__znotesReloadNote) win.__znotesReloadNote(onlyFile) } catch (e) {}
990
- refresh()
1034
+ refreshAfterAction()
991
1035
  } else if (r.kind !== 'cancelled') {
992
1036
  toast('回溯失败:' + hintOf(r), 'err')
993
1037
  }
@@ -1019,7 +1063,7 @@ module.exports = function installGitPanel(win, WS) {
1019
1063
  + '<span class="zgp-fs ' + (f.status || '').charAt(0) + '">' + (f.status || '').charAt(0) + '</span>'
1020
1064
  + '<span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + esc(f.path) + '">' + esc(f.path) + '</span>'
1021
1065
  + '<button class="zgp-ic" title="查看该次改动">' + ICON.diff + '</button>'
1022
- + '<button class="zgp-ic danger" data-restore="1" title="回溯到该版本(先备份)">' + ICON.undo + '</button>'
1066
+ + '<button class="zgp-ic danger" data-restore="1" title="回溯到该版本">' + ICON.undo + '</button>'
1023
1067
  + '</div>'
1024
1068
  }).join('')
1025
1069
  }
@@ -1042,7 +1086,7 @@ module.exports = function installGitPanel(win, WS) {
1042
1086
  toast('已回溯到 ' + hash.slice(0, 7), 'ok')
1043
1087
  logCache = null
1044
1088
  try { if (win.__znotesReloadNote) win.__znotesReloadNote(p) } catch (err) {}
1045
- refresh()
1089
+ refreshAfterAction()
1046
1090
  } else if (r3.kind !== 'cancelled') {
1047
1091
  toast('回溯失败:' + hintOf(r3), 'err')
1048
1092
  }
@@ -1101,6 +1145,23 @@ module.exports = function installGitPanel(win, WS) {
1101
1145
  }
1102
1146
  }
1103
1147
 
1148
+ /**
1149
+ * 操作(提交/推送/拉取/还原/中止)完成后的刷新。
1150
+ *
1151
+ * 不能只调 refresh():① 它带签名去重,读到和上次一样的中间状态会直接返回;
1152
+ * ② git 命令返回时工作区/索引可能还没完全落稳,第一次读到的往往还是旧状态。
1153
+ * 所以先强制刷一次,再补几次重试,同时把历史页和顶部分支条一起拉新。
1154
+ */
1155
+ function refreshAfterAction() {
1156
+ try { refresh(true) } catch (e) {}
1157
+ ;[120, 400, 1000, 2200].forEach(function (ms) {
1158
+ setTimeout(function () { try { refresh(true) } catch (e) {} }, ms)
1159
+ })
1160
+ try { logCache = null } catch (e) {}
1161
+ try { if (activeTab === 'history') renderHistory(true) } catch (e) {}
1162
+ try { if (win.__zbbRefresh) win.__zbbRefresh() } catch (e) {}
1163
+ }
1164
+
1104
1165
  // ============ 事件 ============
1105
1166
  let activeTab = 'changes'
1106
1167
  let panelMode = 'global' // global:全部改动/历史;file:只看当前笔记
@@ -1131,6 +1192,10 @@ module.exports = function installGitPanel(win, WS) {
1131
1192
  const fAct = root.querySelector('#zgp-fileact')
1132
1193
  if (fAct) fAct.style.display = isFile ? 'flex' : 'none'
1133
1194
  if (!isFile && fAct) fAct.innerHTML = ''
1195
+ // 切模式时先清一遍 chips:refresh 有签名去重,命中时会直接 return,
1196
+ // 上一模式的 chip(全局的「分支 main/工作区干净」或单文件的「路径」)就会留在新面板里
1197
+ const chipsEl = doc.getElementById('zgp-chips')
1198
+ if (chipsEl) chipsEl.innerHTML = ''
1134
1199
  }
1135
1200
 
1136
1201
  doc.querySelectorAll('.zgp-tab').forEach((t) => {
@@ -1212,7 +1277,7 @@ module.exports = function installGitPanel(win, WS) {
1212
1277
  } else {
1213
1278
  toast('同步失败:' + hintOf(r), 'err')
1214
1279
  }
1215
- refresh()
1280
+ refreshAfterAction()
1216
1281
  }
1217
1282
 
1218
1283
  const pushBtn = $('zgp-push')
@@ -1221,7 +1286,7 @@ module.exports = function installGitPanel(win, WS) {
1221
1286
  const r = await G.push()
1222
1287
  busyUI(false)
1223
1288
  toast(r.ok ? '已推送' : ('推送失败:' + hintOf(r)), r.ok ? 'ok' : 'err')
1224
- refresh()
1289
+ refreshAfterAction()
1225
1290
  }
1226
1291
 
1227
1292
  const pullBtn = $('zgp-pull')
@@ -1231,7 +1296,7 @@ module.exports = function installGitPanel(win, WS) {
1231
1296
  busyUI(false)
1232
1297
  toast(r.ok ? '已拉取' : ('拉取失败:' + hintOf(r)), r.ok ? 'ok' : 'err')
1233
1298
  logCache = null
1234
- refresh()
1299
+ refreshAfterAction()
1235
1300
  }
1236
1301
  }
1237
1302
  bindStatusButtons()
@@ -1241,6 +1306,15 @@ module.exports = function installGitPanel(win, WS) {
1241
1306
  const status = $('zgp-status')
1242
1307
  if (!status) return
1243
1308
  let changed = false
1309
+ // 先清掉不属于三个容器的散落节点:历史上 renderStatus/renderFileMode 都走过
1310
+ // 「直接给 .zgp-status 写 innerHTML」的兜底,留下的裸 chip 会和重建的容器
1311
+ // 并排显示(全局 chip + 单文件 chip 同时出现),必须一次性清干净
1312
+ const kids = Array.prototype.slice.call(status.children)
1313
+ for (const el of kids) {
1314
+ if (el.id === 'zgp-chips' || el.id === 'zgp-fileact') continue
1315
+ if (el.classList && el.classList.contains('zgp-acts')) continue
1316
+ try { status.removeChild(el); changed = true } catch (e) {}
1317
+ }
1244
1318
  if (!$('zgp-chips')) {
1245
1319
  const d = doc.createElement('div')
1246
1320
  d.className = 'zgp-chips'
@@ -1297,7 +1371,12 @@ module.exports = function installGitPanel(win, WS) {
1297
1371
  refresh()
1298
1372
  }
1299
1373
  }
1300
- win.__zgpOpen = function (mode) { if (!root.classList.contains('open')) win.__zgpToggle(mode || 'global') }
1374
+ win.__zgpOpen = function (mode) {
1375
+ const m = mode === 'file' ? 'file' : 'global'
1376
+ // 面板已经打开时也要切模式:否则从「文档变更记录」再点侧边栏的
1377
+ // 「工作区变更记录」会被静默忽略,标题与内容停在上一层,看起来就是错乱
1378
+ if (!root.classList.contains('open') || m !== panelMode) win.__zgpToggle(m)
1379
+ }
1301
1380
  win.__zgpIsOpen = function () { return root.classList.contains('open') }
1302
1381
 
1303
1382
  // 面板打开时定时刷新(3 秒);历史页不做轮询,避免反复重绘导致闪烁
@@ -20,6 +20,7 @@ const fs = require('fs')
20
20
  const path = require('path')
21
21
  const os = require('os')
22
22
  const crypto = require('crypto')
23
+ const wsPrefs = require('./ws-prefs.js')
23
24
 
24
25
  const DEFAULTS = {
25
26
  autoSync: false,
@@ -30,7 +31,8 @@ const DEFAULTS = {
30
31
  pullBeforePush: true,
31
32
  pushEnabled: true,
32
33
  commitScope: 'notes',
33
- commitMessage: 'notes: 同步 {{datetime}}',
34
+ // 提交信息固定,不开放给用户配置(少一个配置项,也避免有人把模板改坏导致历史不可读)
35
+ commitMessage: 'cmark sync:{{datetime}}',
34
36
  lastRunAt: 0,
35
37
  failCount: 0
36
38
  }
@@ -201,7 +203,7 @@ module.exports = function installGitService(win, argvPath) {
201
203
  { kind: 'detached', re: /detached HEAD|not currently on a branch/i },
202
204
  { kind: 'norepo', re: /not a git repository/i },
203
205
  { kind: 'noUpstream', re: /no upstream configured|has no upstream branch|couldn't find remote ref/i },
204
- { kind: 'auth', re: /(Authentication failed|Permission denied \(publickey|invalid username or password|could not read Username|could not read Password|No such identity)/i },
206
+ { kind: 'auth', re: /(Authentication failed|Permission denied \(publickey|invalid username or password|could not read Username|could not read Password|No such identity|token username invalid|Incorrect username or password|returned error:\s*40[13]|HTTP 40[13])/i },
205
207
  { kind: 'network', re: /could not resolve host|Unable to access|Connection (refused|reset|timed out)|Failed to connect|network is unreachable|Operation timed out/i },
206
208
  { kind: 'perm', re: /Permission denied|EACCES|Read-only file system|No space left/i },
207
209
  { kind: 'dirty', re: /local changes would be lost|Your local changes|uncommitted changes/i }
@@ -209,7 +211,7 @@ module.exports = function installGitService(win, argvPath) {
209
211
 
210
212
  const ERR_TEXT = {
211
213
  network: '网络不通或远端不可达',
212
- auth: '认证失败(检查 SSH key 或凭据)',
214
+ auth: '认证失败:令牌无效或用户名不匹配(Gitee/自建服务重新授权一次即可)',
213
215
  conflict: '存在冲突,需要先解决',
214
216
  lock: 'git 被其他进程占用(index.lock)',
215
217
  detached: 'HEAD 处于游离状态,请先切回分支',
@@ -241,10 +243,73 @@ module.exports = function installGitService(win, argvPath) {
241
243
  // git 会弹出「选择凭据助手」的 GUI 窗口,在插件里就是卡死。
242
244
  const BASE_ARGS = ['-c', 'core.quotepath=false', '-c', 'credential.helper=', '-c', 'credential.helper=manager']
243
245
 
246
+ // ---- 远端凭据(一键建仓用)----
247
+ // token 存在 ~/.cmark-config.json 的 remoteToken 里,网络命令用内联 credential helper
248
+ // 从环境变量读取:不写进 .git/config,也不出现在 git 的命令行参数里。
249
+ const NET_CFG_FILE = path.join(os.homedir(), '.cmark-config.json')
250
+ let netCfgCache = { t: -1, data: null }
251
+ function readNetCfg () {
252
+ try {
253
+ const st = fs.statSync(NET_CFG_FILE)
254
+ if (netCfgCache.data && netCfgCache.t === st.mtimeMs) return netCfgCache.data
255
+ const d = JSON.parse(fs.readFileSync(NET_CFG_FILE, 'utf8'))
256
+ netCfgCache = { t: st.mtimeMs, data: d }
257
+ return d
258
+ } catch (e) { return null }
259
+ }
260
+ const NET_CMDS = { push: 1, pull: 1, fetch: 1, 'ls-remote': 1, clone: 1 }
261
+
262
+ // Gitee 对 HTTPS 的用户名是**强校验**的:用户名必须与令牌所有者一致,否则直接回
263
+ // 403「The token username invalid」(实测:用 x-access-token 当用户名,Gitee 连
264
+ // 假令牌都明确回 username 相关错误)。x-access-token 这种占位用户名只有 GitHub 认。
265
+ // 所以按远端决定用户名:
266
+ // https://gitee.com/<owner>/<repo>.git → owner(个人仓库的 owner 就是令牌所有者)
267
+ // 其它(GitHub 等接受任意用户名) → x-access-token
268
+ const SAFE_USER = /^[A-Za-z0-9._-]{1,64}$/
269
+ function userForUrl (url) {
270
+ const m = /^https?:\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/([^/]+)\//.exec(String(url || ''))
271
+ if (m && /(^|\.)gitee\.com$/i.test(m[1]) && SAFE_USER.test(m[2])) return m[2]
272
+ return 'x-access-token'
273
+ }
274
+ // 从 .git/config 里同步取 origin 的地址(exec 是同步拼参数的场合,不能等 git 命令)
275
+ function originUrl () {
276
+ try {
277
+ const txt = fs.readFileSync(path.join(repoRoot || WS, '.git', 'config'), 'utf8')
278
+ const tail = txt.split(/\[remote "origin"\]/)[1] || ''
279
+ const m = /^[ \t]*url[ \t]*=[ \t]*(.+)$/m.exec(tail)
280
+ return m ? m[1].trim() : ''
281
+ } catch (e) { return '' }
282
+ }
283
+ function tokenHelper (username) {
284
+ const u = SAFE_USER.test(String(username || '')) ? String(username) : 'x-access-token'
285
+ return '!f() { echo username=' + u + '; echo password=$CMARK_GIT_TOKEN; }; f'
286
+ }
287
+ function netCred (args) {
288
+ if (!args.some(function (a) { return NET_CMDS[a] })) return null
289
+ // 先取本工作区的凭据,再回退到旧版全局配置(迁移兼容)
290
+ let rec = null
291
+ try { rec = wsPrefs.read(WS).remoteToken || null } catch (e) {}
292
+ if (!(rec && rec.token)) {
293
+ const c = readNetCfg()
294
+ rec = (c && c.remoteToken) || null
295
+ }
296
+ if (!rec || !rec.token) return null
297
+ // 用户名以配置为准;老配置没有 username 字段就用远端地址推断,
298
+ // 这样已经用旧版本绑过 Gitee 的用户升级后不用重新授权
299
+ return { token: rec.token, username: rec.username || userForUrl(rec.url || originUrl()) }
300
+ }
301
+
244
302
  function exec(args, opt) {
245
303
  const o = opt || {}
304
+ const cred = o.token
305
+ ? { token: o.token, username: o.username || userForUrl(o.url || originUrl()) }
306
+ : netCred(args)
307
+ const token = cred && cred.token
308
+ const pre = token
309
+ ? ['-c', 'core.quotepath=false', '-c', 'credential.helper=', '-c', 'credential.helper=' + tokenHelper(cred.username)]
310
+ : BASE_ARGS
246
311
  return new Promise((resolve) => {
247
- execFile('git', BASE_ARGS.concat(args), {
312
+ execFile('git', pre.concat(args), {
248
313
  cwd: repoRoot || WS,
249
314
  encoding: o.buffer ? 'buffer' : 'utf8',
250
315
  maxBuffer: 32 * 1024 * 1024,
@@ -254,7 +319,8 @@ module.exports = function installGitService(win, argvPath) {
254
319
  GCM_INTERACTIVE: 'never',
255
320
  GIT_TERMINAL_PROMPT: '0',
256
321
  GIT_LITERAL_PATHSPECS: '1',
257
- LC_ALL: 'C'
322
+ LC_ALL: 'C',
323
+ CMARK_GIT_TOKEN: token || ''
258
324
  })
259
325
  }, (err, stdout, stderr) => {
260
326
  if (err) {
@@ -688,7 +754,8 @@ module.exports = function installGitService(win, argvPath) {
688
754
  const names = staged.data.split('\0').filter(Boolean)
689
755
  if (!names.length) return { ok: false, kind: 'nothing', message: ERR_TEXT.nothing }
690
756
 
691
- const msg = formatMessage(message || cfg.commitMessage, { count: names.length })
757
+ // 固定使用内置模板:cmark sync:YYYY-MM-DD HH:mm:ss
758
+ const msg = formatMessage(DEFAULTS.commitMessage, { count: names.length })
692
759
  const r = await runGit(['commit', '-m', msg], { label: 'commit', timeout: 90000 })
693
760
  if (!r.ok) return r
694
761
  return { ok: true, data: { message: msg, files: names.length } }
@@ -1280,6 +1347,31 @@ module.exports = function installGitService(win, argvPath) {
1280
1347
  }
1281
1348
  }
1282
1349
 
1350
+ // ---- 手填远程地址的支持 ----
1351
+ // 远程地址可能是需要授权的私有仓库(GitHub/Gitee/自建 GitLab 等)。
1352
+ // 这里给两个能力:先探测(看能不能免授权访问),再用显式 token 探测/绑定。
1353
+ async function probeRemote (url, token) {
1354
+ const u = String(url || '').trim()
1355
+ if (!u) return { ok: false, kind: 'badrev', message: '请填写远程仓库地址' }
1356
+ if (!/^(https?:\/\/|git@|ssh:\/\/|file:\/\/|[A-Za-z]:[\\/]|\\\\|\/)/.test(u)) {
1357
+ return { ok: false, kind: 'badrev', message: '远程地址格式不正确:' + u }
1358
+ }
1359
+ const r = await runGit(['ls-remote', '--heads', u], { label: 'probe', timeout: 60000, token: token || undefined, url: u })
1360
+ if (r.ok) return { ok: true, data: { hasRefs: !!String(r.data || '').trim() } }
1361
+ return { ok: false, kind: r.kind, message: r.message }
1362
+ }
1363
+
1364
+ /** 保存手填地址用的访问令牌(按工作区存,记录地址用于展示) */
1365
+ function setRemoteToken (token, url) {
1366
+ const t = String(token || '').trim()
1367
+ if (!t) return { ok: false, message: '令牌为空' }
1368
+ const u = String(url || '').trim()
1369
+ // 记下按远端推断出的用户名:Gitee 要求它与令牌所有者一致,见 userForUrl
1370
+ const res = wsPrefs.patch(WS, { remoteToken: { provider: 'custom', token: t, url: u, username: userForUrl(u) } })
1371
+ if (res && res.ok === false) return res
1372
+ return { ok: true }
1373
+ }
1374
+
1283
1375
  async function setRemote(url) {
1284
1376
  const pre = precheck()
1285
1377
  if (!pre.ok) return pre
@@ -1357,6 +1449,7 @@ module.exports = function installGitService(win, argvPath) {
1357
1449
  stage, unstage, discard, restoreTo,
1358
1450
  commitAll, push, pull, sync,
1359
1451
  remoteInfo: getRemoteInfo, setRemote,
1452
+ probeRemote, setRemoteToken,
1360
1453
  identity: getIdentity, setIdentity,
1361
1454
  abortInProgress,
1362
1455
  clearStaleLock,
@@ -0,0 +1,130 @@
1
+ /**
2
+ * 宿主适配:uTools / ZTools / 浏览器
3
+ *
4
+ * 两个宿主的 API 名字一致,但注入的全局对象不同:
5
+ * uTools 注入 window.utools
6
+ * ZTools 注入 window.ztools
7
+ *
8
+ * 两个坑必须一起处理:
9
+ * 1) preload 可能在宿主注入全局对象「之前」执行(uTools 尤其如此),
10
+ * 这时候直接读 window.utools 是 undefined,若把 undefined 当结论,
11
+ * 后面所有 window.ztools.xxx 都会炸,preload 直接中断 —— 表现就是
12
+ * 设置面板、Git 面板全都打不开。
13
+ * 2) 两个宿主的 API 并非完全同集,缺某个方法时不能让调用方 TypeError。
14
+ *
15
+ * 所以这里不写死别名,而是装一个惰性代理:读取时才去 window.utools /
16
+ * window.ztools 找真实对象,找不到就返回 no-op 并记一笔,等宿主注入后
17
+ * 同一个代理立刻恢复正常。
18
+ */
19
+
20
+ const RAW_U = (typeof window !== 'undefined' && window.utools) || null
21
+ const RAW_Z = (typeof window !== 'undefined' && window.ztools) || null
22
+ const PROXY_TAG = '__cmark_host_proxy__'
23
+
24
+ /** 真实的宿主对象(排除我们自己的代理) */
25
+ function target() {
26
+ try {
27
+ const u = window.utools
28
+ const z = window.ztools
29
+ if (u && !u[PROXY_TAG]) return u
30
+ if (z && !z[PROXY_TAG]) return z
31
+ return null
32
+ } catch (e) {
33
+ return null
34
+ }
35
+ }
36
+
37
+ function makeProxy() {
38
+ const missing = new Set()
39
+ const p = new Proxy(
40
+ {},
41
+ {
42
+ get(_, prop) {
43
+ if (prop === PROXY_TAG) return true
44
+ const t = target() || RAW_U || RAW_Z
45
+ if (!t) {
46
+ // 宿主还没注入:函数类先给 no-op,避免 preload 在早期就崩掉
47
+ return function () { return undefined }
48
+ }
49
+ const v = t[prop]
50
+ if (typeof v === 'function') return v.bind(t)
51
+ if (v === undefined) {
52
+ missing.add(String(prop))
53
+ return function () { return undefined }
54
+ }
55
+ return v
56
+ },
57
+ has(_, prop) {
58
+ const t = target() || RAW_U || RAW_Z
59
+ return !!t && prop in t
60
+ },
61
+ }
62
+ )
63
+ try {
64
+ Object.defineProperty(p, '__missing', { value: missing, enumerable: false })
65
+ } catch (e) {}
66
+ return p
67
+ }
68
+
69
+ let proxy = null
70
+
71
+ /**
72
+ * 装 shim:缺哪个补哪个(只在缺失时补,不覆盖宿主原对象)。
73
+ * 返回值就是当前识别到的宿主名。
74
+ */
75
+ function applyHostShim() {
76
+ try {
77
+ if (typeof window === 'undefined') return 'node'
78
+ if (!window.utools && !window.ztools) {
79
+ proxy = makeProxy()
80
+ window.ztools = proxy
81
+ window.utools = proxy
82
+ } else {
83
+ if (!window.utools) window.utools = window.ztools
84
+ if (!window.ztools) window.ztools = window.utools
85
+ }
86
+ // 每次读取时重新判断,宿主晚注入也能正确反映
87
+ try {
88
+ Object.defineProperty(window, '__CMARK_HOST', {
89
+ configurable: true,
90
+ get() { return detect() },
91
+ })
92
+ } catch (e) {
93
+ window.__CMARK_HOST = detect()
94
+ }
95
+ return detect()
96
+ } catch (e) {
97
+ return 'browser'
98
+ }
99
+ }
100
+
101
+ function detect() {
102
+ // 关键:shim 之后 window.utools 与 window.ztools 是同一个对象,
103
+ // 不能拿「是谁」判断,必须用 shim 之前的快照 RAW_U / RAW_Z。
104
+ if (RAW_U && !RAW_Z) return 'utools'
105
+ if (RAW_Z && !RAW_U) return 'ztools'
106
+ // 宿主晚注入的情形:看实时注入的对象里哪个是真实的(非我们的代理)
107
+ const u = window.utools
108
+ const z = window.ztools
109
+ const realU = u && !u[PROXY_TAG]
110
+ const realZ = z && !z[PROXY_TAG]
111
+ if (realU && !realZ) return 'utools'
112
+ if (realZ && !realU) return 'ztools'
113
+ return 'browser'
114
+ }
115
+
116
+ function hostApi() {
117
+ return target() || RAW_U || RAW_Z || null
118
+ }
119
+
120
+ function isInHost() {
121
+ return !!hostApi()
122
+ }
123
+
124
+ /** 哪些方法宿主没提供过(用于排查,不抛错) */
125
+ function missingMethods() {
126
+ if (proxy && proxy.__missing) return Array.from(proxy.__missing)
127
+ return []
128
+ }
129
+
130
+ module.exports = { detect, applyHostShim, hostApi, isInHost, missingMethods, target }