@bytechain.cn/colamd 1.5.0 → 1.5.1-beta.2

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 (193) hide show
  1. package/.trae/specs/optimize-theme-loading/checklist.md +20 -0
  2. package/.trae/specs/optimize-theme-loading/spec.md +103 -0
  3. package/.trae/specs/optimize-theme-loading/tasks.md +40 -0
  4. package/CHANGELOG.md +323 -0
  5. package/CLAUDE.md +56 -0
  6. package/README.md +422 -26
  7. package/README_CN.md +480 -28
  8. package/android/app/build/.npmkeep +0 -0
  9. package/android/app/build.gradle +76 -0
  10. package/android/app/capacitor.build.gradle +24 -0
  11. package/android/app/proguard-rules.pro +21 -0
  12. package/android/app/release.keystore +0 -0
  13. package/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
  14. package/android/app/src/main/AndroidManifest.xml +64 -0
  15. package/android/app/src/main/java/cn/bytechain/colamd/MainActivity.java +180 -0
  16. package/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  17. package/android/app/src/main/res/drawable/splash.png +0 -0
  18. package/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  19. package/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  20. package/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  21. package/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  22. package/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  23. package/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  24. package/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  25. package/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  26. package/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  27. package/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  28. package/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
  29. package/android/app/src/main/res/layout/activity_main.xml +12 -0
  30. package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  31. package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  32. package/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  33. package/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  34. package/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  35. package/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  36. package/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  37. package/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  38. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  39. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  40. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  41. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  42. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  43. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  44. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  45. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  46. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  47. package/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
  48. package/android/app/src/main/res/values/strings.xml +7 -0
  49. package/android/app/src/main/res/values/styles.xml +22 -0
  50. package/android/app/src/main/res/xml/file_paths.xml +5 -0
  51. package/android/app/src/main/res/xml/network_security_config.xml +8 -0
  52. package/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
  53. package/android/build.gradle +29 -0
  54. package/android/capacitor.settings.gradle +21 -0
  55. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  56. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  57. package/android/gradle.properties +22 -0
  58. package/android/gradlew +248 -0
  59. package/android/gradlew.bat +92 -0
  60. package/android/settings.gradle +5 -0
  61. package/android/variables.gradle +16 -0
  62. package/bytechain.cn-colamd-1.5.1-beta.2.tgz +0 -0
  63. package/capacitor.config.js +29 -0
  64. package/capacitor.config.ts +30 -0
  65. package/demo.md +191 -484
  66. package/dist/main/index.js +77 -46
  67. package/dist/renderer/assets/{arc-tTbbM8LO.js → arc-CPdeInCG.js} +1 -1
  68. package/dist/renderer/assets/{architectureDiagram-3BPJPVTR-CEgYow6c.js → architectureDiagram-3BPJPVTR-BAbnaR9G.js} +4 -3
  69. package/dist/renderer/assets/{blockDiagram-GPEHLZMM-LHyVtPwW.js → blockDiagram-GPEHLZMM-CYSWjnJg.js} +5 -4
  70. package/dist/renderer/assets/{c4Diagram-AAUBKEIU-C1P1eJrf.js → c4Diagram-AAUBKEIU-Rb1tstnr.js} +3 -2
  71. package/dist/renderer/assets/{channel-upve91Tq.js → channel-DpG2A6fE.js} +1 -1
  72. package/dist/renderer/assets/{chunk-2J33WTMH-lag2vhq9.js → chunk-2J33WTMH-DFc0Jxy_.js} +1 -1
  73. package/dist/renderer/assets/{chunk-4BX2VUAB-BXJ8Ggh-.js → chunk-4BX2VUAB-BhRxDTNn.js} +1 -1
  74. package/dist/renderer/assets/{chunk-55IACEB6-CiBpxRa1.js → chunk-55IACEB6-DEgMVBk8.js} +1 -1
  75. package/dist/renderer/assets/{chunk-727SXJPM-ODeKQFXC.js → chunk-727SXJPM-bjBIfiz8.js} +5 -5
  76. package/dist/renderer/assets/{chunk-AQP2D5EJ-BK7xJolB.js → chunk-AQP2D5EJ-DwQMzTzD.js} +3 -3
  77. package/dist/renderer/assets/{chunk-FMBD7UC4-BxpCZPtz.js → chunk-FMBD7UC4-CkphwJzs.js} +1 -1
  78. package/dist/renderer/assets/{chunk-ND2GUHAM-CqqaU9Ue.js → chunk-ND2GUHAM-oU09z4y4.js} +1 -1
  79. package/dist/renderer/assets/{chunk-QZHKN3VN-Biq_K124.js → chunk-QZHKN3VN-rCbVuPBn.js} +1 -1
  80. package/dist/renderer/assets/{classDiagram-v2-Q7XG4LA2-Cq95X99o.js → classDiagram-4FO5ZUOK-DGS2faoM.js} +7 -6
  81. package/dist/renderer/assets/{classDiagram-4FO5ZUOK-Cq95X99o.js → classDiagram-v2-Q7XG4LA2-DGS2faoM.js} +7 -6
  82. package/dist/renderer/assets/{cose-bilkent-S5V4N54A-XasiD0bu.js → cose-bilkent-S5V4N54A-iqY6-EwA.js} +2 -1
  83. package/dist/renderer/assets/{dagre-BM42HDAG-Nq84Gfx4.js → dagre-BM42HDAG-5t3X5sDa.js} +4 -3
  84. package/dist/renderer/assets/{diagram-2AECGRRQ-DwuB1GWt.js → diagram-2AECGRRQ-DzHiYDPh.js} +4 -3
  85. package/dist/renderer/assets/{diagram-5GNKFQAL-C2tgeI1h.js → diagram-5GNKFQAL-BiNv6Keq.js} +5 -4
  86. package/dist/renderer/assets/{diagram-KO2AKTUF-D5KzjNBc.js → diagram-KO2AKTUF-ClzeDG6f.js} +4 -3
  87. package/dist/renderer/assets/{diagram-LMA3HP47-C12xHS1c.js → diagram-LMA3HP47-CGkw7wII.js} +4 -3
  88. package/dist/renderer/assets/{diagram-OG6HWLK6-CnxI9oEa.js → diagram-OG6HWLK6-Dl-Hyk1_.js} +5 -4
  89. package/dist/renderer/assets/{erDiagram-TEJ5UH35-D_uPaKwn.js → erDiagram-TEJ5UH35-BxUN79Qb.js} +5 -4
  90. package/dist/renderer/assets/{flowDiagram-I6XJVG4X-B6q_1-tE.js → flowDiagram-I6XJVG4X-CzFk-KNI.js} +7 -6
  91. package/dist/renderer/assets/{ganttDiagram-6RSMTGT7-CFo7ifF9.js → ganttDiagram-6RSMTGT7-C2xl6Igx.js} +3 -2
  92. package/dist/renderer/assets/{gitGraphDiagram-PVQCEYII-WSexHTnq.js → gitGraphDiagram-PVQCEYII-_fn7XCa7.js} +5 -4
  93. package/dist/renderer/assets/{graph-DyX_9f6d.js → graph-CDoHYrHm.js} +1 -1
  94. package/dist/renderer/assets/index-B4uDgADr.js +530 -0
  95. package/dist/renderer/assets/index-CBcVpA3d.js +30 -0
  96. package/dist/renderer/assets/index-CGj1spkU.js +27 -0
  97. package/dist/renderer/assets/{index-dyHEFYvY.css → index-CeFpoCKV.css} +443 -400
  98. package/dist/renderer/assets/index-D4CPFkph.js +9 -0
  99. package/dist/renderer/assets/{index-DW7LS8C1.js → index-DAlXyxzt.js} +1183 -346
  100. package/dist/renderer/assets/index-DxOzbfR-.js +110 -0
  101. package/dist/renderer/assets/index-Y89U1ptl.js +9 -0
  102. package/dist/renderer/assets/{infoDiagram-5YYISTIA-DaeJdLRq.js → infoDiagram-5YYISTIA-DL6XIxLz.js} +3 -2
  103. package/dist/renderer/assets/{ishikawaDiagram-YF4QCWOH-DDCZc35f.js → ishikawaDiagram-YF4QCWOH-BUZLjRo-.js} +2 -1
  104. package/dist/renderer/assets/{journeyDiagram-JHISSGLW-BEdmpAgl.js → journeyDiagram-JHISSGLW-C4rH_mQM.js} +5 -4
  105. package/dist/renderer/assets/{kanban-definition-UN3LZRKU-BEFtQcFb.js → kanban-definition-UN3LZRKU-DRbrBcWV.js} +3 -2
  106. package/dist/renderer/assets/{layout-CAJgQHdw.js → layout-DZl4n4qu.js} +2 -2
  107. package/dist/renderer/assets/{linear-B2ggJ8Am.js → linear-B0Krxg21.js} +1 -1
  108. package/dist/renderer/assets/{mindmap-definition-RKZ34NQL-DSxVgHB5.js → mindmap-definition-RKZ34NQL-DdmPsWrn.js} +4 -3
  109. package/dist/renderer/assets/{pieDiagram-4H26LBE5-CwYoJBuL.js → pieDiagram-4H26LBE5-BPZLqwG0.js} +5 -4
  110. package/dist/renderer/assets/preload-helper-tXtZnHb0.js +88 -0
  111. package/dist/renderer/assets/{quadrantDiagram-W4KKPZXB-CST9Fvg9.js → quadrantDiagram-W4KKPZXB-Dr-oWRk9.js} +3 -2
  112. package/dist/renderer/assets/{requirementDiagram-4Y6WPE33-DtrH52jS.js → requirementDiagram-4Y6WPE33-B6QZd0lo.js} +4 -3
  113. package/dist/renderer/assets/{sankeyDiagram-5OEKKPKP-ca1tPzJ_.js → sankeyDiagram-5OEKKPKP-Cyl9ojEt.js} +2 -1
  114. package/dist/renderer/assets/{sequenceDiagram-3UESZ5HK-Dfp1EJZ7.js → sequenceDiagram-3UESZ5HK-D48Yr9T6.js} +4 -3
  115. package/dist/renderer/assets/{stateDiagram-AJRCARHV-Bha2QoNB.js → stateDiagram-AJRCARHV-qyb8ETsa.js} +7 -6
  116. package/dist/renderer/assets/{stateDiagram-v2-BHNVJYJU-DWgFUYu1.js → stateDiagram-v2-BHNVJYJU-DmDOyyrJ.js} +5 -4
  117. package/dist/renderer/assets/{timeline-definition-PNZ67QCA-C3h_-OTj.js → timeline-definition-PNZ67QCA-C-KQxTi1.js} +3 -2
  118. package/dist/renderer/assets/{vennDiagram-CIIHVFJN-DFzjSrZi.js → vennDiagram-CIIHVFJN-BdaZlnH-.js} +2 -1
  119. package/dist/renderer/assets/{wardley-L42UT6IY-Cx-VbqoS.js → wardley-L42UT6IY-b-_GPpqL.js} +1 -1
  120. package/dist/renderer/assets/{wardleyDiagram-YWT4CUSO-S2D9XqX6.js → wardleyDiagram-YWT4CUSO-B2hBE-EE.js} +4 -3
  121. package/dist/renderer/assets/web-BKE0SH0E.js +36 -0
  122. package/dist/renderer/assets/web-CBsFp24u.js +564 -0
  123. package/dist/renderer/assets/web-Dc8YgoHP.js +24 -0
  124. package/dist/renderer/assets/web-TfDzToU7.js +58 -0
  125. package/dist/renderer/assets/{xychartDiagram-2RQKCTM6-Cfxigbts.js → xychartDiagram-2RQKCTM6-CSvswDTY.js} +3 -2
  126. package/dist/renderer/index.html +62 -3
  127. package/docs/academic-demo.md +566 -0
  128. package/docs/demo.html +748 -0
  129. package/docs/demo.md +546 -0
  130. package/docs/demo.pdf +0 -0
  131. package/docs/theme-paradigm.md +658 -0
  132. package/electron-builder.yml +7 -0
  133. package/electron.vite.config.js +31 -0
  134. package/electron.vite.config.ts +1 -1
  135. package/ios/App/App/AppDelegate.swift +49 -0
  136. package/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png +0 -0
  137. package/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
  138. package/ios/App/App/Assets.xcassets/Contents.json +6 -0
  139. package/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json +23 -0
  140. package/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png +0 -0
  141. package/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png +0 -0
  142. package/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png +0 -0
  143. package/ios/App/App/Base.lproj/LaunchScreen.storyboard +32 -0
  144. package/ios/App/App/Base.lproj/Main.storyboard +19 -0
  145. package/ios/App/App/Info.plist +49 -0
  146. package/ios/App/App.xcodeproj/project.pbxproj +408 -0
  147. package/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  148. package/ios/App/Podfile +28 -0
  149. package/package.json +23 -3
  150. package/resources/templates/slides/template-forest-ink.html +540 -0
  151. package/scripts/generate-icons.js +102 -0
  152. package/src/main/index.ts +87 -63
  153. package/src/preload/index.d.ts +51 -0
  154. package/src/preload/index.js +70 -0
  155. package/src/renderer/capacitor-api.ts +713 -0
  156. package/src/renderer/editor/editor.ts +87 -4
  157. package/src/renderer/editor/plugins/index.ts +24 -32
  158. package/src/renderer/editor/plugins/math-plugin.ts +1 -1
  159. package/src/renderer/editor/plugins/mermaid-plugin-custom.css +13 -398
  160. package/src/renderer/editor/plugins/mermaid-plugin.ts +62 -71
  161. package/src/renderer/editor/plugins/themes/base/dark.css +23 -0
  162. package/src/renderer/editor/plugins/themes/base/elegant.css +32 -0
  163. package/src/renderer/editor/plugins/themes/base/light.css +20 -0
  164. package/src/renderer/editor/plugins/themes/base/newsprint.css +27 -0
  165. package/src/renderer/editor/plugins/themes/components/mermaid/academic.css +43 -0
  166. package/src/renderer/editor/plugins/themes/components/mermaid/dark.css +20 -0
  167. package/src/renderer/editor/plugins/themes/components/mermaid/elegant.css +24 -0
  168. package/src/renderer/editor/plugins/themes/components/mermaid/light.css +21 -0
  169. package/src/renderer/editor/plugins/themes/components/mermaid/newsprint.css +26 -0
  170. package/src/renderer/editor/plugins/themes/components/mermaid/variables.css +592 -0
  171. package/src/renderer/editor/plugins/themes/foundation.css +143 -0
  172. package/src/renderer/editor/plugins/themes/theme-manager.ts +92 -0
  173. package/src/renderer/env.d.ts +4 -1
  174. package/src/renderer/index.html +59 -1
  175. package/src/renderer/main.ts +432 -57
  176. package/src/renderer/mobile.css +429 -0
  177. package/themes/README.md +3 -0
  178. package/themes/academic-paper.css +1321 -0
  179. package/themes/elegant.css +14 -7
  180. package/themes/forest-ink.css +664 -0
  181. package/themes/pixso-design.css +1261 -0
  182. package/themes/swiss-design.css +596 -0
  183. package/themes/template.css +498 -0
  184. package/tsconfig.main.json +1 -0
  185. package/tsconfig.main.tsbuildinfo +1 -0
  186. package/tsconfig.preload.json +1 -0
  187. package/tsconfig.preload.tsbuildinfo +1 -0
  188. package/tsconfig.renderer.json +1 -0
  189. package/tsconfig.renderer.tsbuildinfo +1 -0
  190. package/tsconfig.tsbuildinfo +1 -0
  191. package/.trae/documents/fix-mermaid-colors-and-sankey.md +0 -50
  192. package/src/renderer/themes/theme-manager.ts +0 -40
  193. /package/src/renderer/{themes → editor/plugins/themes}/base.css +0 -0
