@lancc/cmark 0.0.0-stage → 1.1.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 +17 -0
- package/LICENSE +21 -0
- package/README.md +185 -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-BLONSItV.css +1 -0
- package/assets/index-Cz6qaZbV.js +744 -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 +241 -0
- package/preload/branch-bar.js +159 -0
- package/preload/git-panel.js +1311 -0
- package/preload/git-service.js +1403 -0
- package/preload/ndb.js +1407 -0
- package/preload/package.json +3 -0
- package/preload/rename-note.js +151 -0
- package/preload/services.js +432 -0
- package/preload/settings-panel.js +847 -0
- package/preload.js +1615 -0
|
@@ -0,0 +1,847 @@
|
|
|
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 .zgp-in[readonly]{opacity:.8;cursor:default;background:rgba(127,127,127,.08);}
|
|
121
|
+
#zgs-body .zgp-in{padding-left:12px;padding-right:12px;height:34px;box-sizing:border-box;}
|
|
122
|
+
#zgs-body .zgp-in:focus{outline:none;border-color:rgba(120,160,255,.55);
|
|
123
|
+
box-shadow:0 0 0 2px rgba(120,160,255,.16);}
|
|
124
|
+
#zgs-body .zgp-b{min-height:30px;}
|
|
125
|
+
#zgs-body .zgp-sec{padding-top:16px;padding-bottom:16px;}
|
|
126
|
+
#zgs-body .zgp-row{gap:8px;}
|
|
127
|
+
`
|
|
128
|
+
try { (doc.head || doc.body).appendChild(style) } catch (e0) { logBoot('appendStyle failed: ' + e0.message) }
|
|
129
|
+
|
|
130
|
+
// ---------- 面板容器 ----------
|
|
131
|
+
const root = doc.createElement('div')
|
|
132
|
+
root.id = 'zgs-root'
|
|
133
|
+
root.innerHTML = `
|
|
134
|
+
<div class="zgs-head">
|
|
135
|
+
<div class="zgs-title">${ICON.gear}<span>设置</span></div>
|
|
136
|
+
<button class="zgp-close" id="zgs-close" title="关闭 (Esc)">${ICON.x}</button>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="zgs-body" id="zgs-body"></div>
|
|
139
|
+
`
|
|
140
|
+
doc.body.appendChild(root)
|
|
141
|
+
logBoot('panel mounted, sidebar-body=' + !!doc.querySelector('.sidebar-body'))
|
|
142
|
+
|
|
143
|
+
const $ = (id) => doc.getElementById(id)
|
|
144
|
+
|
|
145
|
+
// ---------- Toast(面板内既有 toast 在 git-panel 里,独立时自建一个轻提示) ----------
|
|
146
|
+
let toastTimer = null
|
|
147
|
+
function toast (text, kind) {
|
|
148
|
+
let el = doc.getElementById('zgs-toast')
|
|
149
|
+
if (!el) {
|
|
150
|
+
el = doc.createElement('div')
|
|
151
|
+
el.id = 'zgs-toast'
|
|
152
|
+
el.style.cssText = 'position:fixed;left:50%;bottom:28px;transform:translateX(-50%);' +
|
|
153
|
+
'padding:8px 16px;border-radius:8px;font-size:12.5px;z-index:99999;opacity:0;' +
|
|
154
|
+
'pointer-events:none;transition:opacity .18s;background:rgba(30,30,34,.95);color:#eee;' +
|
|
155
|
+
'border:1px solid rgba(255,255,255,.14)'
|
|
156
|
+
doc.body.appendChild(el)
|
|
157
|
+
}
|
|
158
|
+
el.textContent = text || ''
|
|
159
|
+
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)' }
|
|
160
|
+
el.style.opacity = '1'
|
|
161
|
+
clearTimeout(toastTimer)
|
|
162
|
+
toastTimer = setTimeout(function () { el.style.opacity = '0' }, 2600)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------- 二次确认(破坏性操作用) ----------
|
|
166
|
+
function zgsConfirm (message, okText) {
|
|
167
|
+
return new Promise(function (resolve) {
|
|
168
|
+
const mask = doc.createElement('div')
|
|
169
|
+
mask.className = 'zgs-mask'
|
|
170
|
+
mask.innerHTML = '<div class="zgs-dlg">' +
|
|
171
|
+
'<div class="zgs-dlg-msg">' + esc(String(message || '')).replace(/\n/g, '<br>') + '</div>' +
|
|
172
|
+
'<div class="zgs-dlg-row">' +
|
|
173
|
+
'<button class="zgp-b" data-r="0">取消</button>' +
|
|
174
|
+
'<button class="zgp-b primary" data-r="1">' + esc(okText || '确定') + '</button>' +
|
|
175
|
+
'</div></div>'
|
|
176
|
+
;(root || doc.body).appendChild(mask)
|
|
177
|
+
mask.onclick = function (ev) {
|
|
178
|
+
const btn = ev.target && ev.target.closest ? ev.target.closest('[data-r]') : null
|
|
179
|
+
if (!btn) return
|
|
180
|
+
const ok = btn.getAttribute('data-r') === '1'
|
|
181
|
+
try { mask.remove() } catch (e) {}
|
|
182
|
+
resolve(ok)
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 带输入框的弹窗。传了 onConfirm 就是"异步校验"模式:
|
|
188
|
+
// 点确定后不关窗,先跑 onConfirm(value),成功才关;失败把原因显示在弹窗里,用户可以直接改地址重试。
|
|
189
|
+
function zgsPrompt (title, placeholder, initial, onConfirm) {
|
|
190
|
+
return new Promise(function (resolve) {
|
|
191
|
+
const hasCheck = typeof onConfirm === 'function'
|
|
192
|
+
const mask = doc.createElement('div')
|
|
193
|
+
mask.className = 'zgs-mask'
|
|
194
|
+
mask.innerHTML = '<div class="zgs-dlg">' +
|
|
195
|
+
'<div class="zgs-dlg-msg">' + esc(String(title || '')).replace(/\n/g, '<br>') + '</div>' +
|
|
196
|
+
'<input class="zgp-in zgs-dlg-in" type="text" placeholder="' + esc(placeholder || '') +
|
|
197
|
+
'" value="' + esc(initial || '') + '" spellcheck="false">' +
|
|
198
|
+
'<div class="zgs-dlg-err"></div>' +
|
|
199
|
+
'<div class="zgs-dlg-row">' +
|
|
200
|
+
'<button class="zgp-b" data-r="0">取消</button>' +
|
|
201
|
+
'<button class="zgp-b primary zgs-dlg-ok" type="button">' + (hasCheck ? '检测并继续' : '保存') + '</button>' +
|
|
202
|
+
'</div></div>'
|
|
203
|
+
;(root || doc.body).appendChild(mask)
|
|
204
|
+
const inp = mask.querySelector('.zgs-dlg-in')
|
|
205
|
+
const errEl = mask.querySelector('.zgs-dlg-err')
|
|
206
|
+
const okBtn = mask.querySelector('.zgs-dlg-ok')
|
|
207
|
+
let settled = false
|
|
208
|
+
let busy = false
|
|
209
|
+
const showErr = function (t) {
|
|
210
|
+
if (!errEl) return
|
|
211
|
+
errEl.textContent = t || ''
|
|
212
|
+
errEl.style.display = t ? 'block' : 'none'
|
|
213
|
+
}
|
|
214
|
+
const done = function (val) {
|
|
215
|
+
if (settled) return
|
|
216
|
+
settled = true
|
|
217
|
+
try { mask.remove() } catch (e) {}
|
|
218
|
+
resolve(val)
|
|
219
|
+
}
|
|
220
|
+
const submit = async function () {
|
|
221
|
+
if (busy || settled) return
|
|
222
|
+
const v = ((inp && inp.value) || '').trim()
|
|
223
|
+
if (!v) { showErr('请填写仓库地址'); return }
|
|
224
|
+
if (!hasCheck) { done(v); return }
|
|
225
|
+
busy = true
|
|
226
|
+
showErr('')
|
|
227
|
+
const label = okBtn.textContent
|
|
228
|
+
okBtn.disabled = true
|
|
229
|
+
okBtn.textContent = '检测中…'
|
|
230
|
+
let res = null
|
|
231
|
+
try { res = await onConfirm(v) } catch (e) { res = { ok: false, message: (e && e.message) || String(e) } }
|
|
232
|
+
okBtn.disabled = false
|
|
233
|
+
okBtn.textContent = label
|
|
234
|
+
busy = false
|
|
235
|
+
if (res && res.ok) done(v)
|
|
236
|
+
else showErr((res && res.message) || '校验失败,请检查地址')
|
|
237
|
+
}
|
|
238
|
+
if (okBtn) okBtn.onclick = function (ev) { if (ev) ev.stopPropagation(); submit() }
|
|
239
|
+
mask.onclick = function (ev) {
|
|
240
|
+
const btn = ev.target && ev.target.closest ? ev.target.closest('[data-r="0"]') : null
|
|
241
|
+
if (btn) { done(null); return }
|
|
242
|
+
if (ev.target === mask) done(null)
|
|
243
|
+
}
|
|
244
|
+
mask.onkeydown = function (ev) {
|
|
245
|
+
if (ev.key === 'Enter') { ev.preventDefault(); submit() }
|
|
246
|
+
else if (ev.key === 'Escape') { ev.preventDefault(); done(null) }
|
|
247
|
+
}
|
|
248
|
+
setTimeout(function () { try { inp.focus(); inp.select() } catch (e) {} }, 30)
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ---------- 渲染 ----------
|
|
253
|
+
async function render () {
|
|
254
|
+
if (!G) {
|
|
255
|
+
$('zgs-body').innerHTML = '<div class="zgp-sec"><h5>不可用</h5><div class="zgp-desc">Git 服务未加载</div></div>'
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
const cfg = (G.config() || {}).data || {}
|
|
259
|
+
const ivOpts = [[60000, '1 分钟'], [300000, '5 分钟'], [900000, '15 分钟'], [1800000, '30 分钟'], [3600000, '1 小时']]
|
|
260
|
+
const mode = cfg.autoMode || 'interval'
|
|
261
|
+
const ivHtml = ivOpts.map(function (p) {
|
|
262
|
+
return '<option value="' + p[0] + '"' + (cfg.intervalMs === p[0] ? ' selected' : '') + '>' + p[1] + '</option>'
|
|
263
|
+
}).join('')
|
|
264
|
+
const stopOpts = [[0, '关闭'], [1, '1 分钟'], [3, '3 分钟'], [5, '5 分钟'], [10, '10 分钟']]
|
|
265
|
+
const stopHtml = stopOpts.map(function (p) {
|
|
266
|
+
return '<option value="' + p[0] + '"' + (cfg.onStopEditMinutes === p[0] ? ' selected' : '') + '>' + p[1] + '</option>'
|
|
267
|
+
}).join('')
|
|
268
|
+
|
|
269
|
+
$('zgs-body').innerHTML = `
|
|
270
|
+
<div class="zgp-sec">
|
|
271
|
+
<h5>笔记目录</h5>
|
|
272
|
+
<dl class="zgs-kv"><dt>当前目录</dt><dd class="mono" id="s-ws">${esc(win.__ZNOTES_WS || '-')}</dd></dl>
|
|
273
|
+
<div class="zgp-row" style="margin-top:8px">
|
|
274
|
+
<button class="zgp-b" id="s-ws-pick">更换目录</button>
|
|
275
|
+
<button class="zgp-b" id="s-ws-open">在资源管理器打开</button>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="zgp-desc">更换后重新扫描该目录下的 Markdown 文件;文件本身不会被移动或修改。</div>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="zgp-sec">
|
|
280
|
+
<h5>版本控制</h5>
|
|
281
|
+
<dl class="zgs-kv" id="s-vc-kv">
|
|
282
|
+
<dt>状态</dt><dd><span class="zgs-dot off"></span>读取中…</dd>
|
|
283
|
+
</dl>
|
|
284
|
+
|
|
285
|
+
<div id="s-vc-init" style="display:none">
|
|
286
|
+
<div class="zgp-row" style="margin-top:14px">
|
|
287
|
+
<button class="zgp-b primary" id="s-vc-init-btn">初始化本地仓库</button>
|
|
288
|
+
<button class="zgp-b" id="s-vc-adopt-btn">从远程仓库加载</button>
|
|
289
|
+
</div>
|
|
290
|
+
<div class="zgp-desc">
|
|
291
|
+
初始化:在本地建立版本历史,之后可再关联远端。<br>
|
|
292
|
+
从远程加载:把远端已有的笔记拉到本地,要求工作区为空。
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<div id="s-vc-ready" style="display:none">
|
|
297
|
+
<!-- 只有本机仓库、还没绑定远端时,只露一个入口,不先摆一个空地址框 -->
|
|
298
|
+
<div id="s-vc-link-row" style="display:none">
|
|
299
|
+
<div class="zgp-row" style="margin-top:14px">
|
|
300
|
+
<button class="zgp-b primary" id="s-vc-link-btn">将本地仓库关联到远程</button>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
<!-- 已关联远端(或点了上面的按钮)才展开地址框 -->
|
|
304
|
+
<div id="s-vc-remote-box" style="display:none">
|
|
305
|
+
<div class="zgp-row" style="margin-top:12px">
|
|
306
|
+
<button class="zgp-b danger" id="s-vc-remote-clear">移除远端</button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="zgp-sec">
|
|
312
|
+
<h5>提交</h5>
|
|
313
|
+
<div class="zgp-field">
|
|
314
|
+
<div class="zgp-lab"><span>自定义模板</span>
|
|
315
|
+
<label class="zgp-sw"><input type="checkbox" id="s-msg-custom"><i></i></label>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="zgp-desc">关闭时使用默认模板与全局 git 身份;打开后可以分别自定义。</div>
|
|
318
|
+
</div>
|
|
319
|
+
<div class="zgp-drawer" id="s-msg-drawer">
|
|
320
|
+
<div class="zgp-field">
|
|
321
|
+
<label class="zgs-lab" for="s-msg">提交信息模板</label>
|
|
322
|
+
<input class="zgp-in" id="s-msg" value="${esc(cfg.commitMessage)}">
|
|
323
|
+
<div class="zgp-desc">可用变量:{{date}} {{time}} {{datetime}} {{count}}</div>
|
|
324
|
+
</div>
|
|
325
|
+
<div class="zgp-field">
|
|
326
|
+
<label class="zgs-lab" for="s-vc-name2">提交者姓名</label>
|
|
327
|
+
<input class="zgp-in" id="s-vc-name2" placeholder="留空则使用全局 git 配置">
|
|
328
|
+
<label class="zgs-lab" for="s-vc-email2" style="margin-top:10px">提交者邮箱</label>
|
|
329
|
+
<input class="zgp-in" id="s-vc-email2" placeholder="留空则使用全局 git 配置">
|
|
330
|
+
<div class="zgp-row" style="margin-top:10px">
|
|
331
|
+
<button class="zgp-b" id="s-vc-id-save">保存提交者身份</button>
|
|
332
|
+
</div>
|
|
333
|
+
<div class="zgp-desc" id="s-vc-id-hint2"></div>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<div class="zgp-sec">
|
|
338
|
+
<h5>自动同步</h5>
|
|
339
|
+
<div class="zgp-field">
|
|
340
|
+
<div class="zgp-lab"><span>定时提交并推送</span>
|
|
341
|
+
<label class="zgp-sw"><input type="checkbox" id="s-auto" ${cfg.autoSync ? 'checked' : ''}><i></i></label>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="zgp-desc">按下方策略执行;只在有改动时才产生提交。</div>
|
|
344
|
+
</div>
|
|
345
|
+
<div class="zgp-drawer${cfg.autoSync ? ' open' : ''}" id="s-auto-drawer">
|
|
346
|
+
<div class="zgp-field">
|
|
347
|
+
<div class="zgp-lab"><span>触发方式</span></div>
|
|
348
|
+
<select class="zgp-sel" id="s-mode">
|
|
349
|
+
<option value="interval"${mode === 'interval' ? ' selected' : ''}>固定间隔</option>
|
|
350
|
+
<option value="stop-edit"${mode === 'stop-edit' ? ' selected' : ''}>停止编辑后</option>
|
|
351
|
+
</select>
|
|
352
|
+
<div class="zgp-desc">两种方式互斥,只能选一种。</div>
|
|
353
|
+
</div>
|
|
354
|
+
<div class="zgp-field" id="s-interval-row"${mode === 'interval' ? '' : ' style="display:none"'}>
|
|
355
|
+
<div class="zgp-lab"><span>间隔时长</span></div>
|
|
356
|
+
<select class="zgp-sel" id="s-interval">${ivHtml}</select>
|
|
357
|
+
<div class="zgp-desc">跨会话生效:即使插件没开够一个间隔,下次启动会补一次。</div>
|
|
358
|
+
</div>
|
|
359
|
+
<div class="zgp-field" id="s-stop-row"${mode === 'stop-edit' ? '' : ' style="display:none"'}>
|
|
360
|
+
<div class="zgp-lab"><span>静默时长</span></div>
|
|
361
|
+
<select class="zgp-sel" id="s-stop">${stopHtml}</select>
|
|
362
|
+
<div class="zgp-desc">停止输入 N 分钟后触发一次,适合边写边自动保存。</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="zgp-sec">
|
|
367
|
+
<h5>运行状态</h5>
|
|
368
|
+
<div class="zgp-desc" id="s-repo">读取中…</div>
|
|
369
|
+
<div class="zgp-desc" id="s-info"></div>
|
|
370
|
+
<div class="zgp-desc" id="s-attach" style="margin-top:8px">附件:统计中…</div>
|
|
371
|
+
<div style="margin-top:8px">
|
|
372
|
+
<button class="zgp-b" id="s-attach-clean" title="直接删除没有任何笔记引用的附件(不做回收站,误删可从 git 历史取回)">清理未引用附件</button>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
`
|
|
376
|
+
|
|
377
|
+
// 附件统计 + 清理未引用附件。之前这个按钮根本没绑处理函数,点了没反应。
|
|
378
|
+
// 清理是真删(不做回收站),所以必须先给用户看清楚会删几个、多大。
|
|
379
|
+
const attachBox = $('s-attach')
|
|
380
|
+
const attachBtn = $('s-attach-clean')
|
|
381
|
+
const fmtSize = function (n) {
|
|
382
|
+
const v = Number(n) || 0
|
|
383
|
+
if (v >= 1048576) return (v / 1048576).toFixed(1) + ' MB'
|
|
384
|
+
if (v >= 1024) return (v / 1024).toFixed(0) + ' KB'
|
|
385
|
+
return v + ' B'
|
|
386
|
+
}
|
|
387
|
+
const refreshAttachStat = function () {
|
|
388
|
+
if (!attachBox) return
|
|
389
|
+
try {
|
|
390
|
+
const api = win.__CMARK
|
|
391
|
+
const s = api && api.scanAttachments ? api.scanAttachments() : null
|
|
392
|
+
if (!s || !s.ok) {
|
|
393
|
+
attachBox.textContent = '附件:读取失败' + (s && s.message ? '(' + s.message + ')' : '')
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
const total = Number(s.total) || 0
|
|
397
|
+
const unused = (s.unused || []).length
|
|
398
|
+
attachBox.textContent =
|
|
399
|
+
'附件:' + total + ' 个,共 ' + fmtSize(s.allBytes) +
|
|
400
|
+
';未引用 ' + unused + ' 个(' + fmtSize(s.unusedBytes) + ')'
|
|
401
|
+
} catch (e) {}
|
|
402
|
+
}
|
|
403
|
+
refreshAttachStat()
|
|
404
|
+
if (attachBtn) {
|
|
405
|
+
attachBtn.onclick = function () {
|
|
406
|
+
try {
|
|
407
|
+
const api = win.__CMARK
|
|
408
|
+
if (!api || !api.cleanAttachments) return toast('清理功能不可用', 'err')
|
|
409
|
+
const s = api.scanAttachments ? api.scanAttachments() : null
|
|
410
|
+
const n = s && s.ok ? (s.unused || []).length : 0
|
|
411
|
+
if (!n) return toast('没有未被引用的附件')
|
|
412
|
+
const msg =
|
|
413
|
+
'直接删除 ' + n + ' 个未被引用的附件(' + fmtSize(s.unusedBytes) + ')?\n' +
|
|
414
|
+
'删除后不可撤销;这些附件如果已经提交过,可以从 git 历史里找回。'
|
|
415
|
+
zgsConfirm(msg, '删除').then(function (ok) {
|
|
416
|
+
if (!ok) return
|
|
417
|
+
const r = api.cleanAttachments()
|
|
418
|
+
if (!r || !r.ok) return toast('清理失败:' + ((r && r.message) || '未知错误'), 'err')
|
|
419
|
+
toast('已删除 ' + (r.deleted || 0) + ' 个附件')
|
|
420
|
+
refreshAttachStat()
|
|
421
|
+
})
|
|
422
|
+
} catch (e) {
|
|
423
|
+
toast('清理失败:' + (e && e.message), 'err')
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const pickBtn = $('s-ws-pick')
|
|
429
|
+
if (pickBtn) {
|
|
430
|
+
pickBtn.onclick = function () {
|
|
431
|
+
try {
|
|
432
|
+
if (win.__znotesPickDir) win.__znotesPickDir()
|
|
433
|
+
else toast('更换目录功能不可用', 'err')
|
|
434
|
+
} catch (e) {
|
|
435
|
+
toast('打开目录选择失败:' + (e && e.message), 'err')
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const openWsBtn = $('s-ws-open')
|
|
441
|
+
if (openWsBtn) {
|
|
442
|
+
openWsBtn.onclick = function () {
|
|
443
|
+
try {
|
|
444
|
+
const p = win.__ZNOTES_WS
|
|
445
|
+
if (!p) return
|
|
446
|
+
if (win.ztools && typeof win.ztools.shellOpenPath === 'function') win.ztools.shellOpenPath(p)
|
|
447
|
+
else if (win.ztools && typeof win.ztools.shellOpenExternal === 'function') win.ztools.shellOpenExternal('file://' + p)
|
|
448
|
+
} catch (e) { toast('打开失败:' + (e && e.message), 'err') }
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ---- 版本控制(按仓库状态渲染两种形态)----
|
|
453
|
+
async function refreshVc () {
|
|
454
|
+
let st = {}
|
|
455
|
+
try {
|
|
456
|
+
// inspect() 不含分支,用 state() 才能拿到 branch
|
|
457
|
+
const r = await Promise.resolve(G && typeof G.state === 'function' ? G.state() : null)
|
|
458
|
+
st = (r && r.ok && r.data) ? r.data : ((G && typeof G.inspect === 'function') ? G.inspect() : {})
|
|
459
|
+
} catch (e) {}
|
|
460
|
+
const isRepo = !!(st && st.isRepo)
|
|
461
|
+
const initBox = $('s-vc-init')
|
|
462
|
+
const readyBox = $('s-vc-ready')
|
|
463
|
+
const kvEl = $('s-vc-kv')
|
|
464
|
+
const autoSw = $('s-auto')
|
|
465
|
+
if (autoSw) {
|
|
466
|
+
autoSw.disabled = !isRepo
|
|
467
|
+
if (!isRepo) autoSw.checked = false
|
|
468
|
+
}
|
|
469
|
+
// 这一段只回答"配置成什么样":启用没有、绑没绑远端。
|
|
470
|
+
// 仓库根 / 分支 / 改动数属于运行诊断,统一放在面板底部的「运行状态」里,避免两处重复。
|
|
471
|
+
let rdKv = {}
|
|
472
|
+
try {
|
|
473
|
+
const ri = await Promise.resolve(G && typeof G.remoteInfo === 'function' ? G.remoteInfo() : null)
|
|
474
|
+
rdKv = (ri && ri.ok && ri.data) || {}
|
|
475
|
+
} catch (e) {}
|
|
476
|
+
if (kvEl) {
|
|
477
|
+
// 状态卡就是这一块的完整现状:状态 / 本地仓库路径 / 远程仓库地址
|
|
478
|
+
kvEl.innerHTML = isRepo
|
|
479
|
+
? '<dt>状态</dt><dd><span class="zgs-dot"></span>已启用</dd>' +
|
|
480
|
+
'<dt>本地仓库</dt><dd class="mono zgs-kv-url">' + esc(st.repoRoot || '-') + '</dd>' +
|
|
481
|
+
'<dt>远程仓库</dt><dd' + (rdKv.hasRemote ? ' class="mono zgs-kv-url"' : '') + '>' +
|
|
482
|
+
(rdKv.hasRemote ? esc(rdKv.url || '-') : '未绑定') + '</dd>'
|
|
483
|
+
: '<dt>状态</dt><dd><span class="zgs-dot off"></span>未启用</dd>'
|
|
484
|
+
}
|
|
485
|
+
if (initBox) initBox.style.display = isRepo ? 'none' : ''
|
|
486
|
+
if (readyBox) readyBox.style.display = isRepo ? '' : 'none'
|
|
487
|
+
|
|
488
|
+
if (!isRepo) {
|
|
489
|
+
// 预填全局身份:否则新机器首次提交会失败,initRepo 会回滚刚建的 .git
|
|
490
|
+
let d = {}
|
|
491
|
+
try {
|
|
492
|
+
const id = await Promise.resolve(G && typeof G.identity === 'function' ? G.identity() : null)
|
|
493
|
+
d = (id && id.ok && id.data) || {}
|
|
494
|
+
} catch (e) {}
|
|
495
|
+
// 提交者身份沿用全局 git 配置,未启用时不再单独给输入框(要改去「提交」分区)
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// 已初始化:只在本机 → 露「关联到远程」入口;已绑定 → 露「移除远端」
|
|
500
|
+
const rd = rdKv
|
|
501
|
+
const linkRow = $('s-vc-link-row')
|
|
502
|
+
const remoteBox = $('s-vc-remote-box')
|
|
503
|
+
if (linkRow) linkRow.style.display = rd.hasRemote ? 'none' : 'block'
|
|
504
|
+
if (remoteBox) remoteBox.style.display = rd.hasRemote ? 'block' : 'none'
|
|
505
|
+
|
|
506
|
+
// 「提交」分区抽屉里的身份回填:收起状态也先填好,展开就能看到当前值
|
|
507
|
+
const n2 = $('s-vc-name2')
|
|
508
|
+
const e2 = $('s-vc-email2')
|
|
509
|
+
if (n2 || e2) {
|
|
510
|
+
let d = {}
|
|
511
|
+
try {
|
|
512
|
+
const id = await Promise.resolve(G && typeof G.identity === 'function' ? G.identity() : null)
|
|
513
|
+
d = (id && id.ok && id.data) || {}
|
|
514
|
+
} catch (e) {}
|
|
515
|
+
if (n2 && !n2.value) n2.value = d.localName || ''
|
|
516
|
+
if (e2 && !e2.value) e2.value = d.localEmail || ''
|
|
517
|
+
const hint = $('s-vc-id-hint2')
|
|
518
|
+
if (hint) {
|
|
519
|
+
hint.textContent = (d.localName || d.localEmail)
|
|
520
|
+
? '当前使用仓库内的身份,覆盖全局配置。'
|
|
521
|
+
: '当前沿用全局 git 配置:' + ([d.globalName, d.globalEmail].filter(Boolean).join(' / ') || '未配置')
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// 渲染完成后拉一次版本控制状态,填充状态卡并按仓库状态切换两块形态
|
|
527
|
+
// (这行之前被误删,导致面板一直停在"读取中…")
|
|
528
|
+
refreshVc().catch(function () {})
|
|
529
|
+
|
|
530
|
+
const initBtn = $('s-vc-init-btn')
|
|
531
|
+
if (initBtn) {
|
|
532
|
+
initBtn.onclick = async function () {
|
|
533
|
+
const branch = 'main' // 分支固定 main
|
|
534
|
+
const ok = await zgsConfirm(
|
|
535
|
+
'在\n' + (win.__ZNOTES_WS || '-') + '\n初始化本地 git 仓库?\n\n' +
|
|
536
|
+
'现有笔记会被提交一次,内容不会被修改。\n' +
|
|
537
|
+
'之后可以在下面把本地仓库关联到远端。', '初始化')
|
|
538
|
+
if (!ok) return
|
|
539
|
+
initBtn.disabled = true
|
|
540
|
+
try { if (win.__znotesFlushSave) win.__znotesFlushSave() } catch (e) {}
|
|
541
|
+
let r = null
|
|
542
|
+
try {
|
|
543
|
+
r = await G.initRepo({
|
|
544
|
+
mode: 'local',
|
|
545
|
+
branch: branch
|
|
546
|
+
})
|
|
547
|
+
} catch (e) { r = { ok: false, message: (e && e.message) || String(e) } }
|
|
548
|
+
initBtn.disabled = false
|
|
549
|
+
if (r && r.ok) {
|
|
550
|
+
toast('仓库已初始化', 'ok')
|
|
551
|
+
try { if (G.rediscover) G.rediscover() } catch (e) {}
|
|
552
|
+
try { if (win.__zgpRefresh) win.__zgpRefresh() } catch (e) {}
|
|
553
|
+
render()
|
|
554
|
+
} else {
|
|
555
|
+
toast('初始化失败:' + ((r && r.message) || '未知原因'), 'err')
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// 从远程仓库加载:弹窗填地址 → 校验连通性 + 空目录 → 通过才拉取
|
|
561
|
+
const adoptBtn = $('s-vc-adopt-btn')
|
|
562
|
+
if (adoptBtn) {
|
|
563
|
+
adoptBtn.onclick = async function () {
|
|
564
|
+
const url = await zgsPrompt(
|
|
565
|
+
'从远程仓库加载\n\n输入远端仓库地址。点「检测并继续」会先确认仓库可访问、当前工作区为空,通过后才开始拉取。',
|
|
566
|
+
'https://gitee.com/账号/仓库.git', '',
|
|
567
|
+
async function (v) {
|
|
568
|
+
const chk = await Promise.resolve(
|
|
569
|
+
G && typeof G.checkRemote === 'function' ? G.checkRemote(v) : { ok: true })
|
|
570
|
+
if (!chk || !chk.ok) {
|
|
571
|
+
return { ok: false, message: '仓库地址不可用:' + ((chk && chk.message) || '无法连接') }
|
|
572
|
+
}
|
|
573
|
+
const pre = G && typeof G.adoptPrecheck === 'function' ? G.adoptPrecheck() : null
|
|
574
|
+
if (pre && !pre.ok) {
|
|
575
|
+
const items = (pre.blocking && pre.blocking.length)
|
|
576
|
+
? ':' + pre.blocking.slice(0, 3).join('、') + (pre.blocking.length > 3 ? ' 等' : '')
|
|
577
|
+
: ''
|
|
578
|
+
return { ok: false, message: '加载需要空工作区。' + (pre.reason || '') + items }
|
|
579
|
+
}
|
|
580
|
+
return { ok: true }
|
|
581
|
+
})
|
|
582
|
+
if (!url) return
|
|
583
|
+
toast('正在从远端拉取…', 'info')
|
|
584
|
+
let r = null
|
|
585
|
+
try {
|
|
586
|
+
r = await Promise.resolve(G.adoptRepo({ remoteUrl: url, branch: 'main' }))
|
|
587
|
+
} catch (e) {
|
|
588
|
+
r = { ok: false, message: (e && e.message) || String(e) }
|
|
589
|
+
}
|
|
590
|
+
if (r && r.ok) {
|
|
591
|
+
toast('已从远端加载完成', 'ok')
|
|
592
|
+
try { if (G.rediscover) G.rediscover() } catch (e) {}
|
|
593
|
+
try { if (win.__zgpRefresh) win.__zgpRefresh() } catch (e) {}
|
|
594
|
+
render()
|
|
595
|
+
setTimeout(function () {
|
|
596
|
+
try { if (win.__ZNOTES_REFRESH) win.__ZNOTES_REFRESH() } catch (e) {}
|
|
597
|
+
}, 600)
|
|
598
|
+
} else {
|
|
599
|
+
toast('加载失败:' + ((r && r.message) || '未知原因'), 'err')
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 「将本地仓库关联到远程」:弹窗填地址 → 校验通过才关窗并写入 remote
|
|
605
|
+
const linkBtn = $('s-vc-link-btn')
|
|
606
|
+
if (linkBtn) {
|
|
607
|
+
linkBtn.onclick = async function () {
|
|
608
|
+
let remoteBranches = null
|
|
609
|
+
const url = await zgsPrompt(
|
|
610
|
+
'将本地仓库关联到远程\n\n输入远端仓库地址。点「检测并继续」会先确认仓库可访问,通过后才写入配置。',
|
|
611
|
+
'https://gitee.com/账号/仓库.git', '',
|
|
612
|
+
async function (v) {
|
|
613
|
+
const chk = await Promise.resolve(
|
|
614
|
+
G && typeof G.checkRemote === 'function' ? G.checkRemote(v) : { ok: true })
|
|
615
|
+
if (!chk || !chk.ok) {
|
|
616
|
+
return { ok: false, message: '仓库地址不可用:' + ((chk && chk.message) || '无法连接') }
|
|
617
|
+
}
|
|
618
|
+
remoteBranches = (chk.branches || [])
|
|
619
|
+
return { ok: true }
|
|
620
|
+
})
|
|
621
|
+
if (!url) return
|
|
622
|
+
let r = null
|
|
623
|
+
try { r = await Promise.resolve(G.setRemote(url)) } catch (e) { r = { ok: false, message: (e && e.message) || '' } }
|
|
624
|
+
if (!r || !r.ok) {
|
|
625
|
+
toast('关联失败:' + ((r && r.message) || '未知原因'), 'err')
|
|
626
|
+
return
|
|
627
|
+
}
|
|
628
|
+
try { if (win.__zgpRefresh) win.__zgpRefresh() } catch (e) {}
|
|
629
|
+
// 远端是空仓库:光写 remote 等于什么也没同步,顺手把本地历史推上去,
|
|
630
|
+
// 否则用户会以为"关联成功"但远端正本还是空的。
|
|
631
|
+
if (remoteBranches && remoteBranches.length === 0) {
|
|
632
|
+
toast('远端是空仓库,正在推送本地历史…', 'info')
|
|
633
|
+
let pr = null
|
|
634
|
+
try { pr = await Promise.resolve(G.push()) } catch (e) { pr = { ok: false, message: (e && e.message) || '' } }
|
|
635
|
+
if (pr && pr.ok) toast('已关联到远程并推送完成', 'ok')
|
|
636
|
+
else toast('已关联,但推送失败:' + ((pr && pr.message) || '未知原因'), 'err')
|
|
637
|
+
} else {
|
|
638
|
+
toast('已关联到远程', 'ok')
|
|
639
|
+
}
|
|
640
|
+
render()
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const saveRemote = async function (url, btn) {
|
|
645
|
+
if (!G || typeof G.setRemote !== 'function') { toast('git 服务不可用', 'err'); return }
|
|
646
|
+
let old = ''
|
|
647
|
+
try {
|
|
648
|
+
const ri = await Promise.resolve(G.remoteInfo())
|
|
649
|
+
if (ri && ri.ok && ri.data && ri.data.hasRemote) old = ri.data.url
|
|
650
|
+
} catch (e) {}
|
|
651
|
+
if (url && old && url !== old) {
|
|
652
|
+
const ok = await zgsConfirm('把远端地址\n\n' + old + '\n\n改为\n\n' + url + '\n\n之后推送会进入新仓库。', '修改')
|
|
653
|
+
if (!ok) return
|
|
654
|
+
}
|
|
655
|
+
if (!url) {
|
|
656
|
+
const ok = await zgsConfirm('移除远端后,笔记只在本机提交,不再上传。', '移除')
|
|
657
|
+
if (!ok) return
|
|
658
|
+
}
|
|
659
|
+
if (btn) btn.disabled = true
|
|
660
|
+
let r = null
|
|
661
|
+
try { r = await G.setRemote(url) } catch (e) { r = { ok: false, message: (e && e.message) || '' } }
|
|
662
|
+
if (btn) btn.disabled = false
|
|
663
|
+
if (r && r.ok) {
|
|
664
|
+
toast(url ? '远程地址已保存' : '已移除远端', 'ok')
|
|
665
|
+
// 状态卡要跟着变(已绑定 ↔ 未绑定),否则界面上还是旧地址
|
|
666
|
+
render()
|
|
667
|
+
refreshVc().catch(function () {}) // 局部刷新,避免整块重渲染冲掉其它输入
|
|
668
|
+
} else {
|
|
669
|
+
toast('操作失败:' + ((r && r.message) || '未知原因'), 'err')
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const remoteClearBtn = $('s-vc-remote-clear')
|
|
674
|
+
if (remoteClearBtn) {
|
|
675
|
+
remoteClearBtn.onclick = function () { saveRemote('', remoteClearBtn) }
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const idSaveBtn = $('s-vc-id-save')
|
|
679
|
+
if (idSaveBtn) {
|
|
680
|
+
idSaveBtn.onclick = async function () {
|
|
681
|
+
const n = (($('s-vc-name2') || {}).value || '').trim()
|
|
682
|
+
const e = (($('s-vc-email2') || {}).value || '').trim()
|
|
683
|
+
idSaveBtn.disabled = true
|
|
684
|
+
let r = null
|
|
685
|
+
try { r = await G.setIdentity(n, e) } catch (err) { r = { ok: false, message: (err && err.message) || '' } }
|
|
686
|
+
idSaveBtn.disabled = false
|
|
687
|
+
if (r && r.ok) toast('身份已保存', 'ok')
|
|
688
|
+
else toast('保存失败:' + ((r && r.message) || '未知原因'), 'err')
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const save = function () {
|
|
693
|
+
G.setConfig({
|
|
694
|
+
autoSync: $('s-auto').checked,
|
|
695
|
+
autoMode: $('s-mode') ? $('s-mode').value : 'interval',
|
|
696
|
+
intervalMs: parseInt($('s-interval').value, 10),
|
|
697
|
+
onStopEditMinutes: parseInt($('s-stop').value, 10),
|
|
698
|
+
commitMessage: $('s-msg').value
|
|
699
|
+
})
|
|
700
|
+
toast('设置已保存', 'ok')
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// 自定义提交信息:开关控制抽屉展开,并决定用默认模板还是自定义模板
|
|
704
|
+
const DEFAULT_MSG = 'notes: 同步 {{datetime}}'
|
|
705
|
+
const msgCustom = $('s-msg-custom')
|
|
706
|
+
const msgDrawer = $('s-msg-drawer')
|
|
707
|
+
const msgInput = $('s-msg')
|
|
708
|
+
if (msgCustom && msgDrawer) {
|
|
709
|
+
const isCustom = !!(cfg.commitMessage && cfg.commitMessage !== DEFAULT_MSG)
|
|
710
|
+
msgCustom.checked = isCustom
|
|
711
|
+
msgDrawer.classList.toggle('open', isCustom)
|
|
712
|
+
if (!isCustom && msgInput && !msgInput.value) msgInput.value = DEFAULT_MSG
|
|
713
|
+
msgCustom.onchange = function () {
|
|
714
|
+
const on = msgCustom.checked
|
|
715
|
+
msgDrawer.classList.toggle('open', on)
|
|
716
|
+
if (!on && msgInput) msgInput.value = DEFAULT_MSG
|
|
717
|
+
save()
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const autoSw = $('s-auto')
|
|
722
|
+
if (autoSw) {
|
|
723
|
+
autoSw.onchange = function () {
|
|
724
|
+
const dr = $('s-auto-drawer')
|
|
725
|
+
if (dr) dr.classList.toggle('open', autoSw.checked)
|
|
726
|
+
save()
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const modeSel = $('s-mode')
|
|
730
|
+
if (modeSel) {
|
|
731
|
+
modeSel.onchange = function () {
|
|
732
|
+
const isIv = modeSel.value === 'interval'
|
|
733
|
+
const ivRow = $('s-interval-row')
|
|
734
|
+
const stRow = $('s-stop-row')
|
|
735
|
+
if (ivRow) ivRow.style.display = isIv ? '' : 'none'
|
|
736
|
+
if (stRow) stRow.style.display = isIv ? 'none' : ''
|
|
737
|
+
save()
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
;['s-interval', 's-stop'].forEach(function (id) {
|
|
741
|
+
const el = $(id)
|
|
742
|
+
if (el) el.onchange = save
|
|
743
|
+
})
|
|
744
|
+
if ($('s-msg')) $('s-msg').onblur = save
|
|
745
|
+
|
|
746
|
+
// 仓库 / 状态信息(异步)
|
|
747
|
+
let st = {}
|
|
748
|
+
try {
|
|
749
|
+
const r = await G.state()
|
|
750
|
+
if (r && r.ok && r.data) st = r.data
|
|
751
|
+
} catch (e) {}
|
|
752
|
+
const repoEl = $('s-repo')
|
|
753
|
+
if (repoEl) {
|
|
754
|
+
// 状态也用键值对齐,与上方版本控制保持一致
|
|
755
|
+
let rows = ''
|
|
756
|
+
if (G.scopeRel) rows += '<dt>笔记子目录</dt><dd class="mono">' + esc(G.scopeRel) + '</dd>'
|
|
757
|
+
if (st.branch) rows += '<dt>当前分支</dt><dd class="mono">' + esc(st.branch) + '</dd>'
|
|
758
|
+
if (typeof st.dirty === 'number') rows += '<dt>未提交改动</dt><dd>' + st.dirty + ' 个文件</dd>'
|
|
759
|
+
repoEl.innerHTML = '<dl class="zgs-kv">' + rows + '</dl>'
|
|
760
|
+
}
|
|
761
|
+
const c2 = (G.config() || {}).data || {}
|
|
762
|
+
let irows = '<dt>上次同步</dt><dd>' + (c2.lastRunAt ? new Date(c2.lastRunAt).toLocaleString('zh-CN') : '从未执行') + '</dd>'
|
|
763
|
+
if (c2.lastAutoResult) {
|
|
764
|
+
const ar = c2.lastAutoResult
|
|
765
|
+
irows += '<dt>最近结果</dt><dd>' + (ar.ok ? '成功' : '失败') + (ar.message ? ' · ' + esc(ar.message) : '') + '</dd>'
|
|
766
|
+
if (ar.error) irows += '<dt>错误</dt><dd>' + esc(ar.error) + '</dd>'
|
|
767
|
+
if (ar.at) irows += '<dt>发生时间</dt><dd>' + new Date(ar.at).toLocaleString('zh-CN') + '</dd>'
|
|
768
|
+
}
|
|
769
|
+
const infoEl = $('s-info')
|
|
770
|
+
if (infoEl) infoEl.innerHTML = '<dl class="zgs-kv">' + irows + '</dl>'
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// ---------- 开关 ----------
|
|
774
|
+
function open () {
|
|
775
|
+
if (root.classList.contains('open')) return
|
|
776
|
+
root.classList.add('open')
|
|
777
|
+
render()
|
|
778
|
+
}
|
|
779
|
+
function close () {
|
|
780
|
+
root.classList.remove('open')
|
|
781
|
+
}
|
|
782
|
+
function toggle () {
|
|
783
|
+
if (root.classList.contains('open')) close()
|
|
784
|
+
else open()
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
$('zgs-close').onclick = close
|
|
788
|
+
doc.addEventListener('keydown', function (e) {
|
|
789
|
+
if (e.key === 'Escape' && root.classList.contains('open')) close()
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
// 点击设置面板以外的地方(编辑器 / 空状态)自动收起,和 Git 面板行为一致
|
|
793
|
+
function maybeHide (target) {
|
|
794
|
+
if (!root.classList.contains('open')) return
|
|
795
|
+
const el = target && target.nodeType === 1 ? target : (target && target.parentElement) || null
|
|
796
|
+
if (!el) return
|
|
797
|
+
if (el.closest('#zgs-root') || el.closest('#zgp-root') || el.closest('#zgp-view') || el.closest('#znotes-sbtn')) return
|
|
798
|
+
if (el.closest('.editor-body') || el.closest('[contenteditable="true"]') || el.closest('.worknote-empty')) close()
|
|
799
|
+
}
|
|
800
|
+
doc.addEventListener('mousedown', function (e) { maybeHide(e.target) }, true)
|
|
801
|
+
|
|
802
|
+
// ---------- 侧边栏底部按钮 ----------
|
|
803
|
+
function mountSidebarButton () {
|
|
804
|
+
const body = doc.querySelector('.sidebar-body')
|
|
805
|
+
if (!body) return false
|
|
806
|
+
if (doc.getElementById('znotes-sbtn-wrap')) return true
|
|
807
|
+
const wrap = doc.createElement('div')
|
|
808
|
+
wrap.id = 'znotes-sbtn-wrap'
|
|
809
|
+
wrap.className = 'znotes-footer'
|
|
810
|
+
wrap.innerHTML = '<button id="znotes-sbtn" class="znotes-sbtn" title="设置:笔记目录、自动同步、提交选项">' +
|
|
811
|
+
ICON.gear + '<span>设置</span></button>'
|
|
812
|
+
body.appendChild(wrap)
|
|
813
|
+
const btn = wrap.querySelector('#znotes-sbtn')
|
|
814
|
+
btn.onclick = function (ev) {
|
|
815
|
+
ev.stopPropagation()
|
|
816
|
+
toggle()
|
|
817
|
+
}
|
|
818
|
+
// 让树所在的那个(无 class 的)容器成为可收缩的 flex 项,
|
|
819
|
+
// 否则树的高度会把侧边栏撑破、盖住底部按钮栏
|
|
820
|
+
const treeEl = doc.querySelector('.sidebar-tree')
|
|
821
|
+
if (treeEl && treeEl.parentElement && treeEl.parentElement !== body) {
|
|
822
|
+
const holder = treeEl.parentElement
|
|
823
|
+
holder.style.flex = '1 1 auto'
|
|
824
|
+
holder.style.minHeight = '0'
|
|
825
|
+
holder.style.overflow = 'hidden'
|
|
826
|
+
treeEl.style.height = '100%'
|
|
827
|
+
treeEl.style.boxSizing = 'border-box'
|
|
828
|
+
}
|
|
829
|
+
return true
|
|
830
|
+
}
|
|
831
|
+
// 侧边栏可能是异步渲染的,重试几次
|
|
832
|
+
if (!mountSidebarButton()) {
|
|
833
|
+
let tries = 0
|
|
834
|
+
const t = setInterval(function () {
|
|
835
|
+
tries++
|
|
836
|
+
if (mountSidebarButton() || tries > 40) clearInterval(t)
|
|
837
|
+
}, 500)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// 暴露给 bundle(Git 面板标题栏不需要,留接口给后续)
|
|
841
|
+
win.__zgsToggle = toggle
|
|
842
|
+
win.__zgsOpen = open
|
|
843
|
+
win.__zgsClose = close
|
|
844
|
+
win.__zgsIsOpen = function () { return root.classList.contains('open') }
|
|
845
|
+
win.__zgsRender = render
|
|
846
|
+
logBoot('done, btn=' + !!doc.getElementById('znotes-sbtn'))
|
|
847
|
+
}
|