Complete Phase E theming, onboarding, and Windows share integration

Adds theme presets (Toffee/Slate/Evergreen/Lavender) with a "follow system"
mode and high-contrast awareness, a first-run welcome screen, and the
Windows analog of Android's share sheet for a Win32 app: an aerofetch://
protocol handler plus an Explorer "Send to AeroFetch" entry, both routed
through a single-instance lock so a second launch hands its link to the
already-running window instead of opening a duplicate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 08:45:09 -04:00
parent a822a2bb52
commit fa78b13cac
17 changed files with 883 additions and 77 deletions
+85
View File
@@ -0,0 +1,85 @@
import { app, shell, type BrowserWindow } from 'electron'
import { existsSync, readFileSync } from 'fs'
import { join } from 'path'
/** Only ever forward http(s) targets into the app — same restriction the
* external-link window-open handler in index.ts applies to in-page links. */
function asHttpUrl(candidate: string): string | null {
try {
const u = new URL(candidate)
return u.protocol === 'http:' || u.protocol === 'https:' ? candidate : null
} catch {
return null
}
}
/** Reads a Windows Internet Shortcut (.url) file's target — what Explorer's
* "Send to" menu hands us when the user sends a saved link to AeroFetch. */
function readUrlShortcut(path: string): string | null {
try {
const text = readFileSync(path, 'utf8')
const match = /^URL=(.+)$/im.exec(text)
return match ? asHttpUrl(match[1].trim()) : null
} catch {
return null
}
}
/**
* Pulls a download target out of process argv — either the `aerofetch://`
* custom protocol (`aerofetch://download?url=<encoded>`) or a `.url` Internet
* Shortcut path Explorer's "Send to" menu passes us (see registerSendToShortcut).
*/
export function extractIncomingUrl(argv: string[]): string | null {
for (const arg of argv) {
if (arg.startsWith('aerofetch://')) {
try {
const target = new URL(arg).searchParams.get('url')
const valid = target && asHttpUrl(target)
if (valid) return valid
} catch {
/* malformed protocol invocation — ignore */
}
} else if (/\.url$/i.test(arg) && existsSync(arg)) {
const target = readUrlShortcut(arg)
if (target) return target
}
}
return null
}
/**
* Adds/refreshes a "Send to AeroFetch" entry in Explorer's SendTo menu — the
* closest Windows analog to Android's share sheet a Win32 (non-MSIX) app can
* offer; registering as an actual Share Target requires an MSIX package.
* Best-effort and idempotent (re-running just overwrites the same shortcut),
* so a failure here should never block startup.
*/
export function registerSendToShortcut(): void {
if (process.platform !== 'win32') return
try {
const shortcutPath = join(
app.getPath('appData'),
'Microsoft',
'Windows',
'SendTo',
'AeroFetch.lnk'
)
shell.writeShortcutLink(shortcutPath, {
target: process.execPath,
description: 'Send to AeroFetch',
icon: process.execPath,
iconIndex: 0
})
} catch {
/* SendTo integration is a nice-to-have */
}
}
/** Focuses (and un-minimizes) an existing window — used when a second launch
* (protocol or SendTo) should hand its URL to the already-running instance. */
export function focusWindow(win: BrowserWindow): void {
if (win.isMinimized()) win.restore()
win.show()
win.focus()
}