@@ -1,10 +1,84 @@
1
- import { createEditor, getMarkdown, getHTML, setMarkdown, togglePluginMode } from './editor/editor'
2
- import { applyTheme, loadSavedTheme } from './themes/theme-manager'
1
+ import { createEditor, getMarkdown, getHTML, getLiveHTML, setMarkdown, togglePluginMode } from './editor/editor'
2
+ import { applyTheme, loadSavedTheme, setCachedCustomTheme } from './editor/plugins/themes/theme-manager'
3
3
  import { getAllPlugins, togglePlugin, findPluginBySelector, findExportCapabilities } from './editor/plugins'
4
- import './themes/base.css'
4
+ import './editor/plugins/math-plugin'
5
+ import './editor/plugins/mermaid-plugin'
6
+ import { createCapacitorAPI } from './capacitor-api'
7
+ import './editor/plugins/themes/base.css'
8
+ import './mobile.css'
9
+
10
+ /**
11
+ * Show a brief toast notification. Used for save/export feedback.
12
+ */
13
+ function showToast(message: string, duration = 2000): void {
14
+ const existing = document.querySelector('.cola-toast')
15
+ if (existing) existing.remove()
16
+
17
+ const toast = document.createElement('div')
18
+ toast.className = 'cola-toast'
19
+ toast.textContent = message
20
+ toast.style.cssText =
21
+ 'position:fixed;top:56px;left:50%;transform:translateX(-50%);' +
22
+ 'background:rgba(0,0,0,0.8);color:#fff;padding:10px 20px;' +
23
+ 'border-radius:8px;font-size:14px;z-index:9999;pointer-events:none;'
24
+ document.body.appendChild(toast)
25
+
26
+ setTimeout(() => {
27
+ toast.style.opacity = '0'
28
+ toast.style.transition = 'opacity 0.3s'
29
+ setTimeout(() => toast.remove(), 300)
30
+ }, duration)
31
+ }
32
+
33
+ /**
34
+ * 调用原生桥接层检查是否有通过文件管理器 Intent 待打开的文件。
35
+ * 使用 pull 模式:JS 主动向 Java 查询,避免注入时序问题。
36
+ */
37
+ function checkAndOpenPendingFile(api?: any): void {
38
+ const bridge = (window as any).ColaMDNative
39
+ if (!bridge || typeof bridge.checkPendingFile !== 'function') return
40
+ try {
41
+ const result = bridge.checkPendingFile()
42
+ if (result && result !== 'null') {
43
+ const data = JSON.parse(result)
44
+ if (data && data.content) {
45
+ setContent(data.content)
46
+ if (data.name && api?.setCurrentFile) {
47
+ api.setCurrentFile(data.name)
48
+ }
49
+ }
50
+ }
51
+ } catch (e) { /* ignore */ }
52
+ }
53
+
54
+ /**
55
+ * 轮询原生桥接层,获取待打开的文件。
56
+ * Android onNewIntent 在 WebView 重载前触发,需要轮询等待。
57
+ */
58
+ function processPendingIntentFile(api?: any): void {
59
+ checkAndOpenPendingFile(api)
60
+ }
5
61
 
