@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.
Files changed (197) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +185 -2
  4. package/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  5. package/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  6. package/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  7. package/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  8. package/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  9. package/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  10. package/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  11. package/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  12. package/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  13. package/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  14. package/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  15. package/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  16. package/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  17. package/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  18. package/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  19. package/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  20. package/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  21. package/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  22. package/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  23. package/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  24. package/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  25. package/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  26. package/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  27. package/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  28. package/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  29. package/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  30. package/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  31. package/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  32. package/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  33. package/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  34. package/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  35. package/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  36. package/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  37. package/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  38. package/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  39. package/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  40. package/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  41. package/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  42. package/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  43. package/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  44. package/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  45. package/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  46. package/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  47. package/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  48. package/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  49. package/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  50. package/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  51. package/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  52. package/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  53. package/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  54. package/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  55. package/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  56. package/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  57. package/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  58. package/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  59. package/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  60. package/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  61. package/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  62. package/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  63. package/assets/abnfDiagram-WEYOIA3C-BgXsTF7a.js +1 -0
  64. package/assets/architectureDiagram-ZHVYKT53-BsCWa_qC.js +36 -0
  65. package/assets/blockDiagram-RJAE7QA3-wbIxEiYN.js +140 -0
  66. package/assets/c4Diagram-XDPPPE54-5zdyxFvA.js +38 -0
  67. package/assets/chunk-2E7WHVMD-F85Lca-i.js +88 -0
  68. package/assets/chunk-2Q5K7J3B-CjpAZ0OI.js +1 -0
  69. package/assets/chunk-4EMNKR2T-C8Kbh83c.js +1 -0
  70. package/assets/chunk-55XCU76K-BINcFiLN.js +1 -0
  71. package/assets/chunk-5VM5RSS4-Diy5h2LS.js +15 -0
  72. package/assets/chunk-JWPE2WC7-BXnjDdh1.js +1 -0
  73. package/assets/chunk-VQ5W62QI-BM7ZZo1I.js +1 -0
  74. package/assets/chunk-XXDRQBXY-CW8CjB1w.js +1 -0
  75. package/assets/chunk-ZCHMQSOR-C7ATz1L1.js +2 -0
  76. package/assets/classDiagram-v2-4MYWR3UA-CMoqNk-b.js +217 -0
  77. package/assets/cose-bilkent-JH36ORCC-6rqR5iJk.js +1 -0
  78. package/assets/cynefin-EF2NZ3EQ-DypLIJxl.js +141 -0
  79. package/assets/cynefinDiagram-WF24UDRP-8b2CEBlR.js +62 -0
  80. package/assets/cytoscape.esm-CjX6Hyah.js +331 -0
  81. package/assets/dagre-NK4GIZVX-Dv6vuUbR.js +4 -0
  82. package/assets/diagram-CBK3MWTC-DJgMDeYd.js +24 -0
  83. package/assets/diagram-I5XIIV2D-BB9cMoRW.js +24 -0
  84. package/assets/diagram-ODIL2UCW-BVcRtsdy.js +30 -0
  85. package/assets/diagram-PFBEJUXE-BbGSOtJx.js +3 -0
  86. package/assets/diagram-RJA3I4VV-CzkY_8Fo.js +41 -0
  87. package/assets/diagram-TYUNFSNR-YTl5QdfL.js +200 -0
  88. package/assets/ebnfDiagram-V4P3TTPT-COVjPAjE.js +1 -0
  89. package/assets/echarts-BAPDkhe3.js +61 -0
  90. package/assets/elk-QONTBCTQ-DHsjY8Z_.js +27 -0
  91. package/assets/erDiagram-KYQIGQAA-CUAbTUl1.js +99 -0
  92. package/assets/flowDiagram-IUGLLWBU-CWsiqTQC.js +213 -0
  93. package/assets/ganttDiagram-R7GPNTJY-DetBW0Wm.js +292 -0
  94. package/assets/gitGraphDiagram-PDYL7XHY-ByrdgJmw.js +106 -0
  95. package/assets/graph-D8UmR1vK.js +1 -0
  96. package/assets/index-BLONSItV.css +1 -0
  97. package/assets/index-Cz6qaZbV.js +744 -0
  98. package/assets/infoDiagram-MVDRKAX5-BM48fjhU.js +2 -0
  99. package/assets/ishikawaDiagram-HPEHV2GE-DtHYywHZ.js +70 -0
  100. package/assets/journeyDiagram-BOSHW7O7-DJ1LiRh3.js +139 -0
  101. package/assets/kanban-definition-FNSA4HC5-DPPt8pwJ.js +89 -0
  102. package/assets/katex-CQL9J12G.js +257 -0
  103. package/assets/markdown-DVehI1Sg.js +314 -0
  104. package/assets/mermaid-C_q9pug0.js +312 -0
  105. package/assets/mindmap-definition-6OY57UG3-zbqYOEY6.js +98 -0
  106. package/assets/pegDiagram-ECIUJNBX-e7QG7TMq.js +1 -0
  107. package/assets/pieDiagram-GNMPY74T-Duf6YP0N.js +39 -0
  108. package/assets/quadrantDiagram-D57K4MC3-B2eYoUUy.js +7 -0
  109. package/assets/railroadDiagram-Y72NRBL7-BWCzPcI_.js +1 -0
  110. package/assets/requirementDiagram-EQYSW5RA-HhPMEqB1.js +84 -0
  111. package/assets/sankeyDiagram-SY24JHO5-CMUTwFBB.js +40 -0
  112. package/assets/sequenceDiagram-BLEHNHQU-C9wR2ll7.js +169 -0
  113. package/assets/sizeCapture-INFHLROL-DXfz37S6.js +1 -0
  114. package/assets/stateDiagram-v2-A6IESKWZ-D_gtonhE.js +291 -0
  115. package/assets/swimlanes-B3ENRKFQ-DdJOuuMV.js +1 -0
  116. package/assets/swimlanesDiagram-AYQRV4AK-BWt3HxHV.js +8 -0
  117. package/assets/timeline-definition-JXYKGLGB-DQsdwJe-.js +120 -0
  118. package/assets/usecaseDiagram-WPOLBNNX-DTRyFq7d.js +370 -0
  119. package/assets/vennDiagram-Q3Y3IDCW-dihYszm0.js +34 -0
  120. package/assets/wardleyDiagram-IVAIDKWA-BoPpgYfK.js +78 -0
  121. package/assets/xychartDiagram-4URNHDU4-DoLjYxeB.js +7 -0
  122. package/export-file/github-markdown.css +550 -0
  123. package/export-file/index.html +33 -0
  124. package/fonts/KaTeX_AMS-Regular.ttf +0 -0
  125. package/fonts/KaTeX_AMS-Regular.woff +0 -0
  126. package/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  127. package/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  128. package/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  129. package/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  130. package/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  131. package/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  132. package/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  133. package/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  134. package/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  135. package/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  136. package/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  137. package/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  138. package/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  139. package/fonts/KaTeX_Main-Bold.ttf +0 -0
  140. package/fonts/KaTeX_Main-Bold.woff +0 -0
  141. package/fonts/KaTeX_Main-Bold.woff2 +0 -0
  142. package/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  143. package/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  144. package/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  145. package/fonts/KaTeX_Main-Italic.ttf +0 -0
  146. package/fonts/KaTeX_Main-Italic.woff +0 -0
  147. package/fonts/KaTeX_Main-Italic.woff2 +0 -0
  148. package/fonts/KaTeX_Main-Regular.ttf +0 -0
  149. package/fonts/KaTeX_Main-Regular.woff +0 -0
  150. package/fonts/KaTeX_Main-Regular.woff2 +0 -0
  151. package/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  152. package/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  153. package/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  154. package/fonts/KaTeX_Math-Italic.ttf +0 -0
  155. package/fonts/KaTeX_Math-Italic.woff +0 -0
  156. package/fonts/KaTeX_Math-Italic.woff2 +0 -0
  157. package/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  158. package/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  159. package/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  160. package/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  161. package/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  162. package/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  163. package/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  164. package/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  165. package/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  166. package/fonts/KaTeX_Script-Regular.ttf +0 -0
  167. package/fonts/KaTeX_Script-Regular.woff +0 -0
  168. package/fonts/KaTeX_Script-Regular.woff2 +0 -0
  169. package/fonts/KaTeX_Size1-Regular.ttf +0 -0
  170. package/fonts/KaTeX_Size1-Regular.woff +0 -0
  171. package/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  172. package/fonts/KaTeX_Size2-Regular.ttf +0 -0
  173. package/fonts/KaTeX_Size2-Regular.woff +0 -0
  174. package/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  175. package/fonts/KaTeX_Size3-Regular.ttf +0 -0
  176. package/fonts/KaTeX_Size3-Regular.woff +0 -0
  177. package/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  178. package/fonts/KaTeX_Size4-Regular.ttf +0 -0
  179. package/fonts/KaTeX_Size4-Regular.woff +0 -0
  180. package/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  181. package/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  182. package/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  183. package/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  184. package/index.html +15 -0
  185. package/katex.min.css +1 -0
  186. package/logo.png +0 -0
  187. package/package.json +31 -4
  188. package/plugin.json +241 -0
  189. package/preload/branch-bar.js +159 -0
  190. package/preload/git-panel.js +1311 -0
  191. package/preload/git-service.js +1403 -0
  192. package/preload/ndb.js +1407 -0
  193. package/preload/package.json +3 -0
  194. package/preload/rename-note.js +151 -0
  195. package/preload/services.js +432 -0
  196. package/preload/settings-panel.js +847 -0
  197. package/preload.js +1615 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,151 @@
