fix: improve navigation handler in main.js to better manage external links

This commit is contained in:
2026-07-06 03:29:19 +10:00
parent fdb2dca794
commit e469d51ba4
3 changed files with 126 additions and 14 deletions

2
cinny

Submodule cinny updated: 1f71fd7a51...09db721b7e

View File

@@ -758,20 +758,28 @@ function createWindow() {
return { action: 'allow' }; 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) => { mainWindow.webContents.on('will-navigate', (event, url) => {
const allowedOrigins = [ if (url.startsWith('blob:') || url.startsWith('data:')) {
'http://localhost:8080', return;
'http://localhost:44548', }
'http://127.0.0.1:8080',
'http://127.0.0.1:44548' if (!url.startsWith('http://') && !url.startsWith('https://')) {
]; return;
}
const isAllowed = allowedOrigins.some(origin => url.startsWith(origin)) ||
url.startsWith('blob:') || try {
url.startsWith('data:'); const requestedHost = new URL(url).host;
const currentUrl = mainWindow.webContents.getURL();
if (!isAllowed && (url.startsWith('http://') || url.startsWith('https://'))) { const currentHost = currentUrl ? new URL(currentUrl).host : '';
if (requestedHost && requestedHost !== currentHost) {
event.preventDefault();
shell.openExternal(url);
}
} catch {
event.preventDefault(); event.preventDefault();
shell.openExternal(url); shell.openExternal(url);
} }

104
scripts/migrate-icons.mjs Normal file
View File

@@ -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.`);