6
- const pluginModules = import.meta.glob<{ default?: unknown }>('./editor/plugins/*-plugin.ts', { eager: true })
7
- Object.keys(pluginModules)
62
+ // Eagerly load all plugin modules for side-effect registration (registerPluginModule)
63
+ const _pluginRegistry = import.meta.glob<{ default?: unknown }>('./editor/plugins/*-plugin.ts', { eager: true })
64
+ Object.keys(_pluginRegistry)
65
+
66
+ /** Call ensureRendered on all enabled plugins that support it. Used before export. */
67
+ async function ensureAllPluginsRendered(): Promise<void> {
68
+ for (const p of getAllPlugins()) {
69
+ if (p.enabled && p.ensureRendered) await p.ensureRendered()
70
+ }
71
+ }
72
+
73
+ /** Re-render nodes of plugins that react to theme changes (e.g. mermaid). */
74
+ function refreshThemeSensitivePlugins(): void {
75
+ for (const p of getAllPlugins()) {
76
+ if (p.enabled && p.nodeTypes && p.onThemeChange) {
77
+ togglePluginMode(p.nodeTypes, 'raw')
78
+ requestAnimationFrame(() => togglePluginMode(p.nodeTypes, 'rendered'))
79
+ }
80
+ }
81
+ }
8
82
 
