@lancc/cmark 0.0.0-stage → 1.2.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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +217 -2
- package/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/assets/abnfDiagram-WEYOIA3C-BgXsTF7a.js +1 -0
- package/assets/architectureDiagram-ZHVYKT53-BsCWa_qC.js +36 -0
- package/assets/blockDiagram-RJAE7QA3-wbIxEiYN.js +140 -0
- package/assets/c4Diagram-XDPPPE54-5zdyxFvA.js +38 -0
- package/assets/chunk-2E7WHVMD-F85Lca-i.js +88 -0
- package/assets/chunk-2Q5K7J3B-CjpAZ0OI.js +1 -0
- package/assets/chunk-4EMNKR2T-C8Kbh83c.js +1 -0
- package/assets/chunk-55XCU76K-BINcFiLN.js +1 -0
- package/assets/chunk-5VM5RSS4-Diy5h2LS.js +15 -0
- package/assets/chunk-JWPE2WC7-BXnjDdh1.js +1 -0
- package/assets/chunk-VQ5W62QI-BM7ZZo1I.js +1 -0
- package/assets/chunk-XXDRQBXY-CW8CjB1w.js +1 -0
- package/assets/chunk-ZCHMQSOR-C7ATz1L1.js +2 -0
- package/assets/classDiagram-v2-4MYWR3UA-CMoqNk-b.js +217 -0
- package/assets/cose-bilkent-JH36ORCC-6rqR5iJk.js +1 -0
- package/assets/cynefin-EF2NZ3EQ-DypLIJxl.js +141 -0
- package/assets/cynefinDiagram-WF24UDRP-8b2CEBlR.js +62 -0
- package/assets/cytoscape.esm-CjX6Hyah.js +331 -0
- package/assets/dagre-NK4GIZVX-Dv6vuUbR.js +4 -0
- package/assets/diagram-CBK3MWTC-DJgMDeYd.js +24 -0
- package/assets/diagram-I5XIIV2D-BB9cMoRW.js +24 -0
- package/assets/diagram-ODIL2UCW-BVcRtsdy.js +30 -0
- package/assets/diagram-PFBEJUXE-BbGSOtJx.js +3 -0
- package/assets/diagram-RJA3I4VV-CzkY_8Fo.js +41 -0
- package/assets/diagram-TYUNFSNR-YTl5QdfL.js +200 -0
- package/assets/ebnfDiagram-V4P3TTPT-COVjPAjE.js +1 -0
- package/assets/echarts-BAPDkhe3.js +61 -0
- package/assets/elk-QONTBCTQ-DHsjY8Z_.js +27 -0
- package/assets/erDiagram-KYQIGQAA-CUAbTUl1.js +99 -0
- package/assets/flowDiagram-IUGLLWBU-CWsiqTQC.js +213 -0
- package/assets/ganttDiagram-R7GPNTJY-DetBW0Wm.js +292 -0
- package/assets/gitGraphDiagram-PDYL7XHY-ByrdgJmw.js +106 -0
- package/assets/graph-D8UmR1vK.js +1 -0
- package/assets/index-B17NCAw4.js +758 -0
- package/assets/index-eB_q_VA3.css +1 -0
- package/assets/infoDiagram-MVDRKAX5-BM48fjhU.js +2 -0
- package/assets/ishikawaDiagram-HPEHV2GE-DtHYywHZ.js +70 -0
- package/assets/journeyDiagram-BOSHW7O7-DJ1LiRh3.js +139 -0
- package/assets/kanban-definition-FNSA4HC5-DPPt8pwJ.js +89 -0
- package/assets/katex-CQL9J12G.js +257 -0
- package/assets/markdown-DVehI1Sg.js +314 -0
- package/assets/mermaid-C_q9pug0.js +312 -0
- package/assets/mindmap-definition-6OY57UG3-zbqYOEY6.js +98 -0
- package/assets/pegDiagram-ECIUJNBX-e7QG7TMq.js +1 -0
- package/assets/pieDiagram-GNMPY74T-Duf6YP0N.js +39 -0
- package/assets/quadrantDiagram-D57K4MC3-B2eYoUUy.js +7 -0
- package/assets/railroadDiagram-Y72NRBL7-BWCzPcI_.js +1 -0
- package/assets/requirementDiagram-EQYSW5RA-HhPMEqB1.js +84 -0
- package/assets/sankeyDiagram-SY24JHO5-CMUTwFBB.js +40 -0
- package/assets/sequenceDiagram-BLEHNHQU-C9wR2ll7.js +169 -0
- package/assets/sizeCapture-INFHLROL-DXfz37S6.js +1 -0
- package/assets/stateDiagram-v2-A6IESKWZ-D_gtonhE.js +291 -0
- package/assets/swimlanes-B3ENRKFQ-DdJOuuMV.js +1 -0
- package/assets/swimlanesDiagram-AYQRV4AK-BWt3HxHV.js +8 -0
- package/assets/timeline-definition-JXYKGLGB-DQsdwJe-.js +120 -0
- package/assets/usecaseDiagram-WPOLBNNX-DTRyFq7d.js +370 -0
- package/assets/vennDiagram-Q3Y3IDCW-dihYszm0.js +34 -0
- package/assets/wardleyDiagram-IVAIDKWA-BoPpgYfK.js +78 -0
- package/assets/xychartDiagram-4URNHDU4-DoLjYxeB.js +7 -0
- package/export-file/github-markdown.css +550 -0
- package/export-file/index.html +33 -0
- package/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/fonts/KaTeX_Main-Bold.woff +0 -0
- package/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/fonts/KaTeX_Main-Italic.woff +0 -0
- package/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/fonts/KaTeX_Main-Regular.woff +0 -0
- package/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/fonts/KaTeX_Math-Italic.woff +0 -0
- package/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/fonts/KaTeX_Script-Regular.woff +0 -0
- package/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/index.html +15 -0
- package/katex.min.css +1 -0
- package/logo.png +0 -0
- package/package.json +31 -4
- package/plugin.json +31 -0
- package/preload/branch-bar.js +201 -0
- package/preload/export-css.js +59 -0
- package/preload/git-panel.js +1366 -0
- package/preload/git-service.js +1463 -0
- package/preload/host.js +130 -0
- package/preload/ndb.js +1407 -0
- package/preload/package.json +3 -0
- package/preload/remote-setup.js +392 -0
- package/preload/remote-wizard.js +328 -0
- package/preload/rename-note.js +150 -0
- package/preload/services.js +423 -0
- package/preload/settings-panel.js +959 -0
- package/preload/ws-prefs.js +55 -0
- package/preload.js +1870 -0
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
/* 设置面板:从 Git 面板中独立出来,入口在侧边栏底部齿轮按钮。
|
|
2
|
+
只用 Git 面板那套 .zgp-* 样式,宿主容器换成 #zgs-root。 */
|
|
3
|
+
function logBoot () {} // 调试打点已关闭
|
|
4
|
+
|
|
5
|
+
module.exports = function attachSettingsPanel (win) {
|
|
6
|
+
logBoot('enter')
|
|
7
|
+
const doc = win.document
|
|
8
|
+
if (!doc || doc.getElementById('zgs-root')) return
|
|
9
|
+
// preload 可能在 document-start 阶段执行,这时 body/head 还不存在,等 DOM 就绪再来
|
|
10
|
+
if (!doc.body) {
|
|
11
|
+
setTimeout(function () { try { attachSettingsPanel(win) } catch (e) {} }, 60)
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ICON = {
|
|
16
|
+
gear: '<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><path d="M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zm0-1.3a1.2 1.2 0 1 1 0-2.4 1.2 1.2 0 0 1 0 2.4z"/><path d="M13.1 9.2a4.9 4.9 0 0 0 0-2.4l1.2-.9-1.2-2-1.4.6a5 5 0 0 0-2-1.2L9.4 1.8h-2.8l-.3 1.5a5 5 0 0 0-2 1.2l-1.4-.6-1.2 2 1.2.9a4.9 4.9 0 0 0 0 2.4l-1.2.9 1.2 2 1.4-.6a5 5 0 0 0 2 1.2l.3 1.5h2.8l.3-1.5a5 5 0 0 0 2-1.2l1.4.6 1.2-2-1.2-.9zm-1.7-.4l1 .7-.3.5-1.1-.5a3.7 3.7 0 0 1-1.9 1.1l-.3 1.2h-.6l-.3-1.2a3.7 3.7 0 0 1-1.9-1.1l-1.1.5-.3-.5 1-.7a3.6 3.6 0 0 1 0-2.3l-1-.7.3-.5 1.1.5a3.7 3.7 0 0 1 1.9-1.1l.3-1.2h.6l.3 1.2a3.7 3.7 0 0 1 1.9 1.1l1.1-.5.3.5-1 .7a3.6 3.6 0 0 1 0 2.3z"/></svg>',
|
|
17
|
+
x: '×'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function esc (s) {
|
|
21
|
+
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
22
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const G = win.__znotesGit
|
|
27
|
+
|
|
28
|
+
// ---------- 样式:定位 + 侧边栏底部按钮 ----------
|
|
29
|
+
const style = doc.createElement('style')
|
|
30
|
+
style.textContent = `
|
|
31
|
+
#zgs-root{position:fixed;top:0;right:0;width:520px;max-width:96vw;height:100%;z-index:99998;
|
|
32
|
+
display:none;flex-direction:column;background:var(--zgp-bg,#1b1b1f);color:var(--zgp-fg,#e6e6e6);
|
|
33
|
+
border-left:1px solid var(--zgp-bd,rgba(255,255,255,.12));box-shadow:-8px 0 24px rgba(0,0,0,.28);
|
|
34
|
+
font-family:inherit;}
|
|
35
|
+
#zgs-root.open{display:flex;animation:zgs-in .22s cubic-bezier(.2,.8,.2,1);}
|
|
36
|
+
@keyframes zgs-in{from{transform:translateX(24px);opacity:.4}to{transform:translateX(0);opacity:1}}
|
|
37
|
+
.zgs-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
|
|
38
|
+
padding:14px 16px 12px;border-bottom:1px solid var(--zgp-bd,rgba(255,255,255,.12));}
|
|
39
|
+
.zgs-title{display:flex;align-items:center;gap:7px;font-size:14px;font-weight:600;}
|
|
40
|
+
.zgs-title svg{width:15px;height:15px;fill:currentColor;opacity:.85;}
|
|
41
|
+
.zgs-body{flex:1;overflow:auto;padding:4px 0 18px;}
|
|
42
|
+
.znotes-sbtn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;
|
|
43
|
+
padding:7px 8px;border:none;border-radius:6px;background:transparent;color:inherit;
|
|
44
|
+
font-size:11.5px;cursor:pointer;opacity:.72;transition:background .15s,opacity .15s;}
|
|
45
|
+
.znotes-sbtn:hover{background:rgba(128,128,128,.16);opacity:1;}
|
|
46
|
+
.znotes-sbtn svg{width:13px;height:13px;fill:currentColor;flex:0 0 13px;}
|
|
47
|
+
/* 目录树要让出底部按钮的空间,否则树会压住 footer(按钮点不到) */
|
|
48
|
+
.sidebar-tree{flex:1 1 auto;min-height:0;}
|
|
49
|
+
/* 底部按钮改为绝对定位 + 容器预留 padding:按钮晚挂载时不再改变树的高度,
|
|
50
|
+
避免打开插件瞬间出现一次高度跳变 */
|
|
51
|
+
.sidebar-body{position:relative;padding-bottom:34px;box-sizing:border-box;}
|
|
52
|
+
.znotes-footer{padding:6px 8px;border-top:1px solid rgba(128,128,128,.18);flex:0 0 auto;
|
|
53
|
+
position:absolute;left:0;right:0;bottom:0;z-index:2;background:inherit;}
|
|
54
|
+
/* 顶部工具条整行不要:新建文件夹/新建笔记/折叠全部/工作区变更记录 全在里面,
|
|
55
|
+
新建走右键菜单,git 操作由顶部那条 git 工具条承担 */
|
|
56
|
+
.sidebar-header{display:none !important;}
|
|
57
|
+
/* 设置面板自己的确认层:git-panel 的 askConfirm 把遮罩挂到 display:none 的 #zgp-root,
|
|
58
|
+
在设置里复用会弹出点不到的框,所以这里独立实现一套 */
|
|
59
|
+
.zgs-mask{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.45);
|
|
60
|
+
display:flex;align-items:center;justify-content:center;}
|
|
61
|
+
.zgs-dlg{background:var(--zgp-bg,#1b1b1f);color:var(--zgp-fg,#e6e6e6);
|
|
62
|
+
border:1px solid var(--zgp-bd,rgba(255,255,255,.14));border-radius:10px;
|
|
63
|
+
padding:18px 18px 14px;max-width:440px;width:calc(100% - 48px);
|
|
64
|
+
box-shadow:0 12px 32px rgba(0,0,0,.45);}
|
|
65
|
+
.zgs-dlg-msg{font-size:12.5px;line-height:1.8;color:var(--zgp-fg2,#cfcfcf);word-break:break-word;}
|
|
66
|
+
.zgs-dlg-row{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}
|
|
67
|
+
.zgp-in[disabled]{opacity:.5;cursor:not-allowed;}
|
|
68
|
+
/* ---------- 设置面板排版规范 ---------- */
|
|
69
|
+
/* 状态信息用键值对齐,而不是把多个字段串成一行用中点隔开 */
|
|
70
|
+
.zgs-kv{display:grid;grid-template-columns:64px 1fr;gap:7px 12px;align-items:baseline;
|
|
71
|
+
margin:4px 0 6px;padding:10px 12px;border-radius:8px;background:rgba(128,128,128,.08);}
|
|
72
|
+
.zgs-kv dt{font-size:11.5px;color:var(--zgp-fg3,#8b8b93);}
|
|
73
|
+
.zgs-kv dd{margin:0;font-size:12.5px;color:var(--zgp-fg,#e6e6e6);word-break:break-all;line-height:1.5;}
|
|
74
|
+
.zgs-kv dd.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;}
|
|
75
|
+
.zgs-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;
|
|
76
|
+
vertical-align:1px;background:#3ddc97;box-shadow:0 0 0 2px rgba(61,220,151,.18);}
|
|
77
|
+
.zgs-dot.off{background:#7c7c85;box-shadow:none;}
|
|
78
|
+
/* 子分组标题:故意做得比分区标题轻,避免"子分组比父级更重"的层级倒置 */
|
|
79
|
+
.zgs-sub{font-size:11.5px;font-weight:600;color:#adadb8;
|
|
80
|
+
margin:18px 0 8px;letter-spacing:.03em;}
|
|
81
|
+
.zgs-sub:first-child{margin-top:12px;}
|
|
82
|
+
/* 持久 label:只靠 placeholder 的话,输入后语义就消失了 */
|
|
83
|
+
.zgs-lab{display:block;font-size:11.5px;color:var(--zgp-fg3,#8b8b93);margin:0 0 5px;}
|
|
84
|
+
/* 危险按钮:移除远端这种不能和主按钮同权重并排 */
|
|
85
|
+
/* 用 #zgs-body 提高优先级:否则被 git-panel 的 .zgp-b 规则覆盖成普通按钮 */
|
|
86
|
+
#zgs-body .zgp-b.danger{color:#ff8f85 !important;background:transparent !important;
|
|
87
|
+
border:1px solid rgba(255,120,110,.42) !important;margin-left:auto;}
|
|
88
|
+
#zgs-body .zgp-b.danger:hover{background:rgba(255,120,110,.12) !important;
|
|
89
|
+
border-color:rgba(255,120,110,.6) !important;}
|
|
90
|
+
/* 折叠块:低频配置(提交者身份、提交信息)默认收起,减少视觉噪音 */
|
|
91
|
+
.zgs-fold{margin:18px 0 6px;}
|
|
92
|
+
.zgs-fold > summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:7px;
|
|
93
|
+
padding:2px 0;font-size:11.5px;font-weight:600;color:#adadb8;letter-spacing:.03em;
|
|
94
|
+
user-select:none;transition:color .15s;}
|
|
95
|
+
.zgs-fold > summary::-webkit-details-marker{display:none;}
|
|
96
|
+
.zgs-fold > summary::before{content:'▸';display:inline-block;font-size:10px;opacity:.75;
|
|
97
|
+
transition:transform .16s ease;transform-origin:50% 50%;}
|
|
98
|
+
.zgs-fold[open] > summary::before{transform:rotate(90deg);}
|
|
99
|
+
.zgs-fold > summary:hover{color:var(--zgp-fg,#e6e6e6);}
|
|
100
|
+
.zgs-fold > summary .zgs-fold-badge{font-weight:400;color:var(--zgp-fg3,#8b8b93);}
|
|
101
|
+
.zgs-fold[open] > summary{margin-bottom:10px;}
|
|
102
|
+
|
|
103
|
+
/* details 的收起靠浏览器默认的 display:none,但本文件给内容元素写了
|
|
104
|
+
.zgs-lab{display:block} / .zgp-row{display:flex} 这类作者样式,
|
|
105
|
+
特异性更高会把内容顶出来,所以这里显式隐藏非 summary 的子元素 */
|
|
106
|
+
.zgs-fold:not([open]) > *:not(summary){display:none !important;}
|
|
107
|
+
/* 分区级折叠(提交信息)的标题用分区标题的规格 */
|
|
108
|
+
.zgs-fold.seclvl{margin:0;}
|
|
109
|
+
.zgs-fold.seclvl > summary{font-size:12.5px;letter-spacing:.02em;color:var(--zgp-fg,#e6e6e6);opacity:.95;}
|
|
110
|
+
/* 分区标题:整块里最重的一级,提亮加大,和子分组、说明文字拉开层次 */
|
|
111
|
+
#zgs-body .zgp-sec h5{font-weight:600;font-size:12.5px;letter-spacing:.02em;
|
|
112
|
+
color:var(--zgp-fg,#e6e6e6);opacity:.95;}
|
|
113
|
+
/* 分区内一致性:输入框统一高度与 focus 态 */
|
|
114
|
+
/* 弹窗里的校验错误行 */
|
|
115
|
+
.zgs-dlg-err{display:none;margin-top:9px;font-size:11.5px;line-height:1.5;color:#ff8f85;
|
|
116
|
+
background:rgba(255,120,110,.1);border-radius:6px;padding:6px 9px;word-break:break-all;}
|
|
117
|
+
/* 状态卡里的远端地址可能很长,允许在任意位置折行 */
|
|
118
|
+
.zgs-kv-url{word-break:break-all;}
|
|
119
|
+
/* 只读地址框:看起来是"展示",不是"待填" */
|
|
120
|
+
#zgs-body .zgs-auth-err{margin:10px 0 0;padding:10px 12px;border-radius:8px;font-size:12px;line-height:1.7;
|
|
121
|
+
color:#cf222e;background:rgba(207,34,46,.10);border:1px solid rgba(207,34,46,.35);word-break:break-word;}
|
|
122
|
+
:root[data-theme='dark'] #zgs-body .zgs-auth-err{color:#ff8f85;background:rgba(255,120,110,.12);border-color:rgba(255,120,110,.4);}
|
|
123
|
+
/* 抽屉内的小节(提交 / 自动同步):用细分隔线与上方仓库状态隔开 */
|
|
124
|
+
#zgs-body .zgs-sub{margin-top:16px;padding-top:14px;border-top:1px solid rgba(128,128,128,.22);}
|
|
125
|
+
#zgs-body .zgs-sub h5{font-size:12px;font-weight:600;margin:0 0 8px;letter-spacing:.02em;}
|
|
126
|
+
/* Git 未启用时,提交/自动同步整体置灰且不可交互 */
|
|
127
|
+
#zgs-body .zgp-sec.is-disabled{opacity:.45;pointer-events:none;filter:grayscale(.4);}
|
|
128
|
+
#zgs-body .zgp-in[readonly]{opacity:.8;cursor:default;background:rgba(127,127,127,.08);}
|
|
129
|
+
#zgs-body .zgp-in{padding-left:12px;padding-right:12px;height:34px;box-sizing:border-box;}
|
|
130
|
+
#zgs-body .zgp-in:focus{outline:none;border-color:rgba(120,160,255,.55);
|
|
131
|
+
box-shadow:0 0 0 2px rgba(120,160,255,.16);}
|
|
132
|
+
#zgs-body .zgp-b{min-height:30px;}
|
|
133
|
+
#zgs-body .zgp-sec{padding-top:16px;padding-bottom:16px;}
|
|
134
|
+
#zgs-body .zgp-row{gap:8px;}
|
|
135
|
+
`
|
|
136
|
+
try { (doc.head || doc.body).appendChild(style) } catch (e0) { logBoot('appendStyle failed: ' + e0.message) }
|
|
137
|
+
|
|
138
|
+
// ---------- 面板容器 ----------
|
|
139
|
+
const root = doc.createElement('div')
|
|
140
|
+
root.id = 'zgs-root'
|
|
141
|
+
root.innerHTML = `
|
|
142
|
+
<div class="zgs-head">
|
|
143
|
+
<div class="zgs-title">${ICON.gear}<span>设置</span></div>
|
|
144
|
+
<button class="zgp-close" id="zgs-close" title="关闭 (Esc)">${ICON.x}</button>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="zgs-body" id="zgs-body"></div>
|
|
147
|
+
`
|
|
148
|
+
doc.body.appendChild(root)
|
|
149
|
+
logBoot('panel mounted, sidebar-body=' + !!doc.querySelector('.sidebar-body'))
|
|
150
|
+
|
|
151
|
+
const $ = (id) => doc.getElementById(id)
|
|
152
|
+
|
|
153
|
+
// ---------- Toast(面板内既有 toast 在 git-panel 里,独立时自建一个轻提示) ----------
|
|
154
|
+
let toastTimer = null
|
|
155
|
+
function toast (text, kind) {
|
|
156
|
+
let el = doc.getElementById('zgs-toast')
|
|
157
|
+
if (!el) {
|
|
158
|
+
el = doc.createElement('div')
|
|
159
|
+
el.id = 'zgs-toast'
|
|
160
|
+
el.style.cssText = 'position:fixed;left:50%;bottom:28px;transform:translateX(-50%);' +
|
|
161
|
+
'padding:8px 16px;border-radius:8px;font-size:12.5px;z-index:99999;opacity:0;' +
|
|
162
|
+
'pointer-events:none;transition:opacity .18s;background:rgba(30,30,34,.95);color:#eee;' +
|
|
163
|
+
'border:1px solid rgba(255,255,255,.14)'
|
|
164
|
+
doc.body.appendChild(el)
|
|
165
|
+
}
|
|
166
|
+
el.textContent = text || ''
|
|
167
|
+
if (kind === 'err') { el.style.color = '#ff8a80'; el.style.borderColor = 'rgba(255,120,110,.45)' } else { el.style.color = '#eee'; el.style.borderColor = 'rgba(255,255,255,.14)' }
|
|
168
|
+
el.style.opacity = '1'
|
|
169
|
+
clearTimeout(toastTimer)
|
|
170
|
+
toastTimer = setTimeout(function () { el.style.opacity = '0' }, 2600)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------- 二次确认(破坏性操作用) ----------
|
|
174
|
+
function zgsConfirm (message, okText) {
|
|
175
|
+
return new Promise(function (resolve) {
|
|
176
|
+
const mask = doc.createElement('div')
|
|
177
|
+
mask.className = 'zgs-mask'
|
|
178
|
+
mask.innerHTML = '<div class="zgs-dlg">' +
|
|
179
|
+
'<div class="zgs-dlg-msg">' + esc(String(message || '')).replace(/\n/g, '<br>') + '</div>' +
|
|
180
|
+
'<div class="zgs-dlg-row">' +
|
|
181
|
+
'<button class="zgp-b" data-r="0">取消</button>' +
|
|
182
|
+
'<button class="zgp-b primary" data-r="1">' + esc(okText || '确定') + '</button>' +
|
|
183
|
+
'</div></div>'
|
|
184
|
+
;(root || doc.body).appendChild(mask)
|
|
185
|
+
mask.onclick = function (ev) {
|
|
186
|
+
const btn = ev.target && ev.target.closest ? ev.target.closest('[data-r]') : null
|
|
187
|
+
if (!btn) return
|
|
188
|
+
const ok = btn.getAttribute('data-r') === '1'
|
|
189
|
+
try { mask.remove() } catch (e) {}
|
|
190
|
+
resolve(ok)
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 带输入框的弹窗。传了 onConfirm 就是"异步校验"模式:
|
|
196
|
+
// 点确定后不关窗,先跑 onConfirm(value),成功才关;失败把原因显示在弹窗里,用户可以直接改地址重试。
|
|
197
|
+
function zgsPrompt (title, placeholder, initial, onConfirm) {
|
|
198
|
+
return new Promise(function (resolve) {
|
|
199
|
+
const hasCheck = typeof onConfirm === 'function'
|
|
200
|
+
const mask = doc.createElement('div')
|
|
201
|
+
mask.className = 'zgs-mask'
|
|
202
|
+
mask.innerHTML = '<div class="zgs-dlg">' +
|
|
203
|
+
'<div class="zgs-dlg-msg">' + esc(String(title || '')).replace(/\n/g, '<br>') + '</div>' +
|
|
204
|
+
'<input class="zgp-in zgs-dlg-in" type="text" placeholder="' + esc(placeholder || '') +
|
|
205
|
+
'" value="' + esc(initial || '') + '" spellcheck="false">' +
|
|
206
|
+
'<div class="zgs-dlg-err"></div>' +
|
|
207
|
+
'<div class="zgs-dlg-row">' +
|
|
208
|
+
'<button class="zgp-b" data-r="0">取消</button>' +
|
|
209
|
+
'<button class="zgp-b primary zgs-dlg-ok" type="button">' + (hasCheck ? '检测并继续' : '保存') + '</button>' +
|
|
210
|
+
'</div></div>'
|
|
211
|
+
;(root || doc.body).appendChild(mask)
|
|
212
|
+
const inp = mask.querySelector('.zgs-dlg-in')
|
|
213
|
+
const errEl = mask.querySelector('.zgs-dlg-err')
|
|
214
|
+
const okBtn = mask.querySelector('.zgs-dlg-ok')
|
|
215
|
+
let settled = false
|
|
216
|
+
let busy = false
|
|
217
|
+
const showErr = function (t) {
|
|
218
|
+
if (!errEl) return
|
|
219
|
+
errEl.textContent = t || ''
|
|
220
|
+
errEl.style.display = t ? 'block' : 'none'
|
|
221
|
+
}
|
|
222
|
+
const done = function (val) {
|
|
223
|
+
if (settled) return
|
|
224
|
+
settled = true
|
|
225
|
+
try { mask.remove() } catch (e) {}
|
|
226
|
+
resolve(val)
|
|
227
|
+
}
|
|
228
|
+
const submit = async function () {
|
|
229
|
+
if (busy || settled) return
|
|
230
|
+
const v = ((inp && inp.value) || '').trim()
|
|
231
|
+
if (!v) { showErr('请填写仓库地址'); return }
|
|
232
|
+
if (!hasCheck) { done(v); return }
|
|
233
|
+
busy = true
|
|
234
|
+
showErr('')
|
|
235
|
+
const label = okBtn.textContent
|
|
236
|
+
okBtn.disabled = true
|
|
237
|
+
okBtn.textContent = '检测中…'
|
|
238
|
+
let res = null
|
|
239
|
+
try { res = await onConfirm(v) } catch (e) { res = { ok: false, message: (e && e.message) || String(e) } }
|
|
240
|
+
okBtn.disabled = false
|
|
241
|
+
okBtn.textContent = label
|
|
242
|
+
busy = false
|
|
243
|
+
if (res && res.ok) done(v)
|
|
244
|
+
else showErr((res && res.message) || '校验失败,请检查地址')
|
|
245
|
+
}
|
|
246
|
+
if (okBtn) okBtn.onclick = function (ev) { if (ev) ev.stopPropagation(); submit() }
|
|
247
|
+
mask.onclick = function (ev) {
|
|
248
|
+
const btn = ev.target && ev.target.closest ? ev.target.closest('[data-r="0"]') : null
|
|
249
|
+
if (btn) { done(null); return }
|
|
250
|
+
if (ev.target === mask) done(null)
|
|
251
|
+
}
|
|
252
|
+
mask.onkeydown = function (ev) {
|
|
253
|
+
if (ev.key === 'Enter') { ev.preventDefault(); submit() }
|
|
254
|
+
else if (ev.key === 'Escape') { ev.preventDefault(); done(null) }
|
|
255
|
+
}
|
|
256
|
+
setTimeout(function () { try { inp.focus(); inp.select() } catch (e) {} }, 30)
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------- 渲染 ----------
|
|
261
|
+
async function render () {
|
|
262
|
+
// 页面重载后 <html> 上的 data-theme 有可能还没写上,这里兜一次
|
|
263
|
+
try { if (win.__CMARK_THEME && win.__CMARK_THEME.apply) win.__CMARK_THEME.apply() } catch (e) {}
|
|
264
|
+
if (!G) {
|
|
265
|
+
$('zgs-body').innerHTML = '<div class="zgp-sec"><h5>不可用</h5><div class="zgp-desc">Git 服务未加载</div></div>'
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
const cfg = (G.config() || {}).data || {}
|
|
269
|
+
const ivOpts = [[60000, '1 分钟'], [300000, '5 分钟'], [900000, '15 分钟'], [1800000, '30 分钟'], [3600000, '1 小时']]
|
|
270
|
+
const mode = cfg.autoMode || 'interval'
|
|
271
|
+
const ivHtml = ivOpts.map(function (p) {
|
|
272
|
+
return '<option value="' + p[0] + '"' + (cfg.intervalMs === p[0] ? ' selected' : '') + '>' + p[1] + '</option>'
|
|
273
|
+
}).join('')
|
|
274
|
+
const stopOpts = [[0, '关闭'], [1, '1 分钟'], [3, '3 分钟'], [5, '5 分钟'], [10, '10 分钟']]
|
|
275
|
+
const stopHtml = stopOpts.map(function (p) {
|
|
276
|
+
return '<option value="' + p[0] + '"' + (cfg.onStopEditMinutes === p[0] ? ' selected' : '') + '>' + p[1] + '</option>'
|
|
277
|
+
}).join('')
|
|
278
|
+
|
|
279
|
+
$('zgs-body').innerHTML = `
|
|
280
|
+
<div class="zgp-sec">
|
|
281
|
+
<h5>外观</h5>
|
|
282
|
+
<div class="zgp-row" id="s-theme-row">
|
|
283
|
+
<button class="zgp-b" id="s-theme-light">浅色</button>
|
|
284
|
+
<button class="zgp-b" id="s-theme-dark">深色</button>
|
|
285
|
+
<button class="zgp-b" id="s-theme-system">跟随系统</button>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="zgp-desc">主题对所有界面生效,选择后立即应用并记住。</div>
|
|
288
|
+
</div>
|
|
289
|
+
<div class="zgp-sec">
|
|
290
|
+
<h5>工作区信息</h5>
|
|
291
|
+
<dl class="zgs-kv">
|
|
292
|
+
<dt>工作区目录</dt><dd class="mono" id="s-repo-dir">—</dd>
|
|
293
|
+
<dt>笔记目录</dt><dd class="mono" id="s-ws">—</dd>
|
|
294
|
+
<dt>附件目录</dt><dd class="mono" id="s-attach-dir">—</dd>
|
|
295
|
+
<dt>附件</dt><dd id="s-attach">统计中…</dd>
|
|
296
|
+
</dl>
|
|
297
|
+
<div class="zgp-row" style="margin-top:8px">
|
|
298
|
+
<button class="zgp-b" id="s-repo-pick">修改工作区</button>
|
|
299
|
+
<button class="zgp-b" id="s-notes-pick">选择笔记目录</button>
|
|
300
|
+
<button class="zgp-b" id="s-attach-clean" title="直接删除没有任何笔记引用的附件(不做回收站,误删可从 git 历史取回)">清理未引用附件</button>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="zgp-desc">默认在 <span class="mono">文档\CMark</span>;修改后重新扫描该目录下的 Markdown 文件,文件本身不会被移动或修改。</div>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="zgp-sec">
|
|
305
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px">
|
|
306
|
+
<h5 style="margin:0">Git 集成</h5>
|
|
307
|
+
<label class="zgp-sw"><input type="checkbox" id="s-git-enabled"><i></i></label>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="zgp-desc" style="margin-top:6px">打开后可以提交历史、回溯版本,并同步到你自己的远程仓库。</div>
|
|
310
|
+
<div class="zgp-drawer" id="s-git-drawer">
|
|
311
|
+
<div id="s-git-body"></div>
|
|
312
|
+
<div class="zgs-sub" id="s-sec-auto">
|
|
313
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px">
|
|
314
|
+
<h5 style="margin:0">自动同步</h5>
|
|
315
|
+
<label class="zgp-sw"><input type="checkbox" id="s-auto" ${cfg.autoSync ? 'checked' : ''}><i></i></label>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="zgp-desc" style="margin:6px 0 10px">按下方策略执行;只在有改动时才产生提交。</div>
|
|
318
|
+
<div class="zgp-drawer${cfg.autoSync ? ' open' : ''}" id="s-auto-drawer">
|
|
319
|
+
<div class="zgp-field">
|
|
320
|
+
<div class="zgp-lab"><span>触发方式</span></div>
|
|
321
|
+
<select class="zgp-sel" id="s-mode">
|
|
322
|
+
<option value="interval"${mode === 'interval' ? ' selected' : ''}>固定间隔</option>
|
|
323
|
+
<option value="stop-edit"${mode === 'stop-edit' ? ' selected' : ''}>停止编辑后</option>
|
|
324
|
+
</select>
|
|
325
|
+
</div>
|
|
326
|
+
<div class="zgp-field" id="s-interval-row"${mode === 'interval' ? '' : ' style="display:none"'}>
|
|
327
|
+
<div class="zgp-lab"><span>间隔时长</span></div>
|
|
328
|
+
<select class="zgp-sel" id="s-interval">${ivHtml}</select>
|
|
329
|
+
<div class="zgp-desc">跨会话生效:即使插件没开够一个间隔,下次启动会补一次。</div>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="zgp-field" id="s-stop-row"${mode === 'stop-edit' ? '' : ' style="display:none"'}>
|
|
332
|
+
<div class="zgp-lab"><span>静默时长</span></div>
|
|
333
|
+
<select class="zgp-sel" id="s-stop">${stopHtml}</select>
|
|
334
|
+
<div class="zgp-desc">停止输入 N 分钟后触发一次,适合边写边自动保存。</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
<div class="zgp-sec">
|
|
341
|
+
<h5>运行状态</h5>
|
|
342
|
+
<div class="zgp-desc" id="s-repo">读取中…</div>
|
|
343
|
+
</div>
|
|
344
|
+
`
|
|
345
|
+
|
|
346
|
+
// 附件统计 + 清理未引用附件。之前这个按钮根本没绑处理函数,点了没反应。
|
|
347
|
+
// 清理是真删(不做回收站),所以必须先给用户看清楚会删几个、多大。
|
|
348
|
+
const attachBox = $('s-attach')
|
|
349
|
+
const attachBtn = $('s-attach-clean')
|
|
350
|
+
const fmtSize = function (n) {
|
|
351
|
+
const v = Number(n) || 0
|
|
352
|
+
if (v >= 1048576) return (v / 1048576).toFixed(1) + ' MB'
|
|
353
|
+
if (v >= 1024) return (v / 1024).toFixed(0) + ' KB'
|
|
354
|
+
return v + ' B'
|
|
355
|
+
}
|
|
356
|
+
const attachDirEl = $('s-attach-dir')
|
|
357
|
+
const refreshAttachStat = function () {
|
|
358
|
+
if (attachDirEl) {
|
|
359
|
+
const ws = win.__ZNOTES_WS || ''
|
|
360
|
+
attachDirEl.textContent = ws ? (ws.replace(/[\\/]+$/, '') + '\\.attachments') : '未设置笔记目录'
|
|
361
|
+
}
|
|
362
|
+
if (!attachBox) return
|
|
363
|
+
try {
|
|
364
|
+
const api = win.__CMARK
|
|
365
|
+
const s = api && api.scanAttachments ? api.scanAttachments() : null
|
|
366
|
+
if (!s || !s.ok) {
|
|
367
|
+
attachBox.textContent = '读取失败' + (s && s.message ? '(' + s.message + ')' : '')
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
const total = Number(s.total) || 0
|
|
371
|
+
const unused = (s.unused || []).length
|
|
372
|
+
attachBox.textContent = total + ' 个,共 ' + fmtSize(s.allBytes) +
|
|
373
|
+
';未引用 ' + unused + ' 个(' + fmtSize(s.unusedBytes) + ')'
|
|
374
|
+
} catch (e) {}
|
|
375
|
+
}
|
|
376
|
+
refreshAttachStat()
|
|
377
|
+
if (attachBtn) {
|
|
378
|
+
attachBtn.onclick = function () {
|
|
379
|
+
try {
|
|
380
|
+
const api = win.__CMARK
|
|
381
|
+
if (!api || !api.cleanAttachments) return toast('清理功能不可用', 'err')
|
|
382
|
+
const s = api.scanAttachments ? api.scanAttachments() : null
|
|
383
|
+
const n = s && s.ok ? (s.unused || []).length : 0
|
|
384
|
+
if (!n) return toast('没有未被引用的附件')
|
|
385
|
+
const msg =
|
|
386
|
+
'直接删除 ' + n + ' 个未被引用的附件(' + fmtSize(s.unusedBytes) + ')?\n' +
|
|
387
|
+
'删除后不可撤销;这些附件如果已经提交过,可以从 git 历史里找回。'
|
|
388
|
+
zgsConfirm(msg, '删除').then(function (ok) {
|
|
389
|
+
if (!ok) return
|
|
390
|
+
const r = api.cleanAttachments()
|
|
391
|
+
if (!r || !r.ok) return toast('清理失败:' + ((r && r.message) || '未知错误'), 'err')
|
|
392
|
+
toast('已删除 ' + (r.deleted || 0) + ' 个附件')
|
|
393
|
+
refreshAttachStat()
|
|
394
|
+
})
|
|
395
|
+
} catch (e) {
|
|
396
|
+
toast('清理失败:' + (e && e.message), 'err')
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ---------- 外观:主题三态 ----------
|
|
402
|
+
const themeSvc = win.__CMARK_THEME
|
|
403
|
+
const curMode = (themeSvc && themeSvc.get && themeSvc.get().mode) || 'system'
|
|
404
|
+
const modeLabel = { light: '浅色', dark: '深色', system: '跟随系统' }
|
|
405
|
+
;['light', 'dark', 'system'].forEach(function (m) {
|
|
406
|
+
const btn = $('s-theme-' + m)
|
|
407
|
+
if (!btn) return
|
|
408
|
+
if (m === curMode) btn.classList.add('primary')
|
|
409
|
+
btn.onclick = function () {
|
|
410
|
+
if (!themeSvc || !themeSvc.set) return toast('主题服务未就绪', 'err')
|
|
411
|
+
themeSvc.set(m)
|
|
412
|
+
;['light', 'dark', 'system'].forEach(function (mm) {
|
|
413
|
+
const bb = $('s-theme-' + mm)
|
|
414
|
+
if (bb) bb.classList.toggle('primary', mm === m)
|
|
415
|
+
})
|
|
416
|
+
toast('已切换为' + modeLabel[m])
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
// ---------- Git 集成:开关 + 按目录状态渲染 ----------
|
|
421
|
+
const fs = require('fs')
|
|
422
|
+
const GIT_KEY = 'cmark-git-enabled'
|
|
423
|
+
const gitSwitch = $('s-git-enabled')
|
|
424
|
+
const gitDrawer = $('s-git-drawer')
|
|
425
|
+
const gitBody = $('s-git-body')
|
|
426
|
+
const secAuto = $('s-sec-auto')
|
|
427
|
+
|
|
428
|
+
// 状态源:优先 preload 暴露的配置文件接口(file:// 下 localStorage 可能不可用)
|
|
429
|
+
const readGitFlag = function (fallback) {
|
|
430
|
+
try {
|
|
431
|
+
const v = win.__CMARK_GIT && win.__CMARK_GIT.get ? win.__CMARK_GIT.get() : undefined
|
|
432
|
+
if (v === true || v === false) return v
|
|
433
|
+
} catch (e) {}
|
|
434
|
+
try {
|
|
435
|
+
const v = win.localStorage.getItem(GIT_KEY)
|
|
436
|
+
if (v === '1') return true
|
|
437
|
+
if (v === '0') return false
|
|
438
|
+
} catch (e) {}
|
|
439
|
+
return fallback
|
|
440
|
+
}
|
|
441
|
+
const writeGitFlag = function (on) {
|
|
442
|
+
try {
|
|
443
|
+
if (win.__CMARK_GIT && win.__CMARK_GIT.set) { win.__CMARK_GIT.set(on); return } } catch (e) {}
|
|
444
|
+
try { win.localStorage.setItem(GIT_KEY, on ? '1' : '0') } catch (e) {}
|
|
445
|
+
try { win.dispatchEvent(new CustomEvent('cmark-git-changed', { detail: { enabled: !!on } })) } catch (e) {}
|
|
446
|
+
}
|
|
447
|
+
const setSectionsDisabled = function (disabled, reason) {
|
|
448
|
+
const secs = [secAuto]
|
|
449
|
+
for (let i = 0; i < secs.length; i++) {
|
|
450
|
+
const sec = secs[i]
|
|
451
|
+
if (!sec) continue
|
|
452
|
+
sec.classList.toggle('is-disabled', !!disabled)
|
|
453
|
+
// 置灰时把原因挂在容器上:disabled 的控件本身不弹 title,但祖先的 title 会生效
|
|
454
|
+
if (disabled) sec.setAttribute('title', reason || '请先配置 Git 仓库')
|
|
455
|
+
else sec.removeAttribute('title')
|
|
456
|
+
const nodes = sec.querySelectorAll('input,select,button')
|
|
457
|
+
for (let j = 0; j < nodes.length; j++) nodes[j].disabled = !!disabled
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const openWizard = function (p) {
|
|
461
|
+
if (win.__CMARK_OPEN_WIZARD) win.__CMARK_OPEN_WIZARD(p)
|
|
462
|
+
else toast('建仓向导未加载', 'err')
|
|
463
|
+
}
|
|
464
|
+
const afterGitChange = function () {
|
|
465
|
+
try { win.dispatchEvent(new CustomEvent('cmark-git-changed')) } catch (e) {}
|
|
466
|
+
refreshGitSection().catch(function () {})
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 认证失败时在抽屉顶部插入提示条,并给一个直接重新授权的入口
|
|
470
|
+
const showAuthError = function (msg) {
|
|
471
|
+
if (!gitBody) return
|
|
472
|
+
const old = gitBody.querySelector('.zgs-auth-err')
|
|
473
|
+
if (old) old.remove()
|
|
474
|
+
let prov = 'github'
|
|
475
|
+
try {
|
|
476
|
+
const cur = (win.__CMARK_REMOTE && win.__CMARK_REMOTE.current) ? win.__CMARK_REMOTE.current() : null
|
|
477
|
+
if (cur && cur.provider) prov = cur.provider
|
|
478
|
+
} catch (e) {}
|
|
479
|
+
const box = doc.createElement('div')
|
|
480
|
+
box.className = 'zgs-auth-err'
|
|
481
|
+
box.innerHTML = '授权可能已失效,推送/拉取被拒绝:' + esc(String(msg || '').slice(0, 180)) +
|
|
482
|
+
'<div class="zgp-row" style="margin-top:8px"><button class="zgp-b primary" id="s-git-reauth">重新授权</button></div>'
|
|
483
|
+
gitBody.insertBefore(box, gitBody.firstChild)
|
|
484
|
+
const rb = box.querySelector('#s-git-reauth')
|
|
485
|
+
if (rb) rb.onclick = function () { openWizard(prov) }
|
|
486
|
+
toast('授权可能已失效,可点「重新授权」', 'err')
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 本机 git 可用性:首次检测后缓存,装完 git 可点「重新检测」刷新
|
|
490
|
+
let gitProbe = null
|
|
491
|
+
// 每次打开面板只做一次「未初始化则关开关」的校正;否则用户点开关会被立刻打回
|
|
492
|
+
let gitGateDone = false
|
|
493
|
+
const AUTH_RE = /Authentication failed|could not read Username|could not read Password|Permission denied|Authentication required|Invalid username or password|returned error: 403|403 Forbidden|support for password authentication/i
|
|
494
|
+
const isAuthError = function (msg) { return AUTH_RE.test(String(msg || '')) }
|
|
495
|
+
|
|
496
|
+
async function refreshGitSection () {
|
|
497
|
+
if (!gitBody) return
|
|
498
|
+
|
|
499
|
+
if (gitProbe === null) {
|
|
500
|
+
try {
|
|
501
|
+
const cp = require('child_process')
|
|
502
|
+
const out = cp.execFileSync('git', ['--version'], { encoding: 'utf8', timeout: 6000, windowsHide: true })
|
|
503
|
+
gitProbe = { ok: true, ver: String(out || '').trim() }
|
|
504
|
+
} catch (e) { gitProbe = { ok: false, ver: '' } }
|
|
505
|
+
}
|
|
506
|
+
if (!gitProbe.ok) {
|
|
507
|
+
if (gitSwitch) { gitSwitch.checked = false; gitSwitch.disabled = true }
|
|
508
|
+
if (gitDrawer) gitDrawer.classList.add('open')
|
|
509
|
+
setSectionsDisabled(true, '请先安装 Git')
|
|
510
|
+
gitBody.innerHTML =
|
|
511
|
+
'<div class="zgs-auth-err">没检测到本机安装 Git,无法使用 Git 集成。</div>' +
|
|
512
|
+
'<div class="zgp-desc">装好 Git for Windows 后点「重新检测」;如果装完仍提示未安装,重启一次 ZTools 让 PATH 生效。</div>' +
|
|
513
|
+
'<div class="zgp-row" style="margin-top:10px">' +
|
|
514
|
+
'<button class="zgp-b primary" data-git="redetect">重新检测</button>' +
|
|
515
|
+
'<button class="zgp-b" data-git="dlgit">打开下载页</button></div>'
|
|
516
|
+
const rd = gitBody.querySelector('[data-git="redetect"]')
|
|
517
|
+
if (rd) rd.onclick = function () { gitProbe = null; refreshGitSection().catch(function () {}) }
|
|
518
|
+
const dl = gitBody.querySelector('[data-git="dlgit"]')
|
|
519
|
+
if (dl) dl.onclick = async function () {
|
|
520
|
+
const ok = await zgsConfirm('即将打开外部窗口,CMark 窗口可能会被隐藏。装完 Git 后重新呼出 CMark 可继续设置。', '继续打开')
|
|
521
|
+
if (ok) { try { win.ztools.shellOpenExternal('https://git-scm.com/download/win') } catch (e) {} }
|
|
522
|
+
}
|
|
523
|
+
return
|
|
524
|
+
}
|
|
525
|
+
if (gitSwitch) gitSwitch.disabled = false
|
|
526
|
+
|
|
527
|
+
const insp = (G && G.inspect) ? G.inspect() : { isRepo: false }
|
|
528
|
+
let data = {}
|
|
529
|
+
try { const st = await G.state(); data = (st && st.data) || {} } catch (e) {}
|
|
530
|
+
let remote = { hasRemote: false, url: '' }
|
|
531
|
+
try { const r = await G.remoteInfo(); if (r && r.ok) remote = r.data } catch (e) {}
|
|
532
|
+
const ws = win.__ZNOTES_WS || ''
|
|
533
|
+
let count = 0
|
|
534
|
+
try {
|
|
535
|
+
if (ws && fs.existsSync(ws)) {
|
|
536
|
+
count = fs.readdirSync(ws).filter(function (n) {
|
|
537
|
+
return ['.git', '.cmark', '.attachments', 'backup'].indexOf(n) < 0
|
|
538
|
+
}).length
|
|
539
|
+
}
|
|
540
|
+
} catch (e) {}
|
|
541
|
+
|
|
542
|
+
let enabled = readGitFlag(null)
|
|
543
|
+
if (enabled === null) { enabled = !!insp.isRepo; writeGitFlag(enabled) }
|
|
544
|
+
// 每次打开面板后的首次渲染校正一次:目录还不是仓库却开着开关 → 落盘为关闭,
|
|
545
|
+
// 这样「打开开关后没继续建仓就关面板」下次进来仍是关闭。
|
|
546
|
+
if (!gitGateDone) {
|
|
547
|
+
gitGateDone = true
|
|
548
|
+
if (!insp.isRepo && enabled) { enabled = false; writeGitFlag(false) }
|
|
549
|
+
}
|
|
550
|
+
if (gitSwitch) gitSwitch.checked = enabled
|
|
551
|
+
if (gitDrawer) gitDrawer.classList.toggle('open', enabled)
|
|
552
|
+
setSectionsDisabled(
|
|
553
|
+
!enabled || !insp.isRepo,
|
|
554
|
+
!enabled ? '请先开启 Git 集成' : '请先配置 Git 仓库'
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
const branch = data.branch || 'main'
|
|
558
|
+
const ahead = data.ahead || 0
|
|
559
|
+
const behind = data.behind || 0
|
|
560
|
+
const dirty = typeof data.dirty === 'number' ? data.dirty : -1
|
|
561
|
+
|
|
562
|
+
let kind = 'empty'
|
|
563
|
+
if (insp.isRepo && remote.hasRemote) kind = 'remote'
|
|
564
|
+
else if (insp.isRepo) kind = 'local'
|
|
565
|
+
else if (count > 0) kind = 'files'
|
|
566
|
+
|
|
567
|
+
let html = ''
|
|
568
|
+
if (kind === 'empty') {
|
|
569
|
+
html = '<div class="zgp-desc">目录是空的,建一个仓库就能开始记。</div>' +
|
|
570
|
+
'<div class="zgp-row" style="margin-top:10px">' +
|
|
571
|
+
'<button class="zgp-b primary" data-git="gh">使用 GitHub 作为远程仓库</button>' +
|
|
572
|
+
'<button class="zgp-b" data-git="gt">使用 Gitee 作为远程仓库</button>' +
|
|
573
|
+
'<button class="zgp-b" data-git="adopt">从远程仓库加载</button>' +
|
|
574
|
+
'<button class="zgp-b" data-git="local">仅本地仓库</button></div>'
|
|
575
|
+
} else if (kind === 'files') {
|
|
576
|
+
html = '<div class="zgp-desc">目录里有 ' + count + ' 项内容。初始化后这些文件成为第一个提交,内容不会被改动。</div>' +
|
|
577
|
+
'<div class="zgp-row" style="margin-top:10px">' +
|
|
578
|
+
'<button class="zgp-b primary" data-git="gh">使用 GitHub 作为远程仓库</button>' +
|
|
579
|
+
'<button class="zgp-b" data-git="gt">使用 Gitee 作为远程仓库</button>' +
|
|
580
|
+
'<button class="zgp-b" data-git="local">仅本地仓库</button></div>'
|
|
581
|
+
} else if (kind === 'local') {
|
|
582
|
+
html = '<dl class="zgs-kv"><dt>仓库</dt><dd>本地仓库</dd>' +
|
|
583
|
+
'<dt>分支</dt><dd>' + esc(branch) + '</dd>' +
|
|
584
|
+
'<dt>改动</dt><dd>' + (dirty >= 0 ? dirty + ' 个文件' : '读取失败') + '</dd></dl>' +
|
|
585
|
+
'<div class="zgp-desc">还没有远程仓库,笔记只存在这台机器上。</div>' +
|
|
586
|
+
'<div class="zgp-row" style="margin-top:10px">' +
|
|
587
|
+
'<button class="zgp-b primary" data-git="gh">使用 GitHub 作为远程仓库</button>' +
|
|
588
|
+
'<button class="zgp-b" data-git="gt">使用 Gitee 作为远程仓库</button>' +
|
|
589
|
+
'<button class="zgp-b" data-git="manual">手动填远程地址</button></div>'
|
|
590
|
+
} else {
|
|
591
|
+
html = '<dl class="zgs-kv"><dt>远程</dt><dd class="mono">' + esc(remote.url || '') + '</dd>' +
|
|
592
|
+
'<dt>分支</dt><dd>' + esc(branch) + '</dd>' +
|
|
593
|
+
'<dt>状态</dt><dd>' + (dirty >= 0 ? dirty + ' 处改动' : '读取失败') +
|
|
594
|
+
(ahead ? ' · 待推送 ' + ahead : '') + (behind ? ' · 待拉取 ' + behind : '') + '</dd></dl>' +
|
|
595
|
+
'<div class="zgp-row" style="margin-top:10px">' +
|
|
596
|
+
'<button class="zgp-b" data-git="push">推送</button>' +
|
|
597
|
+
'<button class="zgp-b" data-git="pull">拉取</button>' +
|
|
598
|
+
'<button class="zgp-b danger" data-git="unbind">移除远端</button>' +
|
|
599
|
+
'<button class="zgp-b" data-git="clear-token">清除已保存凭据</button></div>'
|
|
600
|
+
}
|
|
601
|
+
gitBody.innerHTML = html
|
|
602
|
+
|
|
603
|
+
const btns = gitBody.querySelectorAll('[data-git]')
|
|
604
|
+
for (let i = 0; i < btns.length; i++) {
|
|
605
|
+
const b = btns[i]
|
|
606
|
+
b.onclick = async function () {
|
|
607
|
+
const act = b.getAttribute('data-git')
|
|
608
|
+
if (act === 'gh') return openWizard('github')
|
|
609
|
+
if (act === 'gt') return openWizard('gitee')
|
|
610
|
+
if (act === 'adopt') return loadFromRemote()
|
|
611
|
+
if (act === 'local') {
|
|
612
|
+
b.disabled = true
|
|
613
|
+
const r = await G.initRepo({ mode: 'local' })
|
|
614
|
+
b.disabled = false
|
|
615
|
+
if (r && r.ok) { toast('已创建本地仓库'); afterGitChange() } else toast((r && r.message) || '初始化失败', 'err')
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
if (act === 'manual') {
|
|
619
|
+
const bindRemote = async function (u) {
|
|
620
|
+
const r = await G.initRepo({ mode: 'remote', remoteUrl: u, push: true })
|
|
621
|
+
if (r && r.ok) { toast('已绑定远程仓库'); afterGitChange(); return { ok: true } }
|
|
622
|
+
return { ok: false, message: (r && r.message) || '绑定失败' }
|
|
623
|
+
}
|
|
624
|
+
// 第二步:需要授权时让用户贴令牌,存进本工作区后再探测并绑定
|
|
625
|
+
const askToken = function (u, hint) {
|
|
626
|
+
setTimeout(function () {
|
|
627
|
+
zgsPrompt(
|
|
628
|
+
'这个仓库需要授权访问\n把访问令牌粘贴到下面(GitHub PAT / Gitee 私人令牌 / 私有 GitLab token)',
|
|
629
|
+
'粘贴访问令牌(GitHub PAT / Gitee 私人令牌 / 私有 GitLab token)', '',
|
|
630
|
+
async function (tk) {
|
|
631
|
+
const t = String(tk || '').trim()
|
|
632
|
+
if (!t) return { ok: false, message: '请填写访问令牌' }
|
|
633
|
+
const saved = G.setRemoteToken(t, u)
|
|
634
|
+
if (saved && saved.ok === false) return { ok: false, message: saved.message || '保存令牌失败' }
|
|
635
|
+
const p2 = await G.probeRemote(u, t)
|
|
636
|
+
if (!p2 || !p2.ok) {
|
|
637
|
+
return { ok: false, message: '令牌无效或没有写权限:' + ((p2 && p2.message) || '未知原因') }
|
|
638
|
+
}
|
|
639
|
+
const r = await bindRemote(u)
|
|
640
|
+
if (r.ok) toast('已绑定远程仓库(凭据已保存在本机)')
|
|
641
|
+
return r
|
|
642
|
+
}
|
|
643
|
+
)
|
|
644
|
+
}, 140)
|
|
645
|
+
return { ok: true }
|
|
646
|
+
}
|
|
647
|
+
zgsPrompt('填写远程仓库地址', 'https://github.com/you/repo.git', '', async function (url) {
|
|
648
|
+
const u = String(url || '').trim()
|
|
649
|
+
if (!u) return { ok: false, message: '请填写远程仓库地址' }
|
|
650
|
+
// 先免授权探一下:公开仓库直接过,私有仓库会返回 auth 类错误
|
|
651
|
+
const probe = await G.probeRemote(u)
|
|
652
|
+
if (probe && probe.ok) return bindRemote(u)
|
|
653
|
+
const msg = String((probe && probe.message) || '')
|
|
654
|
+
// 注意:GitHub/Gitee 对「私有仓库无权限」和「仓库不存在」都返回 not found,
|
|
655
|
+
// 所以这里把 not found 也算进去 —— 否则私有仓库会直接走到绑定、只丢一句英文报错。
|
|
656
|
+
if (/auth|permission|denied|403|401|username|password|credentials|not found|does not exist/i.test(msg)) {
|
|
657
|
+
// 需要授权:关掉地址输入,接着弹令牌输入
|
|
658
|
+
askToken(u)
|
|
659
|
+
return { ok: true }
|
|
660
|
+
}
|
|
661
|
+
return bindRemote(u)
|
|
662
|
+
})
|
|
663
|
+
return
|
|
664
|
+
}
|
|
665
|
+
if (act === 'push' || act === 'pull') {
|
|
666
|
+
b.disabled = true
|
|
667
|
+
const r = act === 'push' ? await G.push() : await G.pull()
|
|
668
|
+
b.disabled = false
|
|
669
|
+
if (r && r.ok) { toast(act === 'push' ? '推送完成' : '拉取完成'); return }
|
|
670
|
+
const failMsg = (r && r.message) || (act === 'push' ? '推送失败' : '拉取失败')
|
|
671
|
+
if (isAuthError(failMsg)) { showAuthError(failMsg); return }
|
|
672
|
+
toast(failMsg, 'err')
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
if (act === 'clear-token') {
|
|
676
|
+
const ok = await zgsConfirm('清除本机保存的授权凭据后,推送需要重新授权;远端绑定关系保留。', '清除')
|
|
677
|
+
if (!ok) return
|
|
678
|
+
try { if (win.__CMARK_REMOTE && win.__CMARK_REMOTE.clear) win.__CMARK_REMOTE.clear() } catch (e) {}
|
|
679
|
+
toast('已清除本机保存的授权凭据')
|
|
680
|
+
afterGitChange()
|
|
681
|
+
return
|
|
682
|
+
}
|
|
683
|
+
if (act === 'unbind') {
|
|
684
|
+
const ok = await zgsConfirm('移除远端绑定后,笔记只保留在本机。', '移除')
|
|
685
|
+
if (!ok) return
|
|
686
|
+
const r = await G.setRemote('')
|
|
687
|
+
if (r && r.ok) { toast('已移除远端'); afterGitChange() } else toast((r && r.message) || '移除失败', 'err')
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (gitSwitch) {
|
|
694
|
+
gitSwitch.onchange = function () {
|
|
695
|
+
writeGitFlag(!!gitSwitch.checked)
|
|
696
|
+
if (gitDrawer) gitDrawer.classList.toggle('open', !!gitSwitch.checked)
|
|
697
|
+
setSectionsDisabled(!gitSwitch.checked)
|
|
698
|
+
// 通知侧边栏操作栏与编辑区云端按钮同步置灰/隐藏
|
|
699
|
+
try { win.dispatchEvent(new CustomEvent('cmark-git-changed')) } catch (e) {}
|
|
700
|
+
refreshGitSection().catch(function () {})
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
refreshGitSection().catch(function () {})
|
|
704
|
+
|
|
705
|
+
// ---------- 工作区(仓库根)/ 笔记目录 ----------
|
|
706
|
+
const fillDirs = function () {
|
|
707
|
+
const api = win.__CMARK || {}
|
|
708
|
+
const ws = win.__ZNOTES_WS || ''
|
|
709
|
+
let root = ''
|
|
710
|
+
try { root = api.repoDir ? api.repoDir() : '' } catch (e) {}
|
|
711
|
+
if (!root) root = ws
|
|
712
|
+
const rootEl = $('s-repo-dir')
|
|
713
|
+
if (rootEl) rootEl.textContent = root || '未设置'
|
|
714
|
+
const wsEl = $('s-ws')
|
|
715
|
+
if (wsEl) wsEl.textContent = ws || '未设置'
|
|
716
|
+
}
|
|
717
|
+
fillDirs()
|
|
718
|
+
|
|
719
|
+
const repoNames = { pick: '修改工作区', notes: '选择笔记目录' }
|
|
720
|
+
const runDirPick = function (kind) {
|
|
721
|
+
const api = win.__CMARK || {}
|
|
722
|
+
const fn = kind === 'pick' ? api.pickRepoDir : api.pickNotesDir
|
|
723
|
+
if (typeof fn !== 'function') return toast(repoNames[kind] + '功能不可用', 'err')
|
|
724
|
+
let r = null
|
|
725
|
+
try { r = fn.call(api) } catch (e) { return toast('打开目录选择失败:' + (e && e.message), 'err') }
|
|
726
|
+
if (!r || r.canceled) return
|
|
727
|
+
if (!r.ok) return toast(r.message || '操作失败', 'err')
|
|
728
|
+
toast(kind === 'pick' ? '工作区已切换' : '笔记目录已切换')
|
|
729
|
+
if (r.needReload) setTimeout(function () { try { win.location.reload() } catch (e) {} }, 400)
|
|
730
|
+
}
|
|
731
|
+
const repoBtn = $('s-repo-pick')
|
|
732
|
+
if (repoBtn) repoBtn.onclick = function () { runDirPick('pick') }
|
|
733
|
+
const notesBtn = $('s-notes-pick')
|
|
734
|
+
if (notesBtn) notesBtn.onclick = function () { runDirPick('notes') }
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
// 从远程仓库加载(要求空工作区):校验连通性 + 目录状态后才拉取
|
|
738
|
+
async function loadFromRemote () {
|
|
739
|
+
const url = await zgsPrompt(
|
|
740
|
+
'从远程仓库加载\n\n输入远端仓库地址。点「检测并继续」会先确认仓库可访问、当前工作区为空,通过后才开始拉取。',
|
|
741
|
+
'https://gitee.com/账号/仓库.git', '',
|
|
742
|
+
async function (v) {
|
|
743
|
+
const chk = await Promise.resolve(G && typeof G.checkRemote === 'function' ? G.checkRemote(v) : { ok: true })
|
|
744
|
+
if (!chk || !chk.ok) return { ok: false, message: '仓库地址不可用:' + ((chk && chk.message) || '无法连接') }
|
|
745
|
+
const pre = G && typeof G.adoptPrecheck === 'function' ? G.adoptPrecheck() : null
|
|
746
|
+
if (pre && !pre.ok) {
|
|
747
|
+
const items = (pre.blocking && pre.blocking.length)
|
|
748
|
+
? ':' + pre.blocking.slice(0, 3).join('、') + (pre.blocking.length > 3 ? ' 等' : '')
|
|
749
|
+
: ''
|
|
750
|
+
return { ok: false, message: '加载需要空工作区。' + (pre.reason || '') + items }
|
|
751
|
+
}
|
|
752
|
+
return { ok: true }
|
|
753
|
+
})
|
|
754
|
+
if (!url) return
|
|
755
|
+
toast('正在从远端拉取…', 'info')
|
|
756
|
+
let r = null
|
|
757
|
+
try { r = await Promise.resolve(G.adoptRepo({ remoteUrl: url, branch: 'main' })) } catch (e) { r = { ok: false, message: (e && e.message) || String(e) } }
|
|
758
|
+
if (r && r.ok) {
|
|
759
|
+
toast('已从远端加载完成', 'ok')
|
|
760
|
+
try { if (G.rediscover) G.rediscover() } catch (e) {}
|
|
761
|
+
refreshGitSection().catch(function () {})
|
|
762
|
+
|
|
763
|
+
// 暴露给外部一次性监听器:每次 render 更新引用,避免重复注册 addEventListener
|
|
764
|
+
win.__zgsRefreshGitSection = refreshGitSection
|
|
765
|
+
|
|
766
|
+
setTimeout(function () { try { if (win.__ZNOTES_REFRESH) win.__ZNOTES_REFRESH() } catch (e) {} }, 600)
|
|
767
|
+
} else {
|
|
768
|
+
toast('加载失败:' + ((r && r.message) || '未知原因'), 'err')
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const save = function () {
|
|
773
|
+
G.setConfig({
|
|
774
|
+
autoSync: $('s-auto').checked,
|
|
775
|
+
autoMode: $('s-mode') ? $('s-mode').value : 'interval',
|
|
776
|
+
intervalMs: parseInt($('s-interval').value, 10),
|
|
777
|
+
onStopEditMinutes: parseInt($('s-stop').value, 10)
|
|
778
|
+
})
|
|
779
|
+
toast('设置已保存', 'ok')
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const autoSw = $('s-auto')
|
|
783
|
+
if (autoSw) {
|
|
784
|
+
autoSw.onchange = function () {
|
|
785
|
+
const dr = $('s-auto-drawer')
|
|
786
|
+
if (dr) dr.classList.toggle('open', autoSw.checked)
|
|
787
|
+
save()
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const modeSel = $('s-mode')
|
|
791
|
+
if (modeSel) {
|
|
792
|
+
modeSel.onchange = function () {
|
|
793
|
+
const isIv = modeSel.value === 'interval'
|
|
794
|
+
const ivRow = $('s-interval-row')
|
|
795
|
+
const stRow = $('s-stop-row')
|
|
796
|
+
if (ivRow) ivRow.style.display = isIv ? '' : 'none'
|
|
797
|
+
if (stRow) stRow.style.display = isIv ? 'none' : ''
|
|
798
|
+
save()
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
;['s-interval', 's-stop'].forEach(function (id) {
|
|
802
|
+
const el = $(id)
|
|
803
|
+
if (el) el.onchange = save
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
// 仓库 / 状态信息(异步)
|
|
807
|
+
let st = {}
|
|
808
|
+
try {
|
|
809
|
+
const r = await G.state()
|
|
810
|
+
if (r && r.ok && r.data) st = r.data
|
|
811
|
+
} catch (e) {}
|
|
812
|
+
const repoEl = $('s-repo')
|
|
813
|
+
if (repoEl) {
|
|
814
|
+
const inspNow = (G && G.inspect) ? G.inspect() : { isRepo: false }
|
|
815
|
+
let remoteNow = { hasRemote: false, url: '' }
|
|
816
|
+
try { const rr = await G.remoteInfo(); if (rr && rr.ok) remoteNow = rr.data } catch (e) {}
|
|
817
|
+
let lastRun = 0
|
|
818
|
+
try { const cc = G.config(); if (cc && cc.ok) lastRun = cc.data.lastRunAt || 0 } catch (e) {}
|
|
819
|
+
const fmtTime = function (t) {
|
|
820
|
+
if (!t) return ''
|
|
821
|
+
try {
|
|
822
|
+
const diff = Date.now() - t
|
|
823
|
+
if (diff < 0) return ''
|
|
824
|
+
if (diff < 60000) return '刚刚'
|
|
825
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + ' 分钟前'
|
|
826
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + ' 小时前'
|
|
827
|
+
const d = new Date(t)
|
|
828
|
+
const p2 = function (n) { return n < 10 ? '0' + n : String(n) }
|
|
829
|
+
return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate())
|
|
830
|
+
} catch (e) { return '' }
|
|
831
|
+
}
|
|
832
|
+
const c2 = (G.config() || {}).data || {}
|
|
833
|
+
let rows = ''
|
|
834
|
+
rows += '<dt>本地仓库</dt><dd>' + (inspNow.isRepo ? '已绑定' + (st.branch ? ' · ' + esc(st.branch) : '') : '未绑定') + '</dd>'
|
|
835
|
+
rows += '<dt>远程仓库</dt><dd>' + (remoteNow.hasRemote ? '已绑定' : '未绑定') + '</dd>'
|
|
836
|
+
if (inspNow.isRepo) {
|
|
837
|
+
rows += '<dt>上次同步</dt><dd>' + (c2.lastRunAt ? new Date(c2.lastRunAt).toLocaleString('zh-CN') : '从未执行') + '</dd>'
|
|
838
|
+
if (c2.lastAutoResult) {
|
|
839
|
+
const ar = c2.lastAutoResult
|
|
840
|
+
rows += '<dt>最近结果</dt><dd>' + (ar.ok ? '成功' : '失败') + (ar.message ? ' · ' + esc(ar.message) : '') + '</dd>'
|
|
841
|
+
if (ar.error) rows += '<dt>错误</dt><dd>' + esc(ar.error) + '</dd>'
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (typeof st.dirty === 'number') rows += '<dt>未提交</dt><dd>' + st.dirty + ' 个文件</dd>'
|
|
845
|
+
if (G.scopeRel) rows += '<dt>笔记子目录</dt><dd class="mono">' + esc(G.scopeRel) + '</dd>'
|
|
846
|
+
repoEl.innerHTML = '<dl class="zgs-kv">' + rows + '</dl>'
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// ---------- 开关 ----------
|
|
851
|
+
// ZTools 的窗口策略是失焦即隐藏:打开资源管理器 / 外部授权页 / 原生目录选择框之后,
|
|
852
|
+
// 插件窗口会被宿主收起来。这里用 sessionStorage 记住面板是否开着,
|
|
853
|
+
// 窗口重新可见(或插件被重新呼出)时自动恢复,用户不用每次都重新点一遍。
|
|
854
|
+
const OPEN_KEY = 'cmark-settings-open'
|
|
855
|
+
function readOpenFlag () {
|
|
856
|
+
try { return win.sessionStorage.getItem(OPEN_KEY) === '1' } catch (e) { return false }
|
|
857
|
+
}
|
|
858
|
+
function writeOpenFlag (on) {
|
|
859
|
+
try { win.sessionStorage.setItem(OPEN_KEY, on ? '1' : '0') } catch (e) {}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function open () {
|
|
863
|
+
if (root.classList.contains('open')) return
|
|
864
|
+
root.classList.add('open')
|
|
865
|
+
writeOpenFlag(true)
|
|
866
|
+
render()
|
|
867
|
+
}
|
|
868
|
+
function close () {
|
|
869
|
+
root.classList.remove('open')
|
|
870
|
+
writeOpenFlag(false)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// 窗口从隐藏变回可见时恢复面板
|
|
874
|
+
try {
|
|
875
|
+
doc.addEventListener('visibilitychange', function () {
|
|
876
|
+
if (doc.visibilityState === 'visible' && readOpenFlag() && !root.classList.contains('open')) open()
|
|
877
|
+
})
|
|
878
|
+
} catch (e) {}
|
|
879
|
+
// 补一条:宿主重新进入插件时也恢复(部分场景不触发 visibilitychange)
|
|
880
|
+
try {
|
|
881
|
+
if (win.ztools && typeof win.ztools.onPluginEnter === 'function') {
|
|
882
|
+
win.ztools.onPluginEnter(function () { if (readOpenFlag()) open() })
|
|
883
|
+
}
|
|
884
|
+
} catch (e) {}
|
|
885
|
+
// 首次挂载时若上次会话面板是开的,直接打开
|
|
886
|
+
if (readOpenFlag()) setTimeout(function () { try { open() } catch (e) {} }, 80)
|
|
887
|
+
function toggle () {
|
|
888
|
+
if (root.classList.contains('open')) close()
|
|
889
|
+
else open()
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
$('zgs-close').onclick = close
|
|
893
|
+
doc.addEventListener('keydown', function (e) {
|
|
894
|
+
if (e.key === 'Escape' && root.classList.contains('open')) close()
|
|
895
|
+
})
|
|
896
|
+
|
|
897
|
+
// 点击设置面板以外的地方(编辑器 / 空状态)自动收起,和 Git 面板行为一致
|
|
898
|
+
function maybeHide (target) {
|
|
899
|
+
if (!root.classList.contains('open')) return
|
|
900
|
+
const el = target && target.nodeType === 1 ? target : (target && target.parentElement) || null
|
|
901
|
+
if (!el) return
|
|
902
|
+
if (el.closest('#zgs-root') || el.closest('#zgp-root') || el.closest('#zgp-view') || el.closest('#znotes-sbtn')) return
|
|
903
|
+
if (el.closest('.editor-body') || el.closest('[contenteditable="true"]') || el.closest('.worknote-empty')) close()
|
|
904
|
+
}
|
|
905
|
+
doc.addEventListener('mousedown', function (e) { maybeHide(e.target) }, true)
|
|
906
|
+
|
|
907
|
+
// ---------- 侧边栏底部按钮 ----------
|
|
908
|
+
function mountSidebarButton () {
|
|
909
|
+
const body = doc.querySelector('.sidebar-body')
|
|
910
|
+
if (!body) return false
|
|
911
|
+
if (doc.getElementById('znotes-sbtn-wrap')) return true
|
|
912
|
+
const wrap = doc.createElement('div')
|
|
913
|
+
wrap.id = 'znotes-sbtn-wrap'
|
|
914
|
+
wrap.className = 'znotes-footer'
|
|
915
|
+
wrap.innerHTML = '<button id="znotes-sbtn" class="znotes-sbtn" title="设置:笔记目录、自动同步、提交选项">' +
|
|
916
|
+
ICON.gear + '<span>设置</span></button>'
|
|
917
|
+
body.appendChild(wrap)
|
|
918
|
+
const btn = wrap.querySelector('#znotes-sbtn')
|
|
919
|
+
btn.onclick = function (ev) {
|
|
920
|
+
ev.stopPropagation()
|
|
921
|
+
toggle()
|
|
922
|
+
}
|
|
923
|
+
// 让树所在的那个(无 class 的)容器成为可收缩的 flex 项,
|
|
924
|
+
// 否则树的高度会把侧边栏撑破、盖住底部按钮栏
|
|
925
|
+
const treeEl = doc.querySelector('.sidebar-tree')
|
|
926
|
+
if (treeEl && treeEl.parentElement && treeEl.parentElement !== body) {
|
|
927
|
+
const holder = treeEl.parentElement
|
|
928
|
+
holder.style.flex = '1 1 auto'
|
|
929
|
+
holder.style.minHeight = '0'
|
|
930
|
+
holder.style.overflow = 'hidden'
|
|
931
|
+
treeEl.style.height = '100%'
|
|
932
|
+
treeEl.style.boxSizing = 'border-box'
|
|
933
|
+
}
|
|
934
|
+
return true
|
|
935
|
+
}
|
|
936
|
+
// 侧边栏可能是异步渲染的,重试几次
|
|
937
|
+
if (!mountSidebarButton()) {
|
|
938
|
+
let tries = 0
|
|
939
|
+
const t = setInterval(function () {
|
|
940
|
+
tries++
|
|
941
|
+
if (mountSidebarButton() || tries > 40) clearInterval(t)
|
|
942
|
+
}, 500)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// 暴露给 bundle(Git 面板标题栏不需要,留接口给后续)
|
|
946
|
+
win.__zgsToggle = toggle
|
|
947
|
+
win.__zgsOpen = open
|
|
948
|
+
win.__zgsClose = close
|
|
949
|
+
win.__zgsIsOpen = function () { return root.classList.contains('open') }
|
|
950
|
+
// 建仓向导完成 / 开关变更时重算仓库状态(只注册一次;render 会被反复调用)
|
|
951
|
+
try {
|
|
952
|
+
win.addEventListener('cmark-git-changed', function () {
|
|
953
|
+
try { if (typeof win.__zgsRefreshGitSection === 'function') win.__zgsRefreshGitSection() } catch (e) {}
|
|
954
|
+
})
|
|
955
|
+
} catch (e) {}
|
|
956
|
+
|
|
957
|
+
win.__zgsRender = render
|
|
958
|
+
logBoot('done, btn=' + !!doc.getElementById('znotes-sbtn'))
|
|
959
|
+
}
|