@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.
- package/.trae/specs/optimize-theme-loading/checklist.md +20 -0
- package/.trae/specs/optimize-theme-loading/spec.md +103 -0
- package/.trae/specs/optimize-theme-loading/tasks.md +40 -0
- package/CHANGELOG.md +323 -0
- package/CLAUDE.md +56 -0
- package/README.md +422 -26
- package/README_CN.md +480 -28
- package/android/app/build/.npmkeep +0 -0
- package/android/app/build.gradle +76 -0
- package/android/app/capacitor.build.gradle +24 -0
- package/android/app/proguard-rules.pro +21 -0
- package/android/app/release.keystore +0 -0
- package/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
- package/android/app/src/main/AndroidManifest.xml +64 -0
- package/android/app/src/main/java/cn/bytechain/colamd/MainActivity.java +180 -0
- package/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
- package/android/app/src/main/res/drawable/splash.png +0 -0
- package/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
- package/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
- package/android/app/src/main/res/layout/activity_main.xml +12 -0
- package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
- package/android/app/src/main/res/values/strings.xml +7 -0
- package/android/app/src/main/res/values/styles.xml +22 -0
- package/android/app/src/main/res/xml/file_paths.xml +5 -0
- package/android/app/src/main/res/xml/network_security_config.xml +8 -0
- package/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
- package/android/build.gradle +29 -0
- package/android/capacitor.settings.gradle +21 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +22 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +92 -0
- package/android/settings.gradle +5 -0
- package/android/variables.gradle +16 -0
- package/bytechain.cn-colamd-1.5.1-beta.2.tgz +0 -0
- package/capacitor.config.js +29 -0
- package/capacitor.config.ts +30 -0
- package/demo.md +191 -484
- package/dist/main/index.js +77 -46
- package/dist/renderer/assets/{arc-tTbbM8LO.js → arc-CPdeInCG.js} +1 -1
- package/dist/renderer/assets/{architectureDiagram-3BPJPVTR-CEgYow6c.js → architectureDiagram-3BPJPVTR-BAbnaR9G.js} +4 -3
- package/dist/renderer/assets/{blockDiagram-GPEHLZMM-LHyVtPwW.js → blockDiagram-GPEHLZMM-CYSWjnJg.js} +5 -4
- package/dist/renderer/assets/{c4Diagram-AAUBKEIU-C1P1eJrf.js → c4Diagram-AAUBKEIU-Rb1tstnr.js} +3 -2
- package/dist/renderer/assets/{channel-upve91Tq.js → channel-DpG2A6fE.js} +1 -1
- package/dist/renderer/assets/{chunk-2J33WTMH-lag2vhq9.js → chunk-2J33WTMH-DFc0Jxy_.js} +1 -1
- package/dist/renderer/assets/{chunk-4BX2VUAB-BXJ8Ggh-.js → chunk-4BX2VUAB-BhRxDTNn.js} +1 -1
- package/dist/renderer/assets/{chunk-55IACEB6-CiBpxRa1.js → chunk-55IACEB6-DEgMVBk8.js} +1 -1
- package/dist/renderer/assets/{chunk-727SXJPM-ODeKQFXC.js → chunk-727SXJPM-bjBIfiz8.js} +5 -5
- package/dist/renderer/assets/{chunk-AQP2D5EJ-BK7xJolB.js → chunk-AQP2D5EJ-DwQMzTzD.js} +3 -3
- package/dist/renderer/assets/{chunk-FMBD7UC4-BxpCZPtz.js → chunk-FMBD7UC4-CkphwJzs.js} +1 -1
- package/dist/renderer/assets/{chunk-ND2GUHAM-CqqaU9Ue.js → chunk-ND2GUHAM-oU09z4y4.js} +1 -1
- package/dist/renderer/assets/{chunk-QZHKN3VN-Biq_K124.js → chunk-QZHKN3VN-rCbVuPBn.js} +1 -1
- package/dist/renderer/assets/{classDiagram-v2-Q7XG4LA2-Cq95X99o.js → classDiagram-4FO5ZUOK-DGS2faoM.js} +7 -6
- package/dist/renderer/assets/{classDiagram-4FO5ZUOK-Cq95X99o.js → classDiagram-v2-Q7XG4LA2-DGS2faoM.js} +7 -6
- package/dist/renderer/assets/{cose-bilkent-S5V4N54A-XasiD0bu.js → cose-bilkent-S5V4N54A-iqY6-EwA.js} +2 -1
- package/dist/renderer/assets/{dagre-BM42HDAG-Nq84Gfx4.js → dagre-BM42HDAG-5t3X5sDa.js} +4 -3
- package/dist/renderer/assets/{diagram-2AECGRRQ-DwuB1GWt.js → diagram-2AECGRRQ-DzHiYDPh.js} +4 -3
- package/dist/renderer/assets/{diagram-5GNKFQAL-C2tgeI1h.js → diagram-5GNKFQAL-BiNv6Keq.js} +5 -4
- package/dist/renderer/assets/{diagram-KO2AKTUF-D5KzjNBc.js → diagram-KO2AKTUF-ClzeDG6f.js} +4 -3
- package/dist/renderer/assets/{diagram-LMA3HP47-C12xHS1c.js → diagram-LMA3HP47-CGkw7wII.js} +4 -3
- package/dist/renderer/assets/{diagram-OG6HWLK6-CnxI9oEa.js → diagram-OG6HWLK6-Dl-Hyk1_.js} +5 -4
- package/dist/renderer/assets/{erDiagram-TEJ5UH35-D_uPaKwn.js → erDiagram-TEJ5UH35-BxUN79Qb.js} +5 -4
- package/dist/renderer/assets/{flowDiagram-I6XJVG4X-B6q_1-tE.js → flowDiagram-I6XJVG4X-CzFk-KNI.js} +7 -6
- package/dist/renderer/assets/{ganttDiagram-6RSMTGT7-CFo7ifF9.js → ganttDiagram-6RSMTGT7-C2xl6Igx.js} +3 -2
- package/dist/renderer/assets/{gitGraphDiagram-PVQCEYII-WSexHTnq.js → gitGraphDiagram-PVQCEYII-_fn7XCa7.js} +5 -4
- package/dist/renderer/assets/{graph-DyX_9f6d.js → graph-CDoHYrHm.js} +1 -1
- package/dist/renderer/assets/index-B4uDgADr.js +530 -0
- package/dist/renderer/assets/index-CBcVpA3d.js +30 -0
- package/dist/renderer/assets/index-CGj1spkU.js +27 -0
- package/dist/renderer/assets/{index-dyHEFYvY.css → index-CeFpoCKV.css} +443 -400
- package/dist/renderer/assets/index-D4CPFkph.js +9 -0
- package/dist/renderer/assets/{index-DW7LS8C1.js → index-DAlXyxzt.js} +1183 -346
- package/dist/renderer/assets/index-DxOzbfR-.js +110 -0
- package/dist/renderer/assets/index-Y89U1ptl.js +9 -0
- package/dist/renderer/assets/{infoDiagram-5YYISTIA-DaeJdLRq.js → infoDiagram-5YYISTIA-DL6XIxLz.js} +3 -2
- package/dist/renderer/assets/{ishikawaDiagram-YF4QCWOH-DDCZc35f.js → ishikawaDiagram-YF4QCWOH-BUZLjRo-.js} +2 -1
- package/dist/renderer/assets/{journeyDiagram-JHISSGLW-BEdmpAgl.js → journeyDiagram-JHISSGLW-C4rH_mQM.js} +5 -4
- package/dist/renderer/assets/{kanban-definition-UN3LZRKU-BEFtQcFb.js → kanban-definition-UN3LZRKU-DRbrBcWV.js} +3 -2
- package/dist/renderer/assets/{layout-CAJgQHdw.js → layout-DZl4n4qu.js} +2 -2
- package/dist/renderer/assets/{linear-B2ggJ8Am.js → linear-B0Krxg21.js} +1 -1
- package/dist/renderer/assets/{mindmap-definition-RKZ34NQL-DSxVgHB5.js → mindmap-definition-RKZ34NQL-DdmPsWrn.js} +4 -3
- package/dist/renderer/assets/{pieDiagram-4H26LBE5-CwYoJBuL.js → pieDiagram-4H26LBE5-BPZLqwG0.js} +5 -4
- package/dist/renderer/assets/preload-helper-tXtZnHb0.js +88 -0
- package/dist/renderer/assets/{quadrantDiagram-W4KKPZXB-CST9Fvg9.js → quadrantDiagram-W4KKPZXB-Dr-oWRk9.js} +3 -2
- package/dist/renderer/assets/{requirementDiagram-4Y6WPE33-DtrH52jS.js → requirementDiagram-4Y6WPE33-B6QZd0lo.js} +4 -3
- package/dist/renderer/assets/{sankeyDiagram-5OEKKPKP-ca1tPzJ_.js → sankeyDiagram-5OEKKPKP-Cyl9ojEt.js} +2 -1
- package/dist/renderer/assets/{sequenceDiagram-3UESZ5HK-Dfp1EJZ7.js → sequenceDiagram-3UESZ5HK-D48Yr9T6.js} +4 -3
- package/dist/renderer/assets/{stateDiagram-AJRCARHV-Bha2QoNB.js → stateDiagram-AJRCARHV-qyb8ETsa.js} +7 -6
- package/dist/renderer/assets/{stateDiagram-v2-BHNVJYJU-DWgFUYu1.js → stateDiagram-v2-BHNVJYJU-DmDOyyrJ.js} +5 -4
- package/dist/renderer/assets/{timeline-definition-PNZ67QCA-C3h_-OTj.js → timeline-definition-PNZ67QCA-C-KQxTi1.js} +3 -2
- package/dist/renderer/assets/{vennDiagram-CIIHVFJN-DFzjSrZi.js → vennDiagram-CIIHVFJN-BdaZlnH-.js} +2 -1
- package/dist/renderer/assets/{wardley-L42UT6IY-Cx-VbqoS.js → wardley-L42UT6IY-b-_GPpqL.js} +1 -1
- package/dist/renderer/assets/{wardleyDiagram-YWT4CUSO-S2D9XqX6.js → wardleyDiagram-YWT4CUSO-B2hBE-EE.js} +4 -3
- package/dist/renderer/assets/web-BKE0SH0E.js +36 -0
- package/dist/renderer/assets/web-CBsFp24u.js +564 -0
- package/dist/renderer/assets/web-Dc8YgoHP.js +24 -0
- package/dist/renderer/assets/web-TfDzToU7.js +58 -0
- package/dist/renderer/assets/{xychartDiagram-2RQKCTM6-Cfxigbts.js → xychartDiagram-2RQKCTM6-CSvswDTY.js} +3 -2
- package/dist/renderer/index.html +62 -3
- package/docs/academic-demo.md +566 -0
- package/docs/demo.html +748 -0
- package/docs/demo.md +546 -0
- package/docs/demo.pdf +0 -0
- package/docs/theme-paradigm.md +658 -0
- package/electron-builder.yml +7 -0
- package/electron.vite.config.js +31 -0
- package/electron.vite.config.ts +1 -1
- package/ios/App/App/AppDelegate.swift +49 -0
- package/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png +0 -0
- package/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/ios/App/App/Assets.xcassets/Contents.json +6 -0
- package/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json +23 -0
- package/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png +0 -0
- package/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png +0 -0
- package/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png +0 -0
- package/ios/App/App/Base.lproj/LaunchScreen.storyboard +32 -0
- package/ios/App/App/Base.lproj/Main.storyboard +19 -0
- package/ios/App/App/Info.plist +49 -0
- package/ios/App/App.xcodeproj/project.pbxproj +408 -0
- package/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/App/Podfile +28 -0
- package/package.json +23 -3
- package/resources/templates/slides/template-forest-ink.html +540 -0
- package/scripts/generate-icons.js +102 -0
- package/src/main/index.ts +87 -63
- package/src/preload/index.d.ts +51 -0
- package/src/preload/index.js +70 -0
- package/src/renderer/capacitor-api.ts +713 -0
- package/src/renderer/editor/editor.ts +87 -4
- package/src/renderer/editor/plugins/index.ts +24 -32
- package/src/renderer/editor/plugins/math-plugin.ts +1 -1
- package/src/renderer/editor/plugins/mermaid-plugin-custom.css +13 -398
- package/src/renderer/editor/plugins/mermaid-plugin.ts +62 -71
- package/src/renderer/editor/plugins/themes/base/dark.css +23 -0
- package/src/renderer/editor/plugins/themes/base/elegant.css +32 -0
- package/src/renderer/editor/plugins/themes/base/light.css +20 -0
- package/src/renderer/editor/plugins/themes/base/newsprint.css +27 -0
- package/src/renderer/editor/plugins/themes/components/mermaid/academic.css +43 -0
- package/src/renderer/editor/plugins/themes/components/mermaid/dark.css +20 -0
- package/src/renderer/editor/plugins/themes/components/mermaid/elegant.css +24 -0
- package/src/renderer/editor/plugins/themes/components/mermaid/light.css +21 -0
- package/src/renderer/editor/plugins/themes/components/mermaid/newsprint.css +26 -0
- package/src/renderer/editor/plugins/themes/components/mermaid/variables.css +592 -0
- package/src/renderer/editor/plugins/themes/foundation.css +143 -0
- package/src/renderer/editor/plugins/themes/theme-manager.ts +92 -0
- package/src/renderer/env.d.ts +4 -1
- package/src/renderer/index.html +59 -1
- package/src/renderer/main.ts +432 -57
- package/src/renderer/mobile.css +429 -0
- package/themes/README.md +3 -0
- package/themes/academic-paper.css +1321 -0
- package/themes/elegant.css +14 -7
- package/themes/forest-ink.css +664 -0
- package/themes/pixso-design.css +1261 -0
- package/themes/swiss-design.css +596 -0
- package/themes/template.css +498 -0
- package/tsconfig.main.json +1 -0
- package/tsconfig.main.tsbuildinfo +1 -0
- package/tsconfig.preload.json +1 -0
- package/tsconfig.preload.tsbuildinfo +1 -0
- package/tsconfig.renderer.json +1 -0
- package/tsconfig.renderer.tsbuildinfo +1 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/.trae/documents/fix-mermaid-colors-and-sankey.md +0 -50
- package/src/renderer/themes/theme-manager.ts +0 -40
- /package/src/renderer/{themes → editor/plugins/themes}/base.css +0 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
const { Capacitor } = await import('@capacitor/core').catch(() => ({ Capacitor: { isNativePlatform: () => false } }))
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 懒加载 Capacitor 插件模块缓存。
|
|
5
|
+
* Electron 桌面环境不包含这些模块,仅在原生平台按需动态导入。
|
|
6
|
+
* 注意:必须使用 switch-case 中字符串字面量 import(),而不能用 import(name) 变量形式,
|
|
7
|
+
* 否则 Vite 无法在构建时静态分析,导致生产环境中插件模块加载失败。
|
|
8
|
+
*/
|
|
9
|
+
const _capModules: Record<string, any> = {}
|
|
10
|
+
async function capModule(name: string): Promise<any> {
|
|
11
|
+
if (!(name in _capModules)) {
|
|
12
|
+
try {
|
|
13
|
+
switch (name) {
|
|
14
|
+
case '@capacitor/filesystem': _capModules[name] = await import('@capacitor/filesystem'); break
|
|
15
|
+
case '@capacitor/share': _capModules[name] = await import('@capacitor/share'); break
|
|
16
|
+
case '@capacitor/app': _capModules[name] = await import('@capacitor/app'); break
|
|
17
|
+
case '@capawesome/capacitor-file-picker': _capModules[name] = await import('@capawesome/capacitor-file-picker'); break
|
|
18
|
+
case '@capacitor/haptics': _capModules[name] = await import('@capacitor/haptics'); break
|
|
19
|
+
default: _capModules[name] = null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch { _capModules[name] = null }
|
|
23
|
+
}
|
|
24
|
+
return _capModules[name]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 获取 @capacitor/filesystem 模块(含 Filesystem、Directory、Encoding)。
|
|
29
|
+
*/
|
|
30
|
+
async function capFS() { return capModule('@capacitor/filesystem') }
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取 @capacitor/share 模块。
|
|
34
|
+
*/
|
|
35
|
+
async function capShare() { return capModule('@capacitor/share') }
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 获取 @capawesome/capacitor-file-picker 模块。
|
|
39
|
+
*/
|
|
40
|
+
async function capPicker() { return capModule('@capawesome/capacitor-file-picker') }
|
|
41
|
+
|
|
42
|
+
export interface CapacitorBridgeAPI {
|
|
43
|
+
openFile: () => Promise<{ path: string; content: string } | null>
|
|
44
|
+
openFilePath: (path: string) => Promise<{ path: string; content: string } | null>
|
|
45
|
+
setCurrentFile: (path: string) => void
|
|
46
|
+
saveFile: (content: string) => Promise<boolean>
|
|
47
|
+
saveFileAs: (content: string) => Promise<boolean>
|
|
48
|
+
exportPDF: (htmlContent?: string) => Promise<boolean>
|
|
49
|
+
exportHTML: (htmlContent: string) => Promise<boolean>
|
|
50
|
+
newSlides: () => Promise<string | null>
|
|
51
|
+
openAsSlides: (content: string) => Promise<boolean>
|
|
52
|
+
loadCustomTheme: () => Promise<{ name: string; css: string } | null>
|
|
53
|
+
loadThemeCSS: (fileName: string) => Promise<string | null>
|
|
54
|
+
getPathForFile: (_file: File) => string
|
|
55
|
+
openExternal: (url: string) => void
|
|
56
|
+
onFileChanged: (callback: (content: string) => void) => void
|
|
57
|
+
onNewFile: (callback: () => void) => void
|
|
58
|
+
onFileOpened: (callback: (data: { path: string; content: string }) => void) => void
|
|
59
|
+
onMenuOpen: (callback: () => void) => void
|
|
60
|
+
onMenuSave: (callback: () => void) => void
|
|
61
|
+
onMenuSaveAs: (callback: () => void) => void
|
|
62
|
+
onMenuExportPDF: (callback: () => void) => void
|
|
63
|
+
onMenuExportHTML: (callback: () => void) => void
|
|
64
|
+
onMenuNewSlides: (callback: () => void) => void
|
|
65
|
+
onMenuOpenAsSlides: (callback: () => void) => void
|
|
66
|
+
onNewSlidesContent: (callback: (content: string) => void) => void
|
|
67
|
+
onSetTheme: (callback: (theme: string) => void) => void
|
|
68
|
+
onSetCustomCSS: (callback: (css: string) => void) => void
|
|
69
|
+
exportSlides: (content: string) => Promise<boolean>
|
|
70
|
+
onMenuExportSlides: (callback: () => void) => void
|
|
71
|
+
onAgentActivity: (callback: (state: string) => void) => void
|
|
72
|
+
registerPlugins: (plugins: Array<{ id: string; name: string; enabled: boolean }>) => Promise<boolean>
|
|
73
|
+
syncPluginState: (id: string, enabled: boolean) => Promise<void>
|
|
74
|
+
onMenuTogglePlugin: (callback: (id: string) => void) => void
|
|
75
|
+
onMenuImportTheme: (callback: () => void) => void
|
|
76
|
+
exportFile: (dataUrl: string, defaultName: string) => Promise<boolean>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let currentFilePath: string | null = null
|
|
80
|
+
const eventListeners: Record<string, Array<(...args: any[]) => void>> = {}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 将 base64 编码字符串安全解码为 UTF-8 文本。
|
|
84
|
+
*/
|
|
85
|
+
function decodeBase64UTF8(base64: string): string {
|
|
86
|
+
const binary = atob(base64)
|
|
87
|
+
const bytes = new Uint8Array(binary.length)
|
|
88
|
+
for (let i = 0; i < binary.length; i++) {
|
|
89
|
+
bytes[i] = binary.charCodeAt(i)
|
|
90
|
+
}
|
|
91
|
+
return new TextDecoder('utf-8').decode(bytes)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 写入文件并通过系统分享对话框分享。
|
|
96
|
+
*/
|
|
97
|
+
async function writeAndShareFile(fileName: string, content: string): Promise<boolean> {
|
|
98
|
+
try {
|
|
99
|
+
const fs = await capFS()
|
|
100
|
+
await fs.Filesystem.writeFile({
|
|
101
|
+
path: fileName,
|
|
102
|
+
data: content,
|
|
103
|
+
directory: fs.Directory.Documents,
|
|
104
|
+
encoding: fs.Encoding.UTF8,
|
|
105
|
+
recursive: true,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const fileUri = await fs.Filesystem.getUri({
|
|
109
|
+
path: fileName,
|
|
110
|
+
directory: fs.Directory.Documents,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const sh = await capShare()
|
|
114
|
+
try {
|
|
115
|
+
await sh.Share.share({
|
|
116
|
+
title: fileName,
|
|
117
|
+
text: 'ColaMD Export',
|
|
118
|
+
files: [fileUri.uri],
|
|
119
|
+
dialogTitle: 'Share Export',
|
|
120
|
+
})
|
|
121
|
+
} catch { /* share dialog cancelled */ }
|
|
122
|
+
return true
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error('writeAndShareFile failed:', err)
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function emit(event: string, ...args: any[]): void {
|
|
130
|
+
const listeners = eventListeners[event] || []
|
|
131
|
+
listeners.forEach((fn) => fn(...args))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isNativePlatform(): boolean {
|
|
135
|
+
return Capacitor.isNativePlatform()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getBaseName(path: string): string {
|
|
139
|
+
const parts = path.split(/[/\\]/)
|
|
140
|
+
return parts[parts.length - 1] || 'Untitled'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 从 markdown 内容中提取第一个标题作为文件名。
|
|
145
|
+
* 如果没有标题则使用默认名称。
|
|
146
|
+
*/
|
|
147
|
+
function generateFilename(content: string, ext: string): string {
|
|
148
|
+
const match = content.match(/^#\s+(.+)$/m)
|
|
149
|
+
if (match) {
|
|
150
|
+
const title = match[1].replace(/[\\/:*?"<>|]/g, '').trim().slice(0, 50)
|
|
151
|
+
if (title) return `${title}.${ext}`
|
|
152
|
+
}
|
|
153
|
+
const ts = Date.now()
|
|
154
|
+
return `untitled_${ts}.${ext}`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 读取指定路径的文件内容。
|
|
159
|
+
*/
|
|
160
|
+
async function readFileContent(filePath: string): Promise<string> {
|
|
161
|
+
try {
|
|
162
|
+
const fs = await capFS()
|
|
163
|
+
const result = await fs.Filesystem.readFile({
|
|
164
|
+
path: filePath,
|
|
165
|
+
directory: fs.Directory.Documents,
|
|
166
|
+
encoding: fs.Encoding.UTF8,
|
|
167
|
+
})
|
|
168
|
+
return result.data as string
|
|
169
|
+
} catch {
|
|
170
|
+
return ''
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 将内容写入指定路径的文件。
|
|
176
|
+
*/
|
|
177
|
+
async function writeFileContent(filePath: string, content: string): Promise<boolean> {
|
|
178
|
+
try {
|
|
179
|
+
const fs = await capFS()
|
|
180
|
+
await fs.Filesystem.writeFile({
|
|
181
|
+
path: filePath,
|
|
182
|
+
data: content,
|
|
183
|
+
directory: fs.Directory.Documents,
|
|
184
|
+
encoding: fs.Encoding.UTF8,
|
|
185
|
+
recursive: true,
|
|
186
|
+
})
|
|
187
|
+
return true
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error('writeFileContent failed:', err)
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 触发原生平台触觉反馈。
|
|
196
|
+
* 使用已安装的 @capacitor/haptics 插件。
|
|
197
|
+
*/
|
|
198
|
+
async function triggerHaptic(style: 'light' | 'medium' | 'heavy' = 'light'): Promise<void> {
|
|
199
|
+
if (!isNativePlatform()) return
|
|
200
|
+
try {
|
|
201
|
+
const { Haptics } = await import('@capacitor/haptics')
|
|
202
|
+
if (style === 'heavy') await Haptics.impact({ style: 'HEAVY' as any })
|
|
203
|
+
else if (style === 'medium') await Haptics.impact({ style: 'MEDIUM' as any })
|
|
204
|
+
else await Haptics.impact({ style: 'LIGHT' as any })
|
|
205
|
+
} catch { /* haptics not available */ }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 使用 FilePicker 插件选择并读取文件内容。
|
|
210
|
+
*/
|
|
211
|
+
async function pickAndReadFile(): Promise<{ path: string; content: string } | null> {
|
|
212
|
+
try {
|
|
213
|
+
const picker = await capPicker()
|
|
214
|
+
const result = await picker.FilePicker.pickFiles({
|
|
215
|
+
types: [
|
|
216
|
+
'text/markdown',
|
|
217
|
+
'text/plain',
|
|
218
|
+
'application/octet-stream',
|
|
219
|
+
'text/x-markdown',
|
|
220
|
+
],
|
|
221
|
+
multiple: false,
|
|
222
|
+
readData: true,
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
const file = result.files[0]
|
|
226
|
+
if (!file) return null
|
|
227
|
+
|
|
228
|
+
let content = ''
|
|
229
|
+
if (file.data) {
|
|
230
|
+
content = decodeBase64UTF8(file.data)
|
|
231
|
+
} else if (file.path) {
|
|
232
|
+
const fs = await capFS()
|
|
233
|
+
const readResult = await fs.Filesystem.readFile({
|
|
234
|
+
path: file.path,
|
|
235
|
+
encoding: fs.Encoding.UTF8,
|
|
236
|
+
})
|
|
237
|
+
content = readResult.data as string
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const filePath = file.path || file.name
|
|
241
|
+
currentFilePath = filePath
|
|
242
|
+
return { path: filePath, content }
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.warn('File picker cancelled or failed:', error)
|
|
245
|
+
return null
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 使用 FilePicker 插件选择 CSS 文件。
|
|
251
|
+
*/
|
|
252
|
+
async function pickCSSFile(): Promise<{ name: string; css: string } | null> {
|
|
253
|
+
try {
|
|
254
|
+
const picker = await capPicker()
|
|
255
|
+
const result = await picker.FilePicker.pickFiles({
|
|
256
|
+
types: ['text/css'],
|
|
257
|
+
multiple: false,
|
|
258
|
+
readData: true,
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const file = result.files[0]
|
|
262
|
+
if (!file) return null
|
|
263
|
+
|
|
264
|
+
let css = ''
|
|
265
|
+
if (file.data) {
|
|
266
|
+
css = decodeBase64UTF8(file.data)
|
|
267
|
+
} else if (file.path) {
|
|
268
|
+
const fs = await capFS()
|
|
269
|
+
const readResult = await fs.Filesystem.readFile({
|
|
270
|
+
path: file.path,
|
|
271
|
+
encoding: fs.Encoding.UTF8,
|
|
272
|
+
})
|
|
273
|
+
css = readResult.data as string
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { name: file.name, css }
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.warn('CSS file picker cancelled or failed:', error)
|
|
279
|
+
return null
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function createCapacitorAPI(): CapacitorBridgeAPI {
|
|
284
|
+
const api: CapacitorBridgeAPI = {
|
|
285
|
+
|
|
286
|
+
async openFile(): Promise<{ path: string; content: string } | null> {
|
|
287
|
+
if (!isNativePlatform()) {
|
|
288
|
+
const input = document.createElement('input')
|
|
289
|
+
input.type = 'file'
|
|
290
|
+
input.accept = '.md,.markdown,.mdown,.mkd,.txt'
|
|
291
|
+
return new Promise((resolve) => {
|
|
292
|
+
input.onchange = async () => {
|
|
293
|
+
const file = input.files?.[0]
|
|
294
|
+
if (!file) { resolve(null); return }
|
|
295
|
+
const content = await file.text()
|
|
296
|
+
currentFilePath = file.name
|
|
297
|
+
resolve({ path: file.name, content })
|
|
298
|
+
input.remove()
|
|
299
|
+
}
|
|
300
|
+
input.click()
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
return pickAndReadFile()
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
async openFilePath(path: string): Promise<{ path: string; content: string } | null> {
|
|
307
|
+
const content = await readFileContent(path)
|
|
308
|
+
if (!content && content !== '') return null
|
|
309
|
+
currentFilePath = path
|
|
310
|
+
return { path, content }
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
setCurrentFile(path: string): void {
|
|
314
|
+
currentFilePath = path
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
async saveFile(content: string): Promise<boolean> {
|
|
318
|
+
if (!currentFilePath) {
|
|
319
|
+
// First save: generate a filename and write directly.
|
|
320
|
+
// Don't call saveFileAs — its prompt() is unreliable on Android WebView.
|
|
321
|
+
const fileName = generateFilename(content, 'md')
|
|
322
|
+
const ok = await writeFileContent(fileName, content)
|
|
323
|
+
if (ok) {
|
|
324
|
+
currentFilePath = fileName
|
|
325
|
+
triggerHaptic('medium')
|
|
326
|
+
}
|
|
327
|
+
return ok
|
|
328
|
+
}
|
|
329
|
+
const ok = await writeFileContent(currentFilePath, content)
|
|
330
|
+
if (ok) triggerHaptic('light')
|
|
331
|
+
return ok
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
async saveFileAs(content: string): Promise<boolean> {
|
|
335
|
+
const baseName = currentFilePath
|
|
336
|
+
? getBaseName(currentFilePath).replace(/\.md$/, '')
|
|
337
|
+
: 'untitled'
|
|
338
|
+
const fileName = `${baseName}_${Date.now()}.md`
|
|
339
|
+
|
|
340
|
+
if (!isNativePlatform()) {
|
|
341
|
+
const blob = new Blob([content], { type: 'text/markdown' })
|
|
342
|
+
const url = URL.createObjectURL(blob)
|
|
343
|
+
const a = document.createElement('a')
|
|
344
|
+
a.href = url
|
|
345
|
+
a.download = fileName
|
|
346
|
+
a.click()
|
|
347
|
+
URL.revokeObjectURL(url)
|
|
348
|
+
currentFilePath = fileName
|
|
349
|
+
return true
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Android: write file then open Share sheet so user can save to preferred location
|
|
353
|
+
const ok = await writeFileContent(fileName, content)
|
|
354
|
+
if (!ok) {
|
|
355
|
+
triggerHaptic('heavy')
|
|
356
|
+
return false
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
currentFilePath = fileName
|
|
360
|
+
triggerHaptic('medium')
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const fs = await capFS()
|
|
364
|
+
const fileUri = await fs.Filesystem.getUri({
|
|
365
|
+
path: fileName,
|
|
366
|
+
directory: fs.Directory.Documents,
|
|
367
|
+
})
|
|
368
|
+
const sh = await capShare()
|
|
369
|
+
await sh.Share.share({
|
|
370
|
+
title: fileName,
|
|
371
|
+
text: 'ColaMD Document',
|
|
372
|
+
files: [fileUri.uri],
|
|
373
|
+
dialogTitle: 'Save As',
|
|
374
|
+
})
|
|
375
|
+
} catch { /* share cancelled — file already saved */ }
|
|
376
|
+
return true
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
async exportPDF(htmlContent?: string): Promise<boolean> {
|
|
380
|
+
if (!isNativePlatform()) {
|
|
381
|
+
window.print()
|
|
382
|
+
return true
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Android: use native PrintManager via ColaMDNative bridge.
|
|
386
|
+
// This opens the system print dialog with "Save as PDF" option
|
|
387
|
+
// and produces proper paginated PDF output.
|
|
388
|
+
const bridge = (window as any).ColaMDNative
|
|
389
|
+
if (bridge && typeof bridge.printDocument === 'function') {
|
|
390
|
+
// Temporarily unconstrain the fixed-position mobile layout so
|
|
391
|
+
// the full document is captured, not just the visible viewport.
|
|
392
|
+
const orig: { el: HTMLElement; cssText: string }[] = []
|
|
393
|
+
|
|
394
|
+
function saveAndOverride(selector: string, overrides: Record<string, string>): void {
|
|
395
|
+
const el = document.querySelector(selector) as HTMLElement | null
|
|
396
|
+
if (!el) return
|
|
397
|
+
orig.push({ el, cssText: el.style.cssText })
|
|
398
|
+
for (const [prop, value] of Object.entries(overrides)) {
|
|
399
|
+
;(el.style as any)[prop] = value
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Hide mobile UI chrome, unconstrain editor for full-document capture
|
|
404
|
+
saveAndOverride('#titlebar', { display: 'none' })
|
|
405
|
+
saveAndOverride('#mobile-menu', { display: 'none' })
|
|
406
|
+
saveAndOverride('#menu-btn', { display: 'none' })
|
|
407
|
+
saveAndOverride('.cola-toast', { display: 'none' })
|
|
408
|
+
saveAndOverride('html', { height: 'auto', overflow: 'visible' })
|
|
409
|
+
saveAndOverride('body', { height: 'auto', overflow: 'visible' })
|
|
410
|
+
saveAndOverride('#editor', {
|
|
411
|
+
position: 'static',
|
|
412
|
+
height: 'auto',
|
|
413
|
+
overflow: 'visible',
|
|
414
|
+
top: 'auto',
|
|
415
|
+
bottom: 'auto',
|
|
416
|
+
})
|
|
417
|
+
saveAndOverride('#editor .ProseMirror', { minHeight: 'auto' })
|
|
418
|
+
|
|
419
|
+
await new Promise(r => requestAnimationFrame(r))
|
|
420
|
+
|
|
421
|
+
const jobName = currentFilePath
|
|
422
|
+
? getBaseName(currentFilePath).replace(/\.(md|markdown)$/, '')
|
|
423
|
+
: 'ColaMD Document'
|
|
424
|
+
bridge.printDocument(jobName)
|
|
425
|
+
|
|
426
|
+
// Restore layout after print adapter captures the content.
|
|
427
|
+
// The 3s delay gives the Android print framework time to snapshot.
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
for (const { el, cssText } of orig) {
|
|
430
|
+
el.style.cssText = cssText
|
|
431
|
+
}
|
|
432
|
+
}, 3000)
|
|
433
|
+
|
|
434
|
+
return true
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Fallback: if native bridge unavailable, share as HTML file
|
|
438
|
+
const content = htmlContent || ''
|
|
439
|
+
if (!content) return false
|
|
440
|
+
const defaultName = currentFilePath
|
|
441
|
+
? getBaseName(currentFilePath).replace(/\.(md|markdown)$/, '-print.html')
|
|
442
|
+
: 'colamd-print.html'
|
|
443
|
+
return writeAndShareFile(defaultName, content)
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
async exportHTML(htmlContent: string): Promise<boolean> {
|
|
447
|
+
const defaultName = currentFilePath
|
|
448
|
+
? getBaseName(currentFilePath).replace(/\.(md|markdown)$/, '.html')
|
|
449
|
+
: 'colamd-export.html'
|
|
450
|
+
|
|
451
|
+
if (!isNativePlatform()) {
|
|
452
|
+
const blob = new Blob([htmlContent], { type: 'text/html' })
|
|
453
|
+
const url = URL.createObjectURL(blob)
|
|
454
|
+
const a = document.createElement('a')
|
|
455
|
+
a.href = url
|
|
456
|
+
a.download = defaultName
|
|
457
|
+
a.click()
|
|
458
|
+
URL.revokeObjectURL(url)
|
|
459
|
+
return true
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return writeAndShareFile(defaultName, htmlContent)
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
async newSlides(): Promise<string | null> {
|
|
466
|
+
const template = `---
|
|
467
|
+
kicker: ColaMD
|
|
468
|
+
chip: Markdown Editor · 2026
|
|
469
|
+
page: Your Name
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
<!-- type: cover -->
|
|
473
|
+
# Welcome to ColaMD
|
|
474
|
+
Agent Native Markdown Editor
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
<!-- type: statement -->
|
|
479
|
+
## Start Writing
|
|
480
|
+
Edit this file in ColaMD and see your changes in real time.
|
|
481
|
+
`
|
|
482
|
+
return template
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
async openAsSlides(content: string): Promise<boolean> {
|
|
486
|
+
if (!isNativePlatform()) {
|
|
487
|
+
const newWindow = window.open('', '_blank')
|
|
488
|
+
if (newWindow) {
|
|
489
|
+
newWindow.document.write(`<pre>${content}</pre>`)
|
|
490
|
+
}
|
|
491
|
+
return !!newWindow
|
|
492
|
+
}
|
|
493
|
+
return writeAndShareFile('colamd-slides-preview.html', content)
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
async loadCustomTheme(): Promise<{ name: string; css: string } | null> {
|
|
497
|
+
if (!isNativePlatform()) {
|
|
498
|
+
const input = document.createElement('input')
|
|
499
|
+
input.type = 'file'
|
|
500
|
+
input.accept = '.css'
|
|
501
|
+
return new Promise((resolve) => {
|
|
502
|
+
input.onchange = async () => {
|
|
503
|
+
const file = input.files?.[0]
|
|
504
|
+
if (!file) { resolve(null); return }
|
|
505
|
+
const css = await file.text()
|
|
506
|
+
resolve({ name: file.name, css })
|
|
507
|
+
input.remove()
|
|
508
|
+
}
|
|
509
|
+
input.click()
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
return pickCSSFile()
|
|
513
|
+
},
|
|
514
|
+
|
|
515
|
+
async loadThemeCSS(_fileName: string): Promise<string | null> {
|
|
516
|
+
try {
|
|
517
|
+
const fs = await capFS()
|
|
518
|
+
const result = await fs.Filesystem.readFile({
|
|
519
|
+
path: `.colamd/themes/${_fileName}`,
|
|
520
|
+
directory: fs.Directory.Documents,
|
|
521
|
+
encoding: fs.Encoding.UTF8,
|
|
522
|
+
})
|
|
523
|
+
return result.data as string
|
|
524
|
+
} catch {
|
|
525
|
+
return null
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
getPathForFile(_file: File): string {
|
|
530
|
+
return _file.name || ''
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
openExternal(url: string): void {
|
|
534
|
+
if (isNativePlatform()) {
|
|
535
|
+
window.open(url, '_system', 'location=yes')
|
|
536
|
+
} else {
|
|
537
|
+
window.open(url, '_blank')
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
onFileChanged(callback: (content: string) => void): void {
|
|
542
|
+
eventListeners['file-changed'] = eventListeners['file-changed'] || []
|
|
543
|
+
eventListeners['file-changed'].push(callback)
|
|
544
|
+
|
|
545
|
+
if (!isNativePlatform()) return
|
|
546
|
+
let lastContent = ''
|
|
547
|
+
let watcherTimer: ReturnType<typeof setInterval> | null = null
|
|
548
|
+
|
|
549
|
+
const pollChanges = async (): Promise<void> => {
|
|
550
|
+
if (!currentFilePath) return
|
|
551
|
+
try {
|
|
552
|
+
const content = await readFileContent(currentFilePath)
|
|
553
|
+
if (content !== lastContent && content) {
|
|
554
|
+
lastContent = content
|
|
555
|
+
emit('file-changed', content)
|
|
556
|
+
}
|
|
557
|
+
} catch { /* ignore */ }
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (!watcherTimer) {
|
|
561
|
+
watcherTimer = setInterval(pollChanges, 2000)
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
onNewFile(callback: () => void): void {
|
|
566
|
+
eventListeners['new-file'] = eventListeners['new-file'] || []
|
|
567
|
+
eventListeners['new-file'].push(callback)
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
onFileOpened(callback: (data: { path: string; content: string }) => void): void {
|
|
571
|
+
eventListeners['file-opened'] = eventListeners['file-opened'] || []
|
|
572
|
+
eventListeners['file-opened'].push(callback)
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
onMenuOpen(callback: () => void): void {
|
|
576
|
+
eventListeners['menu-open'] = eventListeners['menu-open'] || []
|
|
577
|
+
eventListeners['menu-open'].push(callback)
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
onMenuSave(callback: () => void): void {
|
|
581
|
+
eventListeners['menu-save'] = eventListeners['menu-save'] || []
|
|
582
|
+
eventListeners['menu-save'].push(callback)
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
onMenuSaveAs(callback: () => void): void {
|
|
586
|
+
eventListeners['menu-save-as'] = eventListeners['menu-save-as'] || []
|
|
587
|
+
eventListeners['menu-save-as'].push(callback)
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
onMenuExportPDF(callback: () => void): void {
|
|
591
|
+
eventListeners['menu-export-pdf'] = eventListeners['menu-export-pdf'] || []
|
|
592
|
+
eventListeners['menu-export-pdf'].push(callback)
|
|
593
|
+
},
|
|
594
|
+
|
|
595
|
+
onMenuExportHTML(callback: () => void): void {
|
|
596
|
+
eventListeners['menu-export-html'] = eventListeners['menu-export-html'] || []
|
|
597
|
+
eventListeners['menu-export-html'].push(callback)
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
onMenuNewSlides(callback: () => void): void {
|
|
601
|
+
eventListeners['menu-new-slides'] = eventListeners['menu-new-slides'] || []
|
|
602
|
+
eventListeners['menu-new-slides'].push(callback)
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
onMenuOpenAsSlides(callback: () => void): void {
|
|
606
|
+
eventListeners['menu-open-as-slides'] = eventListeners['menu-open-as-slides'] || []
|
|
607
|
+
eventListeners['menu-open-as-slides'].push(callback)
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
onNewSlidesContent(callback: (content: string) => void): void {
|
|
611
|
+
eventListeners['new-slides-content'] = eventListeners['new-slides-content'] || []
|
|
612
|
+
eventListeners['new-slides-content'].push(callback)
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
onSetTheme(callback: (theme: string) => void): void {
|
|
616
|
+
eventListeners['set-theme'] = eventListeners['set-theme'] || []
|
|
617
|
+
eventListeners['set-theme'].push(callback)
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
onSetCustomCSS(callback: (css: string) => void): void {
|
|
621
|
+
eventListeners['set-custom-css'] = eventListeners['set-custom-css'] || []
|
|
622
|
+
eventListeners['set-custom-css'].push(callback)
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
async exportSlides(content: string): Promise<boolean> {
|
|
626
|
+
const defaultName = 'colamd-slides.html'
|
|
627
|
+
if (!isNativePlatform()) {
|
|
628
|
+
const blob = new Blob([content], { type: 'text/html' })
|
|
629
|
+
const url = URL.createObjectURL(blob)
|
|
630
|
+
const a = document.createElement('a')
|
|
631
|
+
a.href = url
|
|
632
|
+
a.download = defaultName
|
|
633
|
+
a.click()
|
|
634
|
+
URL.revokeObjectURL(url)
|
|
635
|
+
return true
|
|
636
|
+
}
|
|
637
|
+
return writeAndShareFile(defaultName, content)
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
onMenuExportSlides(callback: () => void): void {
|
|
641
|
+
eventListeners['menu-export-slides'] = eventListeners['menu-export-slides'] || []
|
|
642
|
+
eventListeners['menu-export-slides'].push(callback)
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
onAgentActivity(callback: (state: string) => void): void {
|
|
646
|
+
eventListeners['agent-activity'] = eventListeners['agent-activity'] || []
|
|
647
|
+
eventListeners['agent-activity'].push(callback)
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
async registerPlugins(_plugins: Array<{ id: string; name: string; enabled: boolean }>): Promise<boolean> {
|
|
651
|
+
localStorage.setItem('colamd-plugins', JSON.stringify(_plugins))
|
|
652
|
+
return true
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
async syncPluginState(_id: string, _enabled: boolean): Promise<void> {
|
|
656
|
+
const raw = localStorage.getItem('colamd-plugins') || '[]'
|
|
657
|
+
const plugins = JSON.parse(raw)
|
|
658
|
+
const p = plugins.find((x: { id: string }) => x.id === _id)
|
|
659
|
+
if (p) p.enabled = _enabled
|
|
660
|
+
localStorage.setItem('colamd-plugins', JSON.stringify(plugins))
|
|
661
|
+
},
|
|
662
|
+
|
|
663
|
+
onMenuTogglePlugin(callback: (id: string) => void): void {
|
|
664
|
+
eventListeners['menu-toggle-plugin'] = eventListeners['menu-toggle-plugin'] || []
|
|
665
|
+
eventListeners['menu-toggle-plugin'].push(callback)
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
onMenuImportTheme(callback: () => void): void {
|
|
669
|
+
eventListeners['menu-import-theme'] = eventListeners['menu-import-theme'] || []
|
|
670
|
+
eventListeners['menu-import-theme'].push(callback)
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
async exportFile(dataUrl: string, defaultName: string): Promise<boolean> {
|
|
674
|
+
if (!isNativePlatform()) {
|
|
675
|
+
const a = document.createElement('a')
|
|
676
|
+
a.href = dataUrl
|
|
677
|
+
a.download = defaultName
|
|
678
|
+
a.click()
|
|
679
|
+
return true
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const base64Data = dataUrl.replace(/^data:[^;]+;base64,/, '')
|
|
683
|
+
try {
|
|
684
|
+
const fs = await capFS()
|
|
685
|
+
await fs.Filesystem.writeFile({
|
|
686
|
+
path: defaultName,
|
|
687
|
+
data: base64Data,
|
|
688
|
+
directory: fs.Directory.Documents,
|
|
689
|
+
recursive: true,
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
const fileUri = await fs.Filesystem.getUri({
|
|
693
|
+
path: defaultName,
|
|
694
|
+
directory: fs.Directory.Documents,
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
const sh = await capShare()
|
|
698
|
+
try {
|
|
699
|
+
await sh.Share.share({
|
|
700
|
+
title: defaultName,
|
|
701
|
+
files: [fileUri.uri],
|
|
702
|
+
dialogTitle: 'Share Export',
|
|
703
|
+
})
|
|
704
|
+
} catch { /* cancelled */ }
|
|
705
|
+
return true
|
|
706
|
+
} catch {
|
|
707
|
+
return false
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return api
|
|
713
|
+
}
|