9
83
  function isSlidesContent(content: string): boolean {
10
84
  return /^---\s*\n[\s\S]*?(kicker|chip):/m.test(content)
@@ -45,36 +119,92 @@ function getContent(): string {
45
119
  return getMarkdown()
46
120
  }
47
121
 
122
+ function syncRawEdits(): void {
123
+ document.querySelectorAll('.math-block-raw, .math-inline-raw, .mermaid-source').forEach((el) => {
124
+ if ((el as HTMLElement).matches(':focus')) (el as HTMLElement).blur()
125
+ })
126
+ }
127
+
128
+ function restoreRenderedMode(api: any): void {
129
+ for (const p of getAllPlugins()) {
130
+ if (!p.enabled) {
131
+ p.enabled = true
132
+ togglePluginMode(p.nodeTypes || [], 'rendered')
133
+ api.syncPluginState(p.id, true)
134
+ }
135
+ }
136
+ }
137
+
48
138
  async function init(): Promise<void> {
49
- const api = window.electronAPI
50
- const savedTheme = loadSavedTheme()
51
- applyTheme(savedTheme)
52
-
53
- if (savedTheme.startsWith('custom:')) {
54
- const fileName = savedTheme.slice(7)
55
- const css = await api.loadThemeCSS(fileName)
56
- if (css) applyTheme(savedTheme, css)
139
+ const api = window.electronAPI || await createCapacitorAPI()
140
+
141
+ let editorReady = false
142
+ let pendingFileContent: string | null = null
143
+
144
+ // Register file-open listener AS EARLY AS POSSIBLE
145
+ api.onFileOpened((data) => {
146
+ if (editorReady) {
147
+ setContent(data.content)
148
+ } else {
149
+ pendingFileContent = data.content
150
+ }
151
+ })
152
+
153
+ let savedTheme = loadSavedTheme()
154
+
155
+ try {
156
+ if (savedTheme.startsWith('custom:')) {
157
+ const fileName = savedTheme.slice(7)
158
+ const css = await api.loadThemeCSS(fileName)
159
+ if (css) {
160
+ setCachedCustomTheme(fileName, css)
161
+ applyTheme(savedTheme)
162
+ } else {
163
+ applyTheme('elegant')
164
+ }
165
+ } else {
166
+ applyTheme(savedTheme)
167
+ }
168
+ } catch (e) {
169
+ console.error('Theme initialization failed, falling back to elegant:', e)
170
+ applyTheme('elegant')
57
171
  }
58
172
 
59
- await createEditor('editor')
173
+ try {
174
+ await createEditor('editor')
175
+ } catch (e) {
176
+ console.error('Editor initialization failed:', e)
177
+ }
178
+ editorReady = true
179
+
180
+ if (pendingFileContent !== null) {
181
+ setContent(pendingFileContent)
182
+ pendingFileContent = null
183
+ }
60
184
 
61
- // Run plugin init hooks (mermaid initialize, etc.)
62
185
  for (const p of getAllPlugins()) p.onInit?.()
186
+ checkAndOpenPendingFile(api)
63
187
 
64
- // Send plugin list to main process for menu
65
188
  api.registerPlugins(getAllPlugins().map((p) => ({ id: p.id, name: p.name, enabled: p.enabled })))
66
189
 
67
- // Handle plugin toggle from menu
68
190
  api.onMenuTogglePlugin((id) => {
69
- togglePlugin(id)
70
191
  const p = getAllPlugins().find((x) => x.id === id)
71
192
  if (p) {
193
+ togglePlugin(id, !p.enabled)
72
194
  const mode = p.enabled ? 'rendered' : 'raw'
73
- togglePluginMode(p.nodeTypes, mode)
195
+ togglePluginMode(p.nodeTypes || [], mode)
74
196
  api.syncPluginState(p.id, p.enabled)
75
197
  }
76
198
  })
77
199
 
200
+ // ─── Mobile menu setup ───
201
+ setupMobileMenu(api, savedTheme)
202
+
203
+ // Continuously check for Intent files (Android only, APP already running)
204
+ if (!window.electronAPI) {
205
+ setInterval(() => processPendingIntentFile(api), 3000)
206
+ }
207
+
78
208
  // Slides button — open as slides
79
209
  slidesBtnEl().addEventListener('click', () => api.openAsSlides(getContent()))
80
210
 
@@ -83,39 +213,43 @@ async function init(): Promise<void> {
83
213
  if (result) setContent(result.content)
84
214
  })
85
215
 
86
- function syncRawEdits(): void {
87
- document.querySelectorAll('.math-block-raw, .math-inline-raw, .mermaid-source').forEach((el) => {
88
- if ((el as HTMLElement).matches(':focus')) (el as HTMLElement).blur()
89
- })
90
- }
91
-
92
- function restoreRenderedMode(): void {
93
- for (const p of getAllPlugins()) {
94
- if (!p.enabled) {
95
- p.enabled = true
96
- togglePluginMode(p.nodeTypes, 'rendered')
97
- api.syncPluginState(p.id, true)
98
- }
99
- }
100
- }
101
-
102
216
  api.onMenuSave(async () => {
103
217
  syncRawEdits()
104
218
  const ok = await api.saveFile(getContent())
105
- if (ok) restoreRenderedMode()
219
+ if (ok) {
220
+ showToast('Saved')
221
+ restoreRenderedMode(api)
222
+ } else {
223
+ showToast('Save failed')
224
+ }
106
225
  })
107
226
  api.onMenuSaveAs(async () => {
108
227
  syncRawEdits()
109
228
  const ok = await api.saveFileAs(getContent())
110
- if (ok) restoreRenderedMode()
229
+ if (ok) {
230
+ showToast('Saved')
231
+ restoreRenderedMode(api)
232
+ } else {
233
+ showToast('Save cancelled or failed')
234
+ }
111
235
  })
112
236
  api.onMenuExportPDF(async () => {
113
237
  syncRawEdits()
114
- restoreRenderedMode()
115
- await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)))
116
- await api.exportPDF()
238
+ restoreRenderedMode(api)
239
+ await ensureAllPluginsRendered()
240
+ await new Promise(r => setTimeout(r, 300))
241
+ await api.exportPDF(buildExportHTML())
242
+ setTimeout(() => {
243
+ document.querySelectorAll('.mermaid-loading, .mermaid-error')
244
+ .forEach(el => (el as HTMLElement).style.display = 'none')
245
+ }, 1000)
117
246
  })
