From e469d51ba4e23ee735fd4ec295f7e4a660121cbe Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Mon, 6 Jul 2026 03:29:19 +1000 Subject: [PATCH] fix: improve navigation handler in main.js to better manage external links --- cinny | 2 +- electron/main.js | 34 ++++++++----- scripts/migrate-icons.mjs | 104 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 scripts/migrate-icons.mjs diff --git a/cinny b/cinny index 1f71fd7..09db721 160000 --- a/cinny +++ b/cinny @@ -1 +1 @@ -Subproject commit 1f71fd7a5116014636ccaf4d431e2ed42736c8c4 +Subproject commit 09db721b7e890a8bf41fd520ef2cbb4c291004ab diff --git a/electron/main.js b/electron/main.js index 1305a76..c643424 100644 --- a/electron/main.js +++ b/electron/main.js @@ -758,20 +758,28 @@ function createWindow() { return { action: 'allow' }; }); - // Navigation handler - open external links in browser + // Navigation handler - open external links in browser. + // Same-host navigations (e.g. Vite HMR full reload) must stay in-app; + // a hardcoded origin list breaks when the dev server port changes. mainWindow.webContents.on('will-navigate', (event, url) => { - const allowedOrigins = [ - 'http://localhost:8080', - 'http://localhost:44548', - 'http://127.0.0.1:8080', - 'http://127.0.0.1:44548' - ]; - - const isAllowed = allowedOrigins.some(origin => url.startsWith(origin)) || - url.startsWith('blob:') || - url.startsWith('data:'); - - if (!isAllowed && (url.startsWith('http://') || url.startsWith('https://'))) { + if (url.startsWith('blob:') || url.startsWith('data:')) { + return; + } + + if (!url.startsWith('http://') && !url.startsWith('https://')) { + return; + } + + try { + const requestedHost = new URL(url).host; + const currentUrl = mainWindow.webContents.getURL(); + const currentHost = currentUrl ? new URL(currentUrl).host : ''; + + if (requestedHost && requestedHost !== currentHost) { + event.preventDefault(); + shell.openExternal(url); + } + } catch { event.preventDefault(); shell.openExternal(url); } diff --git a/scripts/migrate-icons.mjs b/scripts/migrate-icons.mjs new file mode 100644 index 0000000..28b24fd --- /dev/null +++ b/scripts/migrate-icons.mjs @@ -0,0 +1,104 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const srcRoot = path.resolve(__dirname, '../cinny/src'); +const iconsModule = path.resolve(srcRoot, 'app/components/icons'); + +const ICON_SYMBOLS = new Set(['Icon', 'Icons', 'IconName', 'IconSrc']); + +function walk(dir, files = []) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + walk(fullPath, files); + } else if (/\.(tsx?)$/.test(entry.name)) { + files.push(fullPath); + } + } + return files; +} + +function getIconsImportPath(filePath) { + const rel = path.relative(path.dirname(filePath), iconsModule).replace(/\\/g, '/'); + return rel.startsWith('.') ? rel : `./${rel}`; +} + +function splitFoldsImport(importClause) { + const match = importClause.match(/^\{([^}]+)\}(?:\s+as\s+\w+)?$/); + if (!match) return null; + + const specifiers = match[1] + .split(',') + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => { + const [local, imported] = part.split(/\s+as\s+/).map((s) => s.trim()); + return { local: local ?? imported, imported: imported ?? local }; + }); + + const iconSpecs = specifiers.filter((s) => ICON_SYMBOLS.has(s.imported)); + const foldsSpecs = specifiers.filter((s) => !ICON_SYMBOLS.has(s.imported)); + + if (iconSpecs.length === 0) return null; + + return { iconSpecs, foldsSpecs }; +} + +function migrateFile(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + const importRegex = /import\s+(\{[^}]+\})\s+from\s+['"]folds['"];?/g; + let changed = false; + const iconImports = new Map(); + + content = content.replace(importRegex, (fullMatch, importClause) => { + const split = splitFoldsImport(importClause); + if (!split) return fullMatch; + + changed = true; + for (const spec of split.iconSpecs) { + iconImports.set(spec.local, spec.imported); + } + + if (split.foldsSpecs.length === 0) { + return ''; + } + + const foldsImport = split.foldsSpecs.map((s) => (s.local === s.imported ? s.local : `${s.imported} as ${s.local}`)).join(', '); + return `import { ${foldsImport} } from 'folds';`; + }); + + if (!changed) return false; + + const iconsPath = getIconsImportPath(filePath); + const iconExportList = [...iconImports.entries()] + .map(([local, imported]) => (local === imported ? imported : `${imported} as ${local}`)) + .join(', '); + + const iconsImportLine = `import { ${iconExportList} } from '${iconsPath}';`; + + const foldsImportMatch = content.match(/^import\s+\{[^}]+\}\s+from\s+['"]folds['"];?\s*$/m); + if (foldsImportMatch) { + const insertAt = content.indexOf(foldsImportMatch[0]) + foldsImportMatch[0].length; + content = `${content.slice(0, insertAt)}\n${iconsImportLine}${content.slice(insertAt)}`; + } else { + content = `${iconsImportLine}\n${content}`; + } + + content = content.replace(/\n{3,}/g, '\n\n'); + fs.writeFileSync(filePath, content); + return true; +} + +const files = walk(srcRoot); +let migrated = 0; + +for (const file of files) { + if (file.includes(`${path.sep}components${path.sep}icons${path.sep}`)) continue; + if (migrateFile(file)) { + migrated += 1; + } +} + +console.log(`Migrated ${migrated} files.`);