@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,102 @@
|
|
|
1
|
+
const sharp = require('sharp');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const rootDir = path.join(__dirname, '..');
|
|
6
|
+
const svgPath = path.join(rootDir, 'resources', 'icon.svg');
|
|
7
|
+
const androidResPath = path.join(rootDir, 'android', 'app', 'src', 'main', 'res');
|
|
8
|
+
const iosAssetsPath = path.join(rootDir, 'ios', 'App', 'App', 'Assets.xcassets', 'AppIcon.appiconset');
|
|
9
|
+
|
|
10
|
+
const androidIcons = [
|
|
11
|
+
{ name: 'mipmap-mdpi/ic_launcher.png', size: 48 },
|
|
12
|
+
{ name: 'mipmap-mdpi/ic_launcher_round.png', size: 48 },
|
|
13
|
+
{ name: 'mipmap-hdpi/ic_launcher.png', size: 72 },
|
|
14
|
+
{ name: 'mipmap-hdpi/ic_launcher_round.png', size: 72 },
|
|
15
|
+
{ name: 'mipmap-xhdpi/ic_launcher.png', size: 96 },
|
|
16
|
+
{ name: 'mipmap-xhdpi/ic_launcher_round.png', size: 96 },
|
|
17
|
+
{ name: 'mipmap-xxhdpi/ic_launcher.png', size: 144 },
|
|
18
|
+
{ name: 'mipmap-xxhdpi/ic_launcher_round.png', size: 144 },
|
|
19
|
+
{ name: 'mipmap-xxxhdpi/ic_launcher.png', size: 192 },
|
|
20
|
+
{ name: 'mipmap-xxxhdpi/ic_launcher_round.png', size: 192 },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const androidForegroundIcons = [
|
|
24
|
+
{ name: 'mipmap-mdpi/ic_launcher_foreground.png', size: 108 },
|
|
25
|
+
{ name: 'mipmap-hdpi/ic_launcher_foreground.png', size: 162 },
|
|
26
|
+
{ name: 'mipmap-xhdpi/ic_launcher_foreground.png', size: 216 },
|
|
27
|
+
{ name: 'mipmap-xxhdpi/ic_launcher_foreground.png', size: 324 },
|
|
28
|
+
{ name: 'mipmap-xxxhdpi/ic_launcher_foreground.png', size: 432 },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const iosIcons = [
|
|
32
|
+
{ name: 'AppIcon-512@2x.png', size: 1024 },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
async function generateIcons() {
|
|
36
|
+
console.log('Generating icons from SVG...');
|
|
37
|
+
|
|
38
|
+
for (const icon of androidIcons) {
|
|
39
|
+
const outputPath = path.join(androidResPath, icon.name);
|
|
40
|
+
const dir = path.dirname(outputPath);
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(dir)) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await sharp(svgPath)
|
|
47
|
+
.resize(icon.size, icon.size)
|
|
48
|
+
.png()
|
|
49
|
+
.toFile(outputPath);
|
|
50
|
+
|
|
51
|
+
console.log(`Generated: ${icon.name} (${icon.size}x${icon.size})`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log('\nGenerating adaptive icon foreground layers...');
|
|
55
|
+
|
|
56
|
+
for (const icon of androidForegroundIcons) {
|
|
57
|
+
const outputPath = path.join(androidResPath, icon.name);
|
|
58
|
+
const dir = path.dirname(outputPath);
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(dir)) {
|
|
61
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const iconSize = Math.round(icon.size * 0.67);
|
|
65
|
+
const padding = Math.round((icon.size - iconSize) / 2);
|
|
66
|
+
|
|
67
|
+
await sharp(svgPath)
|
|
68
|
+
.resize(iconSize, iconSize)
|
|
69
|
+
.extend({
|
|
70
|
+
top: padding,
|
|
71
|
+
bottom: padding,
|
|
72
|
+
left: padding,
|
|
73
|
+
right: padding,
|
|
74
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
75
|
+
})
|
|
76
|
+
.png()
|
|
77
|
+
.toFile(outputPath);
|
|
78
|
+
|
|
79
|
+
console.log(`Generated: ${icon.name} (${icon.size}x${icon.size}, icon: ${iconSize}x${iconSize})`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log('\nGenerating iOS icons...');
|
|
83
|
+
|
|
84
|
+
for (const icon of iosIcons) {
|
|
85
|
+
const outputPath = path.join(iosAssetsPath, icon.name);
|
|
86
|
+
|
|
87
|
+
if (!fs.existsSync(iosAssetsPath)) {
|
|
88
|
+
fs.mkdirSync(iosAssetsPath, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await sharp(svgPath)
|
|
92
|
+
.resize(icon.size, icon.size)
|
|
93
|
+
.png()
|
|
94
|
+
.toFile(outputPath);
|
|
95
|
+
|
|
96
|
+
console.log(`Generated: ${icon.name} (${icon.size}x${icon.size})`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('\nDone!');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
generateIcons().catch(console.error);
|
package/src/main/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { app, BrowserWindow, ipcMain, dialog, Menu, shell } from 'electron'
|
|
2
2
|
import { join, basename, dirname, extname } from 'path'
|
|
3
|
-
import { readFile, writeFile, readdir, copyFile, mkdir } from 'fs/promises'
|
|
4
|
-
import {
|
|
3
|
+
import { readFile, writeFile, readdir, copyFile, mkdir, stat } from 'fs/promises'
|
|
4
|
+
import { existsSync, readFileSync } from 'fs'
|
|
5
5
|
import { IncomingMessage, ServerResponse } from 'http'
|
|
6
6
|
import { createServer as createHttpServer } from 'http'
|
|
7
7
|
|
|
@@ -14,19 +14,10 @@ function ensureThemesDir(): void {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async function scanCustomThemes(): Promise<string[]> {
|
|
18
|
-
try {
|
|
19
|
-
const files = await readdir(themesDir)
|
|
20
|
-
return files.filter(f => f.endsWith('.css')).sort()
|
|
21
|
-
} catch {
|
|
22
|
-
return []
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
17
|
// Per-window state
|
|
27
18
|
interface WindowState {
|
|
28
19
|
filePath: string | null
|
|
29
|
-
watcher:
|
|
20
|
+
watcher: ReturnType<typeof setInterval> | null
|
|
30
21
|
isInternalSave: boolean
|
|
31
22
|
debounceTimer: ReturnType<typeof setTimeout> | null
|
|
32
23
|
agentState: 'idle' | 'active' | 'cooldown'
|
|
@@ -110,7 +101,7 @@ function suggestFileName(win: BrowserWindow, content?: string): string | undefin
|
|
|
110
101
|
|
|
111
102
|
function stopWatching(state: WindowState): void {
|
|
112
103
|
if (state.watcher) {
|
|
113
|
-
state.watcher
|
|
104
|
+
clearInterval(state.watcher)
|
|
114
105
|
state.watcher = null
|
|
115
106
|
}
|
|
116
107
|
if (state.agentCooldownTimer) {
|
|
@@ -152,28 +143,36 @@ function watchFile(win: BrowserWindow, state: WindowState): void {
|
|
|
152
143
|
if (!state.filePath) return
|
|
153
144
|
stopWatching(state)
|
|
154
145
|
const filePath = state.filePath
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
146
|
+
let lastMtime = 0
|
|
147
|
+
|
|
148
|
+
stat(filePath).then(s => { lastMtime = s.mtimeMs }).catch(() => {})
|
|
149
|
+
|
|
150
|
+
state.watcher = setInterval(() => {
|
|
151
|
+
if (state.isInternalSave) return
|
|
152
|
+
stat(filePath).then(s => {
|
|
153
|
+
if (s.mtimeMs === lastMtime) return
|
|
154
|
+
lastMtime = s.mtimeMs
|
|
155
|
+
|
|
156
|
+
// Agent activity detection
|
|
157
|
+
const now = Date.now()
|
|
158
|
+
const gap = now - state.lastExternalChange
|
|
159
|
+
state.lastExternalChange = now
|
|
160
|
+
if (gap > 0 && gap < 2000) {
|
|
161
|
+
transitionAgentState(win, state, 'active')
|
|
162
|
+
} else if (state.agentState === 'active') {
|
|
163
|
+
transitionAgentState(win, state, 'active') // reset cooldown timer
|
|
164
|
+
}
|
|
167
165
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
if (state.debounceTimer) clearTimeout(state.debounceTimer)
|
|
167
|
+
state.debounceTimer = setTimeout(() => {
|
|
168
|
+
readFile(filePath, 'utf-8')
|
|
169
|
+
.then((data) => {
|
|
170
|
+
if (!win.isDestroyed()) win.webContents.send('file-changed', resolveImagePaths(data, filePath))
|
|
171
|
+
})
|
|
172
|
+
.catch(() => {})
|
|
173
|
+
}, 100)
|
|
174
|
+
}).catch(() => {})
|
|
175
|
+
}, 500)
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
// Rewrite relative image paths in markdown to absolute file:// URLs
|
|
@@ -382,8 +381,9 @@ ipcMain.handle('save-export-file', async (event, dataUrl: string, defaultName: s
|
|
|
382
381
|
ipcMain.handle('export-pdf', async (event) => {
|
|
383
382
|
const win = getWinFromEvent(event)
|
|
384
383
|
if (!win) return false
|
|
384
|
+
const defaultName = (suggestFileName(win) || 'colamd-print') + '.pdf'
|
|
385
385
|
const result = await dialog.showSaveDialog(win, {
|
|
386
|
-
defaultPath:
|
|
386
|
+
defaultPath: defaultName,
|
|
387
387
|
filters: [{ name: 'PDF', extensions: ['pdf'] }]
|
|
388
388
|
})
|
|
389
389
|
if (result.canceled || !result.filePath) return false
|
|
@@ -392,11 +392,13 @@ ipcMain.handle('export-pdf', async (event) => {
|
|
|
392
392
|
const cssKey = await win.webContents.insertCSS(
|
|
393
393
|
'html, body { height: auto !important; overflow: visible !important; } #titlebar { display: none !important; } #editor { height: auto !important; overflow: visible !important; } #editor .ProseMirror { min-height: auto !important; } .mermaid-error { display: none !important; } .mermaid-loading { display: none !important; } svg .error-icon, svg .error-text { display: none !important; } .math-inline-raw, .math-block-raw, textarea.mermaid-source { display: none !important; }'
|
|
394
394
|
)
|
|
395
|
-
const pdfData = await
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
395
|
+
const pdfData = await Promise.race([
|
|
396
|
+
win.webContents.printToPDF({
|
|
397
|
+
printBackground: true,
|
|
398
|
+
pageSize: 'A4'
|
|
399
|
+
}),
|
|
400
|
+
new Promise<Buffer>((_, reject) => setTimeout(() => reject(new Error('PDF timeout')), 30000))
|
|
401
|
+
])
|
|
400
402
|
await win.webContents.removeInsertedCSS(cssKey)
|
|
401
403
|
await writeFile(result.filePath, pdfData)
|
|
402
404
|
return true
|
|
@@ -408,8 +410,9 @@ ipcMain.handle('export-pdf', async (event) => {
|
|
|
408
410
|
ipcMain.handle('export-html', async (event, htmlContent: string) => {
|
|
409
411
|
const win = getWinFromEvent(event)
|
|
410
412
|
if (!win) return false
|
|
413
|
+
const defaultName = (suggestFileName(win) || 'colamd-export') + '.html'
|
|
411
414
|
const result = await dialog.showSaveDialog(win, {
|
|
412
|
-
defaultPath:
|
|
415
|
+
defaultPath: defaultName,
|
|
413
416
|
filters: [{ name: 'HTML', extensions: ['html'] }]
|
|
414
417
|
})
|
|
415
418
|
if (result.canceled || !result.filePath) return false
|
|
@@ -652,7 +655,8 @@ ipcMain.handle('load-custom-theme', async (event) => {
|
|
|
652
655
|
const destPath = join(themesDir, fileName)
|
|
653
656
|
await copyFile(srcPath, destPath)
|
|
654
657
|
const css = await readFile(destPath, 'utf-8')
|
|
655
|
-
|
|
658
|
+
invalidateThemeCache()
|
|
659
|
+
await buildMenu() // rebuild menu to include new theme
|
|
656
660
|
return { name: fileName, css }
|
|
657
661
|
} catch {
|
|
658
662
|
return null
|
|
@@ -677,18 +681,40 @@ ipcMain.handle('register-plugins', (_event, plugins: Array<{ id: string; name: s
|
|
|
677
681
|
checked: p.enabled,
|
|
678
682
|
click: () => sendToFocused('menu-toggle-plugin', p.id)
|
|
679
683
|
}))
|
|
680
|
-
buildMenu()
|
|
684
|
+
void buildMenu()
|
|
681
685
|
return true
|
|
682
686
|
})
|
|
683
687
|
|
|
684
688
|
ipcMain.handle('sync-plugin-state', (_event, id: string, enabled: boolean) => {
|
|
685
689
|
const item = pluginMenuItems.find((p: any) => p.id === id)
|
|
686
690
|
if (item) item.checked = enabled
|
|
687
|
-
buildMenu()
|
|
691
|
+
void buildMenu()
|
|
688
692
|
})
|
|
689
693
|
|
|
690
694
|
// Menu — targets the focused window
|
|
691
695
|
|
|
696
|
+
let cachedThemeFiles: string[] = []
|
|
697
|
+
let themeFilesValid = false
|
|
698
|
+
|
|
699
|
+
function invalidateThemeCache(): void {
|
|
700
|
+
themeFilesValid = false
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async function scanThemeFiles(): Promise<void> {
|
|
704
|
+
try {
|
|
705
|
+
const files = await readdir(themesDir)
|
|
706
|
+
cachedThemeFiles = files.filter(f => f.endsWith('.css')).sort()
|
|
707
|
+
} catch {
|
|
708
|
+
cachedThemeFiles = []
|
|
709
|
+
}
|
|
710
|
+
themeFilesValid = true
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async function getCachedThemeFiles(): Promise<string[]> {
|
|
714
|
+
if (!themeFilesValid) await scanThemeFiles()
|
|
715
|
+
return cachedThemeFiles
|
|
716
|
+
}
|
|
717
|
+
|
|
692
718
|
function getFocusedWindow(): BrowserWindow | null {
|
|
693
719
|
return BrowserWindow.getFocusedWindow()
|
|
694
720
|
}
|
|
@@ -698,26 +724,23 @@ function sendToFocused(channel: string, ...args: unknown[]): void {
|
|
|
698
724
|
if (win) win.webContents.send(channel, ...args)
|
|
699
725
|
}
|
|
700
726
|
|
|
701
|
-
function buildMenu(): void {
|
|
727
|
+
async function buildMenu(): Promise<void> {
|
|
702
728
|
const isMac = process.platform === 'darwin'
|
|
703
729
|
|
|
704
|
-
// Scan custom themes synchronously for menu building
|
|
705
730
|
const customThemeItems: Electron.MenuItemConstructorOptions[] = []
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}
|
|
720
|
-
} catch { /* themes dir may not exist yet */ }
|
|
731
|
+
const files = await getCachedThemeFiles()
|
|
732
|
+
for (const file of files) {
|
|
733
|
+
customThemeItems.push({
|
|
734
|
+
label: file.replace(/\.css$/, ''),
|
|
735
|
+
click: async () => {
|
|
736
|
+
try {
|
|
737
|
+
const css = await readFile(join(themesDir, file), 'utf-8')
|
|
738
|
+
sendToFocused('set-theme', `custom:${file}`)
|
|
739
|
+
sendToFocused('set-custom-css', css)
|
|
740
|
+
} catch { /* ignore */ }
|
|
741
|
+
}
|
|
742
|
+
})
|
|
743
|
+
}
|
|
721
744
|
|
|
722
745
|
const themeSubmenu: Electron.MenuItemConstructorOptions[] = [
|
|
723
746
|
{ label: 'Light', click: () => sendToFocused('set-theme', 'light') },
|
|
@@ -842,9 +865,10 @@ function buildMenu(): void {
|
|
|
842
865
|
|
|
843
866
|
// App lifecycle
|
|
844
867
|
|
|
845
|
-
app.whenReady().then(() => {
|
|
868
|
+
app.whenReady().then(async () => {
|
|
846
869
|
ensureThemesDir()
|
|
847
|
-
|
|
870
|
+
await scanThemeFiles()
|
|
871
|
+
await buildMenu()
|
|
848
872
|
|
|
849
873
|
// Check command line args for file paths
|
|
850
874
|
const args = process.argv.slice(app.isPackaged ? 1 : 2)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface ElectronAPI {
|
|
2
|
+
openFile: () => Promise<{
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
} | null>;
|
|
6
|
+
openFilePath: (path: string) => Promise<{
|
|
7
|
+
path: string;
|
|
8
|
+
content: string;
|
|
9
|
+
} | null>;
|
|
10
|
+
saveFile: (content: string) => Promise<boolean>;
|
|
11
|
+
saveFileAs: (content: string) => Promise<boolean>;
|
|
12
|
+
exportPDF: () => Promise<boolean>;
|
|
13
|
+
exportHTML: (html: string) => Promise<boolean>;
|
|
14
|
+
newSlides: () => Promise<string | null>;
|
|
15
|
+
openAsSlides: (content: string) => Promise<boolean>;
|
|
16
|
+
loadCustomTheme: () => Promise<{
|
|
17
|
+
name: string;
|
|
18
|
+
css: string;
|
|
19
|
+
} | null>;
|
|
20
|
+
loadThemeCSS: (fileName: string) => Promise<string | null>;
|
|
21
|
+
getPathForFile: (file: File) => string;
|
|
22
|
+
openExternal: (url: string) => void;
|
|
23
|
+
onFileChanged: (callback: (content: string) => void) => void;
|
|
24
|
+
onNewFile: (callback: () => void) => void;
|
|
25
|
+
onFileOpened: (callback: (data: {
|
|
26
|
+
path: string;
|
|
27
|
+
content: string;
|
|
28
|
+
}) => void) => void;
|
|
29
|
+
onMenuOpen: (callback: () => void) => void;
|
|
30
|
+
onMenuSave: (callback: () => void) => void;
|
|
31
|
+
onMenuSaveAs: (callback: () => void) => void;
|
|
32
|
+
onMenuExportPDF: (callback: () => void) => void;
|
|
33
|
+
onMenuExportHTML: (callback: () => void) => void;
|
|
34
|
+
onMenuNewSlides: (callback: () => void) => void;
|
|
35
|
+
onMenuOpenAsSlides: (callback: () => void) => void;
|
|
36
|
+
onNewSlidesContent: (callback: (content: string) => void) => void;
|
|
37
|
+
onSetTheme: (callback: (theme: string) => void) => void;
|
|
38
|
+
onSetCustomCSS: (callback: (css: string) => void) => void;
|
|
39
|
+
exportSlides: (content: string) => Promise<boolean>;
|
|
40
|
+
onMenuExportSlides: (callback: () => void) => void;
|
|
41
|
+
onAgentActivity: (callback: (state: string) => void) => void;
|
|
42
|
+
registerPlugins: (plugins: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
enabled: boolean;
|
|
46
|
+
}>) => Promise<boolean>;
|
|
47
|
+
syncPluginState: (id: string, enabled: boolean) => Promise<void>;
|
|
48
|
+
onMenuTogglePlugin: (callback: (id: string) => void) => void;
|
|
49
|
+
onMenuImportTheme: (callback: () => void) => void;
|
|
50
|
+
exportFile: (dataUrl: string, defaultName: string) => Promise<boolean>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer, webUtils } from 'electron';
|
|
2
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
3
|
+
openFile: () => ipcRenderer.invoke('open-file'),
|
|
4
|
+
openFilePath: (path) => ipcRenderer.invoke('open-file-path', path),
|
|
5
|
+
saveFile: (content) => ipcRenderer.invoke('save-file', content),
|
|
6
|
+
saveFileAs: (content) => ipcRenderer.invoke('save-file-as', content),
|
|
7
|
+
exportPDF: () => ipcRenderer.invoke('export-pdf'),
|
|
8
|
+
exportHTML: (html) => ipcRenderer.invoke('export-html', html),
|
|
9
|
+
exportSlides: (content) => ipcRenderer.invoke('export-slides', content),
|
|
10
|
+
newSlides: () => ipcRenderer.invoke('new-slides'),
|
|
11
|
+
openAsSlides: (content) => ipcRenderer.invoke('open-as-slides', content),
|
|
12
|
+
loadCustomTheme: () => ipcRenderer.invoke('load-custom-theme'),
|
|
13
|
+
loadThemeCSS: (fileName) => ipcRenderer.invoke('load-theme-css', fileName),
|
|
14
|
+
getPathForFile: (file) => webUtils.getPathForFile(file),
|
|
15
|
+
openExternal: (url) => ipcRenderer.send('open-external', url),
|
|
16
|
+
onFileChanged: (callback) => {
|
|
17
|
+
ipcRenderer.on('file-changed', (_event, content) => callback(content));
|
|
18
|
+
},
|
|
19
|
+
onNewFile: (callback) => {
|
|
20
|
+
ipcRenderer.on('new-file', () => callback());
|
|
21
|
+
},
|
|
22
|
+
onFileOpened: (callback) => {
|
|
23
|
+
ipcRenderer.on('file-opened', (_event, data) => callback(data));
|
|
24
|
+
},
|
|
25
|
+
onMenuOpen: (callback) => {
|
|
26
|
+
ipcRenderer.on('menu-open', () => callback());
|
|
27
|
+
},
|
|
28
|
+
onMenuSave: (callback) => {
|
|
29
|
+
ipcRenderer.on('menu-save', () => callback());
|
|
30
|
+
},
|
|
31
|
+
onMenuSaveAs: (callback) => {
|
|
32
|
+
ipcRenderer.on('menu-save-as', () => callback());
|
|
33
|
+
},
|
|
34
|
+
onMenuExportPDF: (callback) => {
|
|
35
|
+
ipcRenderer.on('menu-export-pdf', () => callback());
|
|
36
|
+
},
|
|
37
|
+
onMenuExportHTML: (callback) => {
|
|
38
|
+
ipcRenderer.on('menu-export-html', () => callback());
|
|
39
|
+
},
|
|
40
|
+
onMenuNewSlides: (callback) => {
|
|
41
|
+
ipcRenderer.on('menu-new-slides', () => callback());
|
|
42
|
+
},
|
|
43
|
+
onMenuOpenAsSlides: (callback) => {
|
|
44
|
+
ipcRenderer.on('menu-open-as-slides', () => callback());
|
|
45
|
+
},
|
|
46
|
+
onNewSlidesContent: (callback) => {
|
|
47
|
+
ipcRenderer.on('new-slides-content', (_event, content) => callback(content));
|
|
48
|
+
},
|
|
49
|
+
onSetTheme: (callback) => {
|
|
50
|
+
ipcRenderer.on('set-theme', (_event, theme) => callback(theme));
|
|
51
|
+
},
|
|
52
|
+
onSetCustomCSS: (callback) => {
|
|
53
|
+
ipcRenderer.on('set-custom-css', (_event, css) => callback(css));
|
|
54
|
+
},
|
|
55
|
+
onMenuImportTheme: (callback) => {
|
|
56
|
+
ipcRenderer.on('menu-import-theme', () => callback());
|
|
57
|
+
},
|
|
58
|
+
onMenuExportSlides: (callback) => {
|
|
59
|
+
ipcRenderer.on('menu-export-slides', () => callback());
|
|
60
|
+
},
|
|
61
|
+
onAgentActivity: (callback) => {
|
|
62
|
+
ipcRenderer.on('agent-activity', (_event, state) => callback(state));
|
|
63
|
+
},
|
|
64
|
+
registerPlugins: (plugins) => ipcRenderer.invoke('register-plugins', plugins),
|
|
65
|
+
syncPluginState: (id, enabled) => ipcRenderer.invoke('sync-plugin-state', id, enabled),
|
|
66
|
+
onMenuTogglePlugin: (callback) => {
|
|
67
|
+
ipcRenderer.on('menu-toggle-plugin', (_event, id) => callback(id));
|
|
68
|
+
},
|
|
69
|
+
exportFile: (dataUrl, defaultName) => ipcRenderer.invoke('save-export-file', dataUrl, defaultName),
|
|
70
|
+
});
|