1
+ // 笔记重命名弹层:目录树右键「重命名」调用
2
+ // 改标题后走 __NDB.put,put 内部会把磁盘文件同步改名
3
+ module.exports = function (win, findInstance) {
4
+ const doc = win.document
5
+ const ID = 'znotes-rename'
6
+ const STYLE_ID = ID + '-style'
7
+ const LOG = function () {} // 调试打点已关闭
8
+
9
+ function injectStyle() {
10
+ if (doc.getElementById(STYLE_ID)) return
11
+ const st = doc.createElement('style')
12
+ st.id = STYLE_ID
13
+ st.textContent = `
14
+ #${ID}{position:fixed;inset:0;z-index:2147483200;display:none;align-items:center;justify-content:center;
15
+ background:rgba(0,0,0,.28);}
16
+ #${ID}.open{display:flex;}
17
+ #${ID} .zrn-card{width:340px;max-width:86vw;border-radius:10px;background:#fff;color:#1f2328;
18
+ box-shadow:0 16px 40px rgba(0,0,0,.28);padding:16px 18px 14px;font-size:13px;}
19
+ #${ID} .zrn-title{font-weight:600;font-size:13.5px;margin-bottom:10px;}
20
+ #${ID} input{width:100%;box-sizing:border-box;padding:8px 10px;border-radius:6px;
21
+ border:1px solid rgba(128,128,128,.42);background:transparent;color:inherit;font-size:13px;outline:none;}
22
+ #${ID} input:focus{border-color:#3f7ef7;}
23
+ #${ID} .zrn-btns{display:flex;justify-content:flex-end;gap:8px;margin-top:14px;}
24
+ #${ID} button{padding:6px 14px;border-radius:6px;border:1px solid rgba(128,128,128,.36);
25
+ background:transparent;color:inherit;font-size:12.5px;cursor:pointer;}
26
+ #${ID} button.primary{background:#3f7ef7;border-color:#3f7ef7;color:#fff;}
27
+ @media (prefers-color-scheme: dark){
28
+ #${ID} .zrn-card{background:#2b2f36;color:#e6e6e6;box-shadow:0 16px 40px rgba(0,0,0,.5);}
29
+ #${ID} input{border-color:rgba(255,255,255,.22);}
30
+ #${ID} button{border-color:rgba(255,255,255,.2);}
31
+ }
32
+ `
33
+ try { (doc.head || doc.body || doc.documentElement).appendChild(st) } catch (e) {}
34
+ }
35
+
36
+ let root = null
37
+ let onSubmit = null
38
+ let promptTitle = '重命名笔记'
39
+
40
+ function ensure() {
41
+ injectStyle()
42
+ if (root && doc.body.contains(root)) return root
43
+ root = doc.createElement('div')
44
+ root.id = ID
45
+ root.innerHTML =
46
+ '<div class="zrn-card">' +
47
+ '<div class="zrn-title"></div>' +
48
+ '<input type="text" maxlength="120" spellcheck="false">' +
49
+ '<div class="zrn-btns">' +
50
+ '<button data-act="cancel">取消</button>' +
51
+ '<button data-act="ok" class="primary">确定</button>' +
52
+ '</div></div>'
53
+ const input = root.querySelector('input')
54
+ root.addEventListener('mousedown', function (e) {
55
+ if (e.target === root) hide()
56
+ e.stopPropagation()
57
+ })
58
+ root.addEventListener('click', function (e) {
59
+ const b = e.target.closest && e.target.closest('button[data-act]')
60
+ LOG('click on rename root: ' + (b ? b.getAttribute('data-act') : 'non-button'))
61
+ if (!b) return
62
+ if (b.getAttribute('data-act') === 'ok') submit()
63
+ else hide()
64
+ })
65
+ root.addEventListener('keydown', function (e) {
66
+ if (e.key === 'Enter') { e.preventDefault(); submit() }
67
+ else if (e.key === 'Escape') { e.preventDefault(); hide() }
68
+ e.stopPropagation()
69
+ })
70
+ doc.body.appendChild(root)
71
+ return root
72
+ }
73
+
74
+ function hide() {
75
+ LOG('hide, was=' + (root ? root.classList.contains('open') : 'no-root'))
76
+ if (root) root.classList.remove('open')
77
+ onSubmit = null
78
+ }
79
+
80
+ function submit() {
81
+ LOG('submit called, hasCb=' + !!onSubmit)
82
+ const input = root && root.querySelector('input')
83
+ const val = input ? String(input.value || '').trim() : ''
84
+ const cur = onSubmit
85
+ hide()
86
+ if (!val || !cur) return
87
+ cur(val)
88
+ }
89
+
90
+ function openNote(node, fallbackDoc) {
91
+ const el = ensure()
92
+ let id = node && node.data && node.data.id
93
+ let d = id ? (win.NOTES_DIC || {})[id] : null
94
+ if (!d && fallbackDoc) { d = fallbackDoc; id = fallbackDoc._id }
95
+ if (!d) return
96
+ const old = String(d.title || '')
97
+ promptTitle = '重命名笔记'
98
+ el.querySelector('.zrn-title').textContent = promptTitle
99
+ const input = el.querySelector('input')
100
+ input.value = old
101
+ el.classList.add('open')
102
+ setTimeout(function () { try { input.focus(); input.select() } catch (e) {} }, 20)
103
+ onSubmit = function (next) {
104
+ if (next === old) return
105
+ const doc2 = {}
106
+ for (const k in d) if (Object.prototype.hasOwnProperty.call(d, k)) doc2[k] = d[k]
107
+ doc2.title = next
108
+ let res = null
109
+ try { res = win.__NDB.put(doc2) } catch (e) { res = { error: true, message: String(e && e.message) } }
110
+ if (res && res.error) {
111
+ try { win.ztools.showNotification('重命名失败:' + (res.message || '')) } catch (e) {}
112
+ return
113
+ }
114
+ // put 可能把 _id 改成新的物理路径,同步 NOTES_DIC 的键
115
+ const newId = (doc2 && doc2._id) || id
116
+ if (newId !== id) { try { delete win.NOTES_DIC[id] } catch (e) {} }
117
+ doc2._id = newId
118
+ win.NOTES_DIC[newId] = doc2
119
+ // 当前打开的就是这篇:立刻替换 workNote,面板顶部文件名即时更新
120
+ try {
121
+ const inst = findInstance('main')
122
+ if (inst && inst.state && inst.state.workNote && inst.state.workNote._id === id) {
123
+ inst.setState({ workNote: doc2 })
124
+ }
125
+ } catch (e) {}
126
+ try { if (win.__znotesRefreshTree) win.__znotesRefreshTree() } catch (e) {}
127
+ }
128
+ }
129
+
130
+ // 通用输入弹层:title=标题,value=默认值,cb(输入值)
131
+ win.__znotesPrompt = function (title, value, cb) {
132
+ const el = ensure()
133
+ promptTitle = title || ''
134
+ el.querySelector('.zrn-title').textContent = promptTitle
135
+ el.querySelector('input').value = value == null ? '' : String(value)
136
+ el.classList.add('open')
137
+ LOG('open title=' + promptTitle)
138
+ setTimeout(function () { try { const i = el.querySelector('input'); i.focus(); i.select() } catch (e) {} }, 20)
139
+ onSubmit = cb
140
+ }
141
+
142
+ win.__znotesRenameNote = function (node) {
143
+ let inst = null
144
+ try { inst = findInstance('main') } catch (e) {}
145
+ const sb = inst && inst.sidebarRef
146
+ const n = node || (sb && sb.state && sb.state.contextMenu && sb.state.contextMenu.node)
147
+ const fb = inst && inst.state && inst.state.workNote
148
+ openNote(n, fb)
149
+ }
150
+ win.__znotesRenameOpen = openNote
151
+ }
@@ -0,0 +1,432 @@
1
+ /**
2
+ * services.js —— 导出、附件、剪贴板、文件杂项
3
+ *
4
+ * V1.1.0 新写。行为对齐 V1.0.0(用户已习惯的交互),实现全部按功能规格重写:
5
+ * - 导出 md:内容 + 附件一起落到目标目录
6
+ * - 导出 html:包一层带样式的文档,公式用 KaTeX 的 CSS
7
+ * - 导出 pdf / 图片:开一个隐藏窗口渲染 HTML 再抓图/打印
8
+ * - 图片附件:按内容 md5 命名,同一张图多处引用只存一份
9
+ */
10
+ const fs = require('fs')
11
+ const path = require('path')
12
+ const os = require('os')
13
+ const crypto = require('crypto')
14
+
15
+ const IMAGE_EXT = {
16
+ 'image/png': '.png',
17
+ 'image/jpeg': '.jpg',
18
+ 'image/jpg': '.jpg',
19
+ 'image/gif': '.gif',
20
+ 'image/webp': '.webp',
21
+ 'image/bmp': '.bmp',
22
+ 'image/svg+xml': '.svg',
23
+ 'image/x-icon': '.ico',
24
+ 'image/vnd.microsoft.icon': '.ico',
25
+ 'image/tiff': '.tiff',
26
+ }
27
+ const EXT_IS_IMAGE = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg', '.ico', '.tiff']
28
+ const MAX_IMAGE_BYTES = 10 * 1024 * 1024
29
+
30
+ function md5(buf) {
31
+ return crypto.createHash('md5').update(buf).digest('hex')
32
+ }
33
+
34
+ function safeName(name, fallback) {
35
+ const cleaned = String(name || '')
36
+ .replace(/[\\/:*?"<>|]/g, '')
37
+ .trim()
38
+ return cleaned || String(fallback || Date.now())
39
+ }
40
+
41
+ function ensureDir(dir) {
42
+ fs.mkdirSync(dir, { recursive: true })
43
+ return dir
44
+ }
45
+
46
+ function attachmentCacheDir(win) {
47
+ const base = path.join(win.ztools.getPath('temp') || os.tmpdir(), 'cmark.attachments')
48
+ return ensureDir(base)
49
+ }
50
+
51
+ module.exports = function installServices(win) {
52
+ const svc = {
53
+ /* ---------------- 首次使用示例 ---------------- */
54
+ getFirstMDFileContent() {
55
+ return [
56
+ '# 欢迎使用 CMark',
57
+ '',
58
+ '这是一个基于 ZTools 的本地 Markdown 笔记插件。',
59
+ '',
60
+ '- 笔记就是磁盘上的 `.md` 文件,用你习惯的编辑器也能打开',
61
+ '- 支持 git:提交、推送、拉取,改动能同步到远端仓库',
62
+ '- 支持表格、代码块、公式、流程图、图表',
63
+ '',
64
+ '在左侧右键新建笔记,或把已有的 `.md` 拖进目录。',
65
+ '',
66
+ ].join('\n')
67
+ },
68
+
69
+ /* ---------------- 剪贴板 ---------------- */
70
+ readClipboardData() {
71
+ try {
72
+ const { clipboard } = require('electron')
73
+ return { text: clipboard.readText(), html: clipboard.readHTML() }
74
+ } catch (e) {
75
+ return { text: '', html: '' }
76
+ }
77
+ },
78
+
79
+ readClipboardHtml() {
80
+ try {
81
+ return require('electron').clipboard.readHTML()
82
+ } catch (e) {
83
+ return ''
84
+ }
85
+ },
86
+
87
+ writeClipboardData(data) {
88
+ try {
89
+ if (!data) return
90
+ const clip = require('electron').clipboard
91
+ if (data.text != null) clip.writeText(String(data.text))
92
+ if (data.html) clip.writeHTML(String(data.html))
93
+ } catch (e) {}
94
+ },
95
+
96
+ copyHtml(html) {
97
+ try {
98
+ require('electron').clipboard.writeHTML(String(html || ''))
99
+ } catch (e) {}
100
+ },
101
+
102
+ /* ---------------- 目录 / 文件 ---------------- */
103
+ makeFolder(dir, name) {
104
+ const p = path.join(dir, safeName(name, '新建文件夹'))
105
+ ensureDir(p)
106
+ return p
107
+ },
108
+
109
+ getFileBaseName(p) {
110
+ return path.basename(p || '')
111
+ },
112
+
113
+ getFileContent(p) {
114
+ try {
115
+ return fs.readFileSync(p, 'utf-8')
116
+ } catch (e) {
117
+ return ''
118
+ }
119
+ },
120
+
121
+ fileUrlToPath(u) {
122
+ try {
123
+ return require('url').fileURLToPath(u)
124
+ } catch (e) {
125
+ return u
126
+ }
127
+ },
128
+
129
+ shellOpenPath(p) {
130
+ setTimeout(() => {
131
+ try {
132
+ if (!fs.existsSync(p)) {
133
+ win.ztools.showNotification('路径不存在:' + p)
134
+ return
135
+ }
136
+ win.ztools.shellOpenPath(p)
137
+ } catch (e) {}
138
+ })
139
+ },
140
+
141
+ /** 把 md 里的相对图片路径解析成绝对路径(存在才返回) */
142
+ convertMdFileImageURLToAbsolutePath(mdPath, rel) {
143
+ const abs = path.join(path.dirname(mdPath), rel)
144
+ return fs.existsSync(abs) ? abs : null
145
+ },
146
+
147
+ /* ---------------- 图片 ---------------- */
148
+ /**
149
+ * 解析一个图片来源,返回 {digest, name, data(Buffer), contentType}
150
+ * 支持:data:base64、本地文件路径
151
+ */
152
+ getImageData(src) {
153
+ if (!src) throw new Error('图片为空')
154
+ const dataUrl = /^data:(image\/[a-z.+-]+);base64,/i.exec(src)
155
+ if (dataUrl) {
156
+ const buf = Buffer.from(src.slice(dataUrl[0].length), 'base64')
157
+ const type = dataUrl[1].toLowerCase()
158
+ if (buf.length > MAX_IMAGE_BYTES) throw new Error('图片大小超过 10 M')
159
+ return { digest: md5(buf), name: 'pasted-image', data: buf, contentType: type }
160
+ }
161
+
162
+ if (!fs.existsSync(src)) throw new Error('图片文件不存在')
163
+ const ext = path.extname(src).toLowerCase()
164
+ if (!EXT_IS_IMAGE.includes(ext)) throw new Error('不是支持的图片格式')
165
+ const st = fs.statSync(src)
166
+ if (st.size > MAX_IMAGE_BYTES) throw new Error('图片大小超过 10 M')
167
+ const buf = fs.readFileSync(src)
168
+ const type = 'image/' + ext.replace('.', '').replace('jpg', 'jpeg')
169
+ return { digest: md5(buf), name: path.basename(src), data: buf, contentType: type }
170
+ },
171
+
172
+ /** 从剪贴板取图(截图或复制的图片文件) */
173
+ getClipboardImage() {
174
+ // 优先:资源管理器里复制的图片文件
175
+ try {
176
+ const files = win.ztools.getCopyedFiles && win.ztools.getCopyedFiles()
177
+ if (files && files.length) {
178
+ if (files.length > 1) throw new Error('存在多个文件,请一次粘贴一个图片文件')
179
+ return svc.getImageData(files[0].path)
180
+ }
181
+ } catch (e) {
182
+ if (e && e.message) throw e
183
+ }
184
+
185
+ // 其次:剪贴板里的位图(截图)
186
+ try {
187
+ const { clipboard } = require('electron')
188
+ const formats = clipboard.availableFormats()
189
+ if (!formats || !formats.length) return null
190
+ const hasImage = formats.some((f) => f.startsWith('image/'))
191
+ if (!hasImage) return null
192
+ const img = clipboard.readImage()
193
+ if (!img || img.isEmpty()) return null
194
+ const buf = img.toPNG()
195
+ if (buf.length > MAX_IMAGE_BYTES) throw new Error('图片大小超过 10 M')
196
+ return { digest: md5(buf), name: '截图', data: buf, contentType: 'image/png' }
197
+ } catch (e) {
198
+ if (e && e.message) throw e
199
+ return null
200
+ }
201
+ },
202
+
203
+ /** 附件落盘到临时缓存,返回可给 <img src> 用的绝对路径 */
204
+ getAttachmentTempPath(digest) {
205
+ if (!digest) return null
206
+ const dir = attachmentCacheDir(win)
207
+ let ext = ''
208
+ try {
209
+ const meta = win.__NDB && win.__NDB.get && win.__NDB.get('attachment/' + digest)
210
+ if (meta && meta._attachments && meta._attachments[0]) {
211
+ ext = IMAGE_EXT[meta._attachments[0].content_type] || ''
212
+ }
213
+ } catch (e) {}
214
+
215
+ const target = path.join(dir, digest + ext)
216
+ if (fs.existsSync(target)) return target
217
+
218
+ try {
219
+ const buf = win.__NDB && win.__NDB.getAttachment && win.__NDB.getAttachment('attachment/' + digest)
220
+ if (!buf) return null
221
+ fs.writeFileSync(target, Buffer.isBuffer(buf) ? buf : Buffer.from(buf))
222
+ return target
223
+ } catch (e) {
224
+ return null
225
+ }
226
+ },
227
+
228
+ /** 把 data URL 或远端图片存成本地文件(用于渲染外链图片) */
229
+ async saveImgToFileBySrc(src) {
230
+ if (!src) return null
231
+ let buf = null
232
+ let ext = ''
233
+ const dataUrl = /^data:(image\/[a-z.+-]+);base64,/i.exec(src)
234
+ if (dataUrl) {
235
+ ext = IMAGE_EXT[dataUrl[1].toLowerCase()] || ''
236
+ if (!ext) return null
237
+ buf = Buffer.from(src.slice(dataUrl[0].length), 'base64')
238
+ } else if (/^https?:\/\//i.test(src)) {
239
+ try {
240
+ const res = await fetch(src)
241
+ const type = (res.headers.get('content-type') || '').split(';')[0].toLowerCase()
242
+ ext = IMAGE_EXT[type] || ''
243
+ if (!ext) return null
244
+ buf = Buffer.from(await res.arrayBuffer())
245
+ } catch (e) {
246
+ return null
247
+ }
248
+ } else {
249
+ return null
250
+ }
251
+ if (!buf) return null
252
+ const dir = attachmentCacheDir(win)
253
+ const target = path.join(dir, md5(buf) + ext)
254
+ if (!fs.existsSync(target)) {
255
+ try {
256
+ fs.writeFileSync(target, buf)
257
+ } catch (e) {
258
+ return null
259
+ }
260
+ }
261
+ return target
262
+ },
263
+
264
+ /* ---------------- 导出 ---------------- */
265
+ _exportDir: '',
266
+
267
+ /** 导出 Markdown:正文 + 附件一起复制 */
268
+ writeMarkdownFile(dir, name, content, attachmentPaths) {
269
+ const base = safeName(name, Date.now())
270
+ let targetDir = dir
271
+ if (!targetDir) {
272
+ targetDir = win.ztools.showSaveDialog({
273
+ defaultPath: path.join(win.ztools.getPath('downloads') || '', base),
274
+ })
275
+ if (!targetDir) return null
276
+ ensureDir(targetDir)
277
+ }
278
+ for (const a of attachmentPaths || []) {
279
+ try {
280
+ fs.copyFileSync(a, path.join(targetDir, path.basename(a)))
281
+ } catch (e) {}
282
+ }
283
+ const file = path.join(targetDir, base + '.md')
284
+ fs.writeFileSync(file, content, 'utf8')
285
+ return file
286
+ },
287
+
288
+ /** 导出 HTML:套一层文档壳,样式内联 */
289
+ writeHtmlFile(dir, name, html, attachmentPaths, withKatex) {
290
+ const base = safeName(name, Date.now())
291
+ const katexCss = withKatex
292
+ ? '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">'
293
+ : ''
294
+ const doc = [
295
+ '<!doctype html>',
296
+ '<html lang="zh-CN">',
297
+ '<head>',
298
+ '<meta charset="utf-8"/>',
299
+ '<meta name="viewport" content="width=device-width,initial-scale=1"/>',
300
+ '<title>' + base + '</title>',
301
+ katexCss,
302
+ '<style>',
303
+ 'body{margin:0;background:#fff;color:#24292f;font:16px/1.7 -apple-system,"Segoe UI",system-ui,"Microsoft YaHei",sans-serif}',
304
+ '.markdown-body{box-sizing:border-box;max-width:980px;margin:0 auto;padding:45px}',
305
+ '@media (max-width:767px){.markdown-body{padding:16px}}',
306
+ '.markdown-body h1,.markdown-body h2{border-bottom:1px solid #eaecef;padding-bottom:.3em}',
307
+ '.markdown-body code{background:#f6f8fa;border-radius:4px;padding:.2em .4em;font-size:.9em}',
308
+ '.markdown-body pre{background:#f6f8fa;border-radius:8px;padding:14px;overflow:auto}',
309
+ '.markdown-body pre code{background:none;padding:0}',
310
+ '.markdown-body blockquote{margin:0;padding:0 1em;color:#57606a;border-left:.25em solid #d0d7de}',
311
+ '.markdown-body table{border-collapse:collapse}.markdown-body th,.markdown-body td{border:1px solid #d0d7de;padding:6px 12px}',
312
+ '.markdown-body img{max-width:100%}',
313
+ '</style>',
314
+ '</head>',
315
+ '<body><article class="markdown-body">',
316
+ String(html || ''),
317
+ '</article></body></html>',
318
+ ].join('\n')
319
+
320
+ let targetDir = dir
321
+ if (!targetDir) {
322
+ const picked = win.ztools.showSaveDialog({
323
+ defaultPath: path.join(win.ztools.getPath('downloads') || '', base),
324
+ })
325
+ if (!picked) return null
326
+ targetDir = ensureDir(picked)
327
+ }
328
+ for (const a of attachmentPaths || []) {
329
+ try {
330
+ fs.copyFileSync(a, path.join(targetDir, path.basename(a)))
331
+ } catch (e) {}
332
+ }
333
+ const file = path.join(targetDir, base + '.html')
334
+ fs.writeFileSync(file, doc, 'utf8')
335
+ return file
336
+ },
337
+
338
+ /**
339
+ * 导出 PDF / 图片:开隐藏窗口渲染 HTML,再抓图或打印
340
+ * type: 'pdf' | 'image'
341
+ */
342
+ exportFile(type, html, extraCss, cb, outDir) {
343
+ const done = typeof cb === 'function' ? cb : () => {}
344
+ if (!win.ztools.createBrowserWindow) {
345
+ done(new Error('当前平台不支持静默渲染,请改用导出 HTML'))
346
+ return
347
+ }
348
+ const shell = [
349
+ '<!doctype html><html><head><meta charset="utf-8"/>',
350
+ '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">',
351
+ '<style>',
352
+ 'html,body{margin:0;padding:0;background:#fff}',
353
+ '.markdown-body{box-sizing:border-box;max-width:980px;margin:0 auto;padding:45px;',
354
+ 'color:#24292f;font:16px/1.7 -apple-system,"Segoe UI",system-ui,"Microsoft YaHei",sans-serif}',
355
+ extraCss || '',
356
+ '</style></head><body><article class="markdown-body"></article></body></html>',
357
+ ].join('\n')
358
+
359
+ const tmpHtml = path.join(attachmentCacheDir(win), 'export-' + Date.now() + '.html')
360
+ fs.writeFileSync(tmpHtml, shell, 'utf8')
361
+
362
+ let bw
363
+ try {
364
+ bw = win.ztools.createBrowserWindow(
365
+ 'file://' + tmpHtml.replace(/\\/g, '/'),
366
+ { width: 980, height: 1200, show: false },
367
+ async () => {
368
+ try {
369
+ await bw.webContents.executeJavaScript(
370
+ 'document.querySelector(".markdown-body").innerHTML = ' + JSON.stringify(String(html || ''))
371
+ )
372
+ // 等图片解码,最多 3 秒
373
+ await bw.webContents.executeJavaScript(
374
+ 'Promise.all(Array.from(document.images).map(i => i.complete ? 1 : new Promise(r => { i.onload = i.onerror = r })))'
375
+ )
376
+
377
+ let payload
378
+ if (type === 'image') {
379
+ const h = await bw.webContents.executeJavaScript('document.body.scrollHeight')
380
+ bw.setContentSize(980, Math.min(h + 30, 20000))
381
+ const img = await bw.webContents.capturePage()
382
+ payload = img.toPNG()
383
+ } else {
384
+ payload = await bw.webContents.printToPDF({
385
+ printBackground: true,
386
+ pageSize: 'A4',
387
+ landscape: false,
388
+ marginsType: 0,
389
+ })
390
+ }
391
+
392
+ const dir = outDir || win.ztools.getPath('temp')
393
+ const ext = type === 'image' ? '.png' : '.pdf'
394
+ const out = path.join(dir, 'cmark-export-' + Date.now() + ext)
395
+ fs.writeFileSync(out, payload)
396
+ try {
397
+ bw.destroy()
398
+ } catch (e) {}
399
+ done(null, out)
400
+ } catch (e) {
401
+ try {
402
+ bw.destroy()
403
+ } catch (e2) {}
404
+ done(e)
405
+ }
406
+ }
407
+ )
408
+ } catch (e) {
409
+ done(e)
410
+ }
411
+ },
412
+
413
+ /** 把导出的临时文件"另存为"到用户选的位置 */
414
+ dialogSaveExportFile(tempPath, name, ext) {
415
+ const base = safeName(name, Date.now())
416
+ const realExt = ext === 'image' ? 'png' : ext
417
+ const target = win.ztools.showSaveDialog({
418
+ defaultPath: path.join(win.ztools.getPath('downloads') || '', base + '.' + realExt),
419
+ filters: [{ name: realExt.toUpperCase(), extensions: [realExt] }],
420
+ })
421
+ if (!target) return null
422
+ try {
423
+ fs.copyFileSync(tempPath, target)
424
+ return target
425
+ } catch (e) {
426
+ return null
427
+ }
428
+ },
429
+ }
430
+
431
+ return svc
432
+ }