118
- api.onMenuExportHTML(() => {
247
+ api.onMenuExportHTML(async () => {
248
+ syncRawEdits()
249
+ restoreRenderedMode(api)
250
+ await ensureAllPluginsRendered()
251
+ await new Promise(r => setTimeout(r, 200))
252
+
119
253
  const s = getComputedStyle(document.body)
120
254
  const v = (name: string) => s.getPropertyValue(name).trim()
121
255
  const bgColor = v('--bg-color')
@@ -126,6 +260,7 @@ async function init(): Promise<void> {
126
260
  const codeBg = v('--code-bg')
127
261
  const codeBlockBg = v('--code-block-bg')
128
262
  const codeBlockText = v('--code-block-text') || textColor
263
+ const mermaidBg = v('--mermaid-background') || bgColor
129
264
  const blockquoteBorder = v('--blockquote-border')
130
265
  const blockquoteBg = v('--blockquote-bg') || 'transparent'
131
266
  const tableHeaderBg = v('--table-header-bg')
@@ -161,19 +296,18 @@ th{background:${tableHeaderBg};font-weight:600}
161
296
  hr{border:none;border-top:2px solid ${borderColor};margin:2em 0}
162
297
  img{max-width:100%}
163
298
  ::selection{background:${selectionBg}}
164
- .math-inline{display:inline;padding:2px 4px;border-radius:3px;background:${codeBg}}
165
- .math-block{display:block;padding:16px;margin:1em 0;border-radius:6px;background:${codeBlockBg};text-align:center;overflow-x:auto}
166
- .mermaid-block{display:block;padding:16px;margin:1em 0;border-radius:6px;background:${codeBlockBg};border:1px solid ${borderColor}}
299
+ .math-inline{display:inline;padding:2px 4px;border-radius:3px;background:${mermaidBg}}
300
+ .math-block{display:block;padding:16px;margin:1em 0;border-radius:6px;background:${mermaidBg};text-align:center;overflow-x:auto}
301
+ .mermaid-block{display:block;padding:16px;margin:1em 0;border-radius:6px;background:${mermaidBg};border:1px solid ${borderColor}}
167
302
  .mermaid-preview{display:flex;justify-content:center;align-items:center}
168
303
  .mermaid-preview svg{max-width:100%;height:auto}
169
304
  .mermaid-preview svg .label text,.mermaid-preview svg .nodeLabel text,.mermaid-preview svg .state-title,.mermaid-preview svg .state-description,.mermaid-preview svg .pieTitleText,.mermaid-preview svg .titleText{transform:translateY(-2px)}.mermaid-preview svg .nodeLabel,.mermaid-preview svg .edgeLabel{display:inline-block;position:relative;top:-2px}
170
305
  </style>
171
- </head><body>${getHTML()}</body></html>`
306
+ </head><body>${getLiveHTML()}</body></html>`
172
307
  api.exportHTML(html)
173
308
  })
174
309
 
175
310
  api.onNewFile(() => { exitSourceMode(); setMarkdown('') })
176
- api.onFileOpened((data) => setContent(data.content))
177
311
  api.onFileChanged((content) => {
178
312
  if (sourceModeActive) {
179
313
  sourceEl().value = content
@@ -181,20 +315,25 @@ img{max-width:100%}
181
315
  setMarkdown(content)
182
316
  }
183
317
  })
184
- api.onSetTheme((theme) => {
318
+ let pendingThemeChange: string | null = null
319
+ let themeChangeTimer: ReturnType<typeof setTimeout> | null = null
320
+
321
+ function applyThemeChange(theme: string): void {
322
+ pendingThemeChange = null
185
323
  applyTheme(theme)
186
- // Notify all plugins of theme change
187
324
  for (const p of getAllPlugins()) p.onThemeChange?.(theme)
188
- // Force re-render all enabled plugin nodes so they pick up the new theme
189
- for (const p of getAllPlugins()) {
190
- if (p.enabled) {
191
- togglePluginMode(p.nodeTypes, 'raw')
192
- requestAnimationFrame(() => togglePluginMode(p.nodeTypes, 'rendered'))
193
- }
194
- }
325
+ refreshThemeSensitivePlugins()
326
+ }
327
+
328
+ api.onSetTheme((theme) => {
329
+ if (themeChangeTimer) clearTimeout(themeChangeTimer)
330
+ pendingThemeChange = theme
331
+ themeChangeTimer = setTimeout(() => {
332
+ if (pendingThemeChange) applyThemeChange(pendingThemeChange)
333
+ }, 50)
195
334
  })
196
335
  api.onSetCustomCSS((css) => {
197
- const theme = loadSavedTheme()
336
+ const theme = pendingThemeChange || loadSavedTheme()
198
337
  applyTheme(theme, css)
199
338
  })
200
339
 
@@ -216,7 +355,10 @@ img{max-width:100%}
216
355
 
217
356
  api.onMenuImportTheme(async () => {
218
357
  const result = await api.loadCustomTheme()
219
- if (result) applyTheme(`custom:${result.name}`, result.css)
358
+ if (result) {
359
+ setCachedCustomTheme(result.name, result.css)
360
+ applyTheme(`custom:${result.name}`)
361
+ }
220
362
  })
221
363
 
222
364
  const agentDot = document.getElementById('agent-dot')
@@ -300,4 +442,237 @@ img{max-width:100%}
300
442
  })
301
443
  }
302
444
 
445
+ /**
446
+ * 初始化移动端侧边栏菜单的交互逻辑。
447
+ * 包括菜单开关、主题切换、插件开关、文件操作等。
448
+ * @param api 平台 API(Electron 或 Capacitor)
449
+ * @param currentTheme 当前激活的主题名称
450
+ */
451
+ function setupMobileMenu(api: any, currentTheme: string): void {
452
+ const menuEl = document.getElementById('mobile-menu')!
453
+ const menuBtn = document.getElementById('menu-btn')!
454
+ const closeBtn = document.getElementById('menu-close-btn')
455
+ const overlay = document.getElementById('menu-overlay')
456
+ if (!menuEl || !menuBtn) return
457
+
458
+ function openMenu(): void {
459
+ menuEl.classList.add('open')
460
+ }
461
+
462
+ function closeMenu(): void {
463
+ menuEl.classList.remove('open')
464
+ }
465
+
466
+ menuBtn.addEventListener('click', openMenu)
467
+ closeBtn?.addEventListener('click', closeMenu)
468
+ overlay?.addEventListener('click', closeMenu)
469
+
470
+ // 主题列表
471
+ const themes = [
472
+ { key: 'light', label: 'Light' },
473
+ { key: 'dark', label: 'Dark' },
474
+ { key: 'elegant', label: 'Elegant' },
475
+ { key: 'newsprint', label: 'Newsprint' },
476
+ ]
477
+
478
+ const themeListEl = document.getElementById('menu-theme-list')
479
+ if (themeListEl) {
480
+ themeListEl.innerHTML = themes.map((t) =>
481
+ `<div class="menu-theme-item${t.key === currentTheme ? ' active' : ''}" data-theme="${t.key}">${t.label}</div>`
482
+ ).join('')
483
+
484
+ themeListEl.querySelectorAll('.menu-theme-item').forEach((el) => {
485
+ el.addEventListener('click', () => {
486
+ const theme = (el as HTMLElement).dataset.theme!
487
+ applyTheme(theme)
488
+ for (const p of getAllPlugins()) p.onThemeChange?.(theme)
489
+ refreshThemeSensitivePlugins()
490
+ themeListEl.querySelectorAll('.menu-theme-item').forEach((e) => e.classList.remove('active'))
491
+ el.classList.add('active')
492
+ closeMenu()
493
+ })
494
+ })
495
+ }
496
+
497
+ // 插件列表
498
+ const pluginsSection = document.getElementById('menu-plugins-section')
499
+ const pluginListEl = document.getElementById('menu-plugin-list')
500
+ const plugins = getAllPlugins()
501
+ if (pluginsSection && pluginListEl && plugins.length > 0) {
502
+ pluginsSection.style.display = ''
503
+ pluginListEl.innerHTML = plugins.map((p) =>
504
+ `<div class="menu-plugin-item" data-plugin-id="${p.id}"><span>${p.name}</span><div class="menu-plugin-toggle${p.enabled ? ' on' : ''}"></div></div>`
505
+ ).join('')
506
+
507
+ pluginListEl.querySelectorAll('.menu-plugin-item').forEach((el) => {
508
+ el.addEventListener('click', () => {
509
+ const id = (el as HTMLElement).dataset.pluginId!
510
+ const p = getAllPlugins().find((x) => x.id === id)
511
+ if (p) {
512
+ togglePlugin(id, !p.enabled)
513
+ const mode = p.enabled ? 'rendered' : 'raw'
514
+ togglePluginMode(p.nodeTypes || [], mode)
515
+ api.syncPluginState(p.id, p.enabled)
516
+ const toggle = el.querySelector('.menu-plugin-toggle')
517
+ toggle?.classList.toggle('on', p.enabled)
518
+ }
519
+ })
520
+ })
521
+ }
522
+
523
+ // 菜单操作按钮
524
+ menuEl.querySelectorAll('.menu-item').forEach((el) => {
525
+ el.addEventListener('click', async () => {
526
+ const action = (el as HTMLElement).dataset.action
527
+ if (!action) return
528
+ closeMenu()
529
+
530
+ switch (action) {
531
+ case 'new':
532
+ exitSourceMode()
533
+ setMarkdown('')
534
+ break
535
+ case 'open': {
536
+ const result = await api.openFile()
537
+ if (result) setContent(result.content)
538
+ break
539
+ }
540
+ case 'save':
541
+ syncRawEdits()
542
+ {
543
+ const ok = await api.saveFile(getContent())
544
+ if (ok) {
545
+ showToast('Saved')
546
+ restoreRenderedMode(api)
547
+ } else {
548
+ showToast('Save failed')
549
+ }
550
+ }
551
+ break
552
+ case 'save-as':
553
+ syncRawEdits()
554
+ {
555
+ const ok = await api.saveFileAs(getContent())
556
+ if (ok) {
557
+ showToast('Saved')
558
+ restoreRenderedMode(api)
559
+ } else {
560
+ showToast('Save cancelled or failed')
561
+ }
562
+ }
563
+ break
564
+ case 'export-pdf':
565
+ syncRawEdits()
566
+ restoreRenderedMode(api)
567
+ await ensureAllPluginsRendered()
568
+ await new Promise(r => setTimeout(r, 300))
569
+ await api.exportPDF(buildExportHTML())
570
+ setTimeout(() => {
571
+ document.querySelectorAll('.mermaid-loading, .mermaid-error')
572
+ .forEach(el => (el as HTMLElement).style.display = 'none')
573
+ }, 1000)
574
+ break
575
+ case 'export-html':
576
+ syncRawEdits()
577
+ restoreRenderedMode(api)
578
+ await ensureAllPluginsRendered()
579
+ await new Promise(r => setTimeout(r, 200))
580
+ api.exportHTML(buildExportHTML())
581
+ break
582
+ case 'export-slides':
583
+ await api.exportSlides(getContent())
584
+ break
585
+ case 'new-slides': {
586
+ const template = await api.newSlides()
587
+ if (template) enterSourceMode(template)
588
+ break
589
+ }
590
+ case 'open-as-slides':
591
+ await api.openAsSlides(getContent())
592
+ break
593
+ case 'import-theme': {
594
+ const result = await api.loadCustomTheme()
595
+ if (result) {
596
+ setCachedCustomTheme(result.name, result.css)
597
+ applyTheme(`custom:${result.name}`)
598
+ }
599
+ break
600
+ }
601
+ case 'about':
602
+ api.openExternal('https://github.com/byteuser1977/ColaMD-extend')
603
+ break
604
+ case 'exit':
605
+ try {
606
+ const { App } = await import('@capacitor/app')
607
+ await App.exitApp()
608
+ } catch {
609
+ window.close()
610
+ }
611
+ break
612
+ }
613
+ })
614
+ })
615
+ }
616
+
617
+ /**
618
+ * 构建导出用的 HTML 字符串,包含当前主题样式。
619
+ * @returns 完整的 HTML 文档字符串
620
+ */
621
+ function buildExportHTML(): string {
622
+ const s = getComputedStyle(document.body)
623
+ const v = (name: string) => s.getPropertyValue(name).trim()
624
+ const bgColor = v('--bg-color')
625
+ const textColor = v('--text-color')
626
+ const textMuted = v('--text-muted')
627
+ const borderColor = v('--border-color')
628
+ const linkColor = v('--link-color')
629
+ const codeBg = v('--code-bg')
630
+ const codeBlockBg = v('--code-block-bg')
631
+ const codeBlockText = v('--code-block-text') || textColor
632
+ const mermaidBg = v('--mermaid-background') || bgColor
633
+ const blockquoteBorder = v('--blockquote-border')
634
+ const blockquoteBg = v('--blockquote-bg') || 'transparent'
635
+ const tableHeaderBg = v('--table-header-bg')
636
+ const selectionBg = v('--selection-bg')
637
+
638
+ const editor = document.querySelector('#editor .ProseMirror')
639
+ const fontFamily = editor ? getComputedStyle(editor).fontFamily : '-apple-system,BlinkMacSystemFont,sans-serif'
640
+
641
+ const getElColor = (selector: string, fallback: string): string => {
642
+ const el = document.querySelector(`#editor .ProseMirror ${selector}`)
643
+ return el ? getComputedStyle(el).color : fallback
644
+ }
645
+ const strongColor = getElColor('strong', textColor)
646
+ const codeColor = getElColor('code', textColor)
647
+
648
+ return `<!DOCTYPE html>
649
+ <html><head><meta charset="utf-8"><title>ColaMD Export</title>
650
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.46/dist/katex.min.css">
651
+ <style>
652
+ body{max-width:780px;margin:40px auto;padding:20px;font-family:${fontFamily};line-height:1.75;background:${bgColor};color:${textColor}}
653
+ h1{font-size:2em;font-weight:700;border-bottom:1px solid ${borderColor};padding-bottom:.3em}
654
+ h2{font-size:1.5em;font-weight:600;border-bottom:1px solid ${borderColor};padding-bottom:.25em}
655
+ h3{font-size:1.25em;font-weight:600}
656
+ strong{color:${strongColor}}
657
+ a{color:${linkColor};text-decoration:none}
658
+ code{background:${codeBg};color:${codeColor};padding:2px 6px;border-radius:3px;font-size:.875em;font-family:'SF Mono','Fira Code',Menlo,monospace}
659
+ pre{background:${codeBlockBg};color:${codeBlockText};padding:16px;border-radius:6px;overflow-x:auto;margin:1em 0}
660
+ pre code{background:none;padding:0;color:inherit}
661
+ blockquote{border-left:4px solid ${blockquoteBorder};background:${blockquoteBg};padding-left:16px;margin:1em 0;color:${textMuted}}
662
+ table{border-collapse:collapse;width:100%;margin:1em 0}
663
+ th,td{border:1px solid ${borderColor};padding:8px 12px}
664
+ th{background:${tableHeaderBg};font-weight:600}
665
+ hr{border:none;border-top:2px solid ${borderColor};margin:2em 0}
666
+ img{max-width:100%}
667
+ ::selection{background:${selectionBg}}
668
+ .math-inline{display:inline;padding:2px 4px;border-radius:3px;background:${codeBg}}
669
+ .math-block{display:block;padding:16px;margin:1em 0;border-radius:6px;background:${codeBlockBg};text-align:center;overflow-x:auto}
670
+ .mermaid-block{display:block;padding:16px;margin:1em 0;border-radius:6px;background:${mermaidBg};border:1px solid ${borderColor}}
671
+ .mermaid-preview{display:flex;justify-content:center;align-items:center}
672
+ .mermaid-preview svg{max-width:100%;height:auto}
673
+ @page{margin:15mm;size:A4}
674
+ @media print{body{max-width:none;margin:0;padding:20px}#editor{position:static!important;overflow:visible!important}}
675
+ </style>
676
+ </head><body>${getLiveHTML()}</body></html>`
677
+ }
303
678
  init().catch((e) => console.error('ColaMD init failed:', e))