diff --git a/desktop-app/neutralino.config.json b/desktop-app/neutralino.config.json index 6976fde..069bfd0 100644 --- a/desktop-app/neutralino.config.json +++ b/desktop-app/neutralino.config.json @@ -13,7 +13,16 @@ "enabled": true, "writeToLogFile": true }, - "nativeAllowList": ["app.*", "os.*", "filesystem.*", "debug.log"], + "nativeAllowList": [ + "app.exit", + "os.showOpenDialog", + "os.showSaveDialog", + "os.showMessageBox", + "os.open", + "os.setTray", + "filesystem.readFile", + "filesystem.writeFile" + ], "globalVariables": {}, "modes": { "window": { diff --git a/desktop-app/prepare.js b/desktop-app/prepare.js index 5d05411..278c076 100644 --- a/desktop-app/prepare.js +++ b/desktop-app/prepare.js @@ -1,86 +1,145 @@ -#!/usr/bin/env node - -/** - * prepare.js — Build script for the Neutralinojs desktop app. - * - * Copies shared browser-version files (script.js, styles.css, assets/) - * from the repo root into desktop-app/resources/, and generates a - * Neutralinojs-compatible index.html from the root index.html by - * injecting the required Neutralinojs script tags and wrapper elements. - * - * Run from the desktop-app/ directory: - * node prepare.js - */ - -const fs = require("fs"); -const path = require("path"); - -const ROOT_DIR = path.resolve(__dirname, ".."); -const RESOURCES_DIR = path.resolve(__dirname, "resources"); - -/** @section Copy shared files */ - -/** - * Recursively copy a directory, creating target dirs as needed. - */ -function copyDirSync(src, dest) { - fs.mkdirSync(dest, { recursive: true }); - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { - const srcPath = path.join(src, entry.name); - const destPath = path.join(dest, entry.name); - if (entry.isDirectory()) { - copyDirSync(srcPath, destPath); - } else { - fs.copyFileSync(srcPath, destPath); - } - } -} - -/** script.js → resources/js/script.js */ -const jsDest = path.join(RESOURCES_DIR, "js"); -fs.mkdirSync(jsDest, { recursive: true }); -fs.copyFileSync( - path.join(ROOT_DIR, "script.js"), - path.join(jsDest, "script.js"), -); -console.log("✓ Copied script.js → resources/js/script.js"); - -/** styles.css → resources/styles.css */ -fs.copyFileSync( - path.join(ROOT_DIR, "styles.css"), - path.join(RESOURCES_DIR, "styles.css"), -); -console.log("✓ Copied styles.css → resources/styles.css"); - -/** assets/ → resources/assets/ */ -copyDirSync(path.join(ROOT_DIR, "assets"), path.join(RESOURCES_DIR, "assets")); -console.log("✓ Copied assets/ → resources/assets/"); - -/** @section Generate index.html with Neutralinojs injections */ - -let html = fs.readFileSync(path.join(ROOT_DIR, "index.html"), "utf-8"); - -/** Fix relative asset paths → absolute (Neutralinojs documentRoot is /resources/) */ -html = html.replace(/href="assets\//g, 'href="/assets/'); -html = html.replace(/href="styles\.css"/g, 'href="/styles.css"'); -/** Replace root script.js tag with neutralino.js + main.js + script.js under /js/ */ -html = html.replace( - /<\/script>/i, - '\n \n ', -); - -/** Inject Neutralinojs app-info element after .app-container */ -html = html.replace( - '
', - `
-
-
-
`, -); - -fs.writeFileSync(path.join(RESOURCES_DIR, "index.html"), html, "utf-8"); -console.log( - "✓ Generated resources/index.html (Neutralinojs injections applied)", -); - -console.log("\nDone! Run `npm run dev` to start the desktop app."); +#!/usr/bin/env node + +/** + * prepare.js — Build script for the Neutralinojs desktop app. + * + * Copies shared browser-version files (script.js, styles.css, assets/) + * from the repo root into desktop-app/resources/, downloads all remote CDN + * libraries locally for 100% offline capabilities, and generates a + * Neutralinojs-compatible index.html. + */ + +const fs = require("fs"); +const path = require("path"); +const https = require("https"); + +const ROOT_DIR = path.resolve(__dirname, ".."); +const RESOURCES_DIR = path.resolve(__dirname, "resources"); +const jsDest = path.join(RESOURCES_DIR, "js"); +const LIBS_DIR = path.join(RESOURCES_DIR, "libs"); + +// Create directories +fs.mkdirSync(jsDest, { recursive: true }); +fs.mkdirSync(LIBS_DIR, { recursive: true }); + +function copyDirSync(src, dest) { + fs.mkdirSync(dest, { recursive: true }); + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + if (entry.isDirectory()) { + copyDirSync(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } +} + +// Copy shared assets +fs.copyFileSync(path.join(ROOT_DIR, "script.js"), path.join(jsDest, "script.js")); +console.log("✓ Copied script.js → resources/js/script.js"); + +fs.copyFileSync(path.join(ROOT_DIR, "styles.css"), path.join(RESOURCES_DIR, "styles.css")); +console.log("✓ Copied styles.css → resources/styles.css"); + +copyDirSync(path.join(ROOT_DIR, "assets"), path.join(RESOURCES_DIR, "assets")); +console.log("✓ Copied assets/ → resources/assets/"); + +// Download helper +function downloadFile(url, destPath) { + return new Promise((resolve, reject) => { + if (fs.existsSync(destPath) && fs.statSync(destPath).size > 0) { + resolve(); + return; + } + console.log(`Downloading offline dependency: ${path.basename(destPath)}...`); + https.get(url, (res) => { + if (res.statusCode !== 200) { + reject(new Error(`Failed to load ${url} (${res.statusCode})`)); + return; + } + const stream = fs.createWriteStream(destPath); + res.pipe(stream); + stream.on("finish", () => { + stream.close(); + resolve(); + }); + }).on("error", reject); + }); +} + +async function prepareOfflineDependencies() { + console.log("\nStarting Offline Assets Preparation..."); + let html = fs.readFileSync(path.join(ROOT_DIR, "index.html"), "utf-8"); + + // Find all CDN script and link tags + const cdnRegex = /(href|src)="(https:\/\/(?:cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net)\/[^"]+)"/g; + let match; + const downloads = []; + const replacements = []; + + while ((match = cdnRegex.exec(html)) !== null) { + const attr = match[1]; + const url = match[2]; + + // Determine local filename - sanitize package version tags or query strings + const urlPath = new URL(url).pathname; + let filename = path.basename(urlPath); + if (url.includes("bootstrap-icons")) { + filename = "bootstrap-icons.min.css"; + } + + const localDest = path.join(LIBS_DIR, filename); + downloads.push(downloadFile(url, localDest)); + + // Queue replacement in HTML to point to local libs folder + replacements.push({ + original: `${attr}="${url}"`, + replaced: `${attr}="/libs/${filename}"` + }); + } + + // Also download the relative fonts loaded by bootstrap-icons + const fontDir = path.join(LIBS_DIR, "fonts"); + fs.mkdirSync(fontDir, { recursive: true }); + downloads.push(downloadFile("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff2", path.join(fontDir, "bootstrap-icons.woff2"))); + downloads.push(downloadFile("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff", path.join(fontDir, "bootstrap-icons.woff"))); + + // Wait for all downloads to finish + try { + await Promise.all(downloads); + console.log("✓ All offline libraries successfully prepared."); + } catch (err) { + console.warn("⚠ Failed to bundle some dependencies offline. Fallback to CDNs will occur.", err.message); + } + + // Apply replacements in HTML + replacements.forEach(rep => { + html = html.replace(rep.original, rep.replaced); + }); + + // Fix relative assets + html = html.replace(/href="assets\//g, 'href="/assets/'); + html = html.replace(/href="styles\.css"/g, 'href="/styles.css"'); + + // Inject Neutralino script tags + html = html.replace( + /<\/script>/i, + '\n \n ', + ); + + // Inject app-info element + html = html.replace( + '
', + `
+
+
+
`, + ); + + fs.writeFileSync(path.join(RESOURCES_DIR, "index.html"), html, "utf-8"); + console.log("✓ Generated resources/index.html (Offline replacements & injections applied)"); + console.log("\nDone! Run `npm run dev` to start the desktop app."); +} + +prepareOfflineDependencies().catch(console.error); diff --git a/desktop-app/resources/index.html b/desktop-app/resources/index.html index a4ef16a..1e55d7f 100644 --- a/desktop-app/resources/index.html +++ b/desktop-app/resources/index.html @@ -29,22 +29,19 @@ Markdown Viewer - - - - - + + + + + - - - - - - - - + + + + + - - - - - + + + + + - - - - + + + +
-
-
-
+
+
+
@@ -315,7 +312,7 @@
Menu
-