Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"private": true,
"main": "./out/main/index.js",
"author": {
"name": "Adi Bhanna",
"name": "Adib Hanna",
"email": "adibhanna@gmail.com"
},
"license": "MIT",
Expand Down Expand Up @@ -147,7 +147,7 @@
"AppImage",
"deb"
],
"maintainer": "Adi Bhanna <adibhanna@gmail.com>",
"maintainer": "Adib Hanna <adibhanna@gmail.com>",
"category": "Office"
}
}
Expand Down
82 changes: 81 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ function installAppMenu(): void {
{
label: 'Check for Updates…',
click: () => {
void checkForAppUpdates()
void runMenuUpdateCheck()
}
},
{ type: 'separator' },
Expand Down Expand Up @@ -878,6 +878,86 @@ function installAppMenu(): void {
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
}

async function runMenuUpdateCheck(): Promise<void> {
const parent = BrowserWindow.getFocusedWindow() ?? mainWindow ?? undefined
const showDialog = async (
options: Electron.MessageBoxOptions
): Promise<Electron.MessageBoxReturnValue> => {
return parent
? await dialog.showMessageBox(parent, options)
: await dialog.showMessageBox(options)
}
const state = await checkForAppUpdates()

if (state.phase === 'available') {
const { response } = await showDialog({
type: 'info',
buttons: ['Download Update', 'Later'],
defaultId: 0,
cancelId: 1,
title: 'ZenNotes Update Available',
message: `ZenNotes ${state.availableVersion ?? ''} is available.`,
detail: state.message
})
if (response === 0) {
void downloadAppUpdate()
await showDialog({
type: 'info',
buttons: ['OK'],
defaultId: 0,
title: 'Downloading Update',
message: `ZenNotes ${state.availableVersion ?? ''} is downloading in the background.`,
detail: 'Open Settings → About to track progress and install when the download finishes.'
})
}
return
}

if (state.phase === 'downloaded') {
const { response } = await showDialog({
type: 'info',
buttons: ['Install and Relaunch', 'Later'],
defaultId: 0,
cancelId: 1,
title: 'ZenNotes Update Ready',
message: `ZenNotes ${state.availableVersion ?? ''} is ready to install.`,
detail: state.message
})
if (response === 0) {
installAppUpdate()
}
return
}

if (state.phase === 'downloading' || state.phase === 'checking') {
await showDialog({
type: 'info',
buttons: ['OK'],
defaultId: 0,
title: 'ZenNotes Updates',
message: state.phase === 'checking' ? 'Checking for updates…' : 'Downloading update…',
detail: state.message
})
return
}

await showDialog({
type: state.phase === 'error' ? 'warning' : 'info',
buttons: ['OK'],
defaultId: 0,
title: 'ZenNotes Updates',
message:
state.phase === 'not-available'
? 'ZenNotes is up to date.'
: state.phase === 'unsupported'
? 'Update checks are unavailable.'
: state.phase === 'error'
? 'Could not check for updates.'
: 'ZenNotes Updates',
detail: state.message
})
}

app.whenReady().then(async () => {
protocol.handle(LOCAL_ASSET_SCHEME, async (request) => {
const abs = decodeLocalAssetRequestPath(request.url)
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/src/components/FloatingNoteApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ function loadFloatingPrefs(): FloatingPrefs {
vimMode: true,
livePreview: true,
themeId: DEFAULT_THEME_ID,
themeFamily: 'apple',
themeMode: 'auto',
themeFamily: 'gruvbox',
themeMode: 'dark',
editorFontSize: 16,
editorLineHeight: 1.7,
lineNumberMode: 'off',
Expand Down
52 changes: 49 additions & 3 deletions src/renderer/src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,26 @@ function formatBytes(bytes: number | null): string | null {
return `${rounded} ${unit}`
}

function formatReleaseNotesForDisplay(notes: string | null): string | null {
if (!notes) return null
const trimmed = notes.trim()
if (!trimmed) return null
if (!/[<&]/.test(trimmed)) return trimmed

try {
const parser = new DOMParser()
const doc = parser.parseFromString(trimmed, 'text/html')
const text = (doc.body.innerText || doc.body.textContent || '')
.replace(/\r\n?/g, '\n')
.replace(/[ \t]+\n/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.trim()
return text || trimmed
} catch {
return trimmed
}
}

export function SettingsModal(): JSX.Element {
const setSettingsOpen = useStore((s) => s.setSettingsOpen)
const vimMode = useStore((s) => s.vimMode)
Expand Down Expand Up @@ -243,7 +263,28 @@ export function SettingsModal(): JSX.Element {
}, [])

const triggerUpdateCheck = useCallback(() => {
void window.zen.checkForAppUpdates()
void window.zen.checkForAppUpdates().then(
(state) => {
if (state.phase === 'available') {
window.alert(
`ZenNotes ${state.availableVersion ?? ''} is available. Use “Download Update” to fetch it.`
)
return
}
if (state.phase === 'not-available') {
window.alert(state.message)
return
}
if (state.phase === 'unsupported' || state.phase === 'error') {
window.alert(state.message)
}
},
(error) => {
const message =
error instanceof Error ? error.message : 'Could not check for updates.'
window.alert(message)
}
)
}, [])

const triggerUpdateDownload = useCallback(() => {
Expand All @@ -254,6 +295,11 @@ export function SettingsModal(): JSX.Element {
void window.zen.installAppUpdate()
}, [])

const displayedReleaseNotes = useMemo(
() => formatReleaseNotesForDisplay(appUpdateState?.releaseNotes ?? null),
[appUpdateState?.releaseNotes]
)

// Family list — Apple is the default, followed by the other families.
const familyOptions = useMemo<{ id: ThemeFamily; label: string }[]>(
() => [
Expand Down Expand Up @@ -916,13 +962,13 @@ export function SettingsModal(): JSX.Element {
</div>
</div>
)}
{appUpdateState?.releaseNotes && (
{displayedReleaseNotes && (
<details className="mt-3 rounded-xl border border-paper-300/60 bg-paper-100/60 px-3 py-2.5">
<summary className="cursor-pointer text-xs font-medium uppercase tracking-[0.16em] text-ink-500">
Release notes
</summary>
<pre className="mt-2 whitespace-pre-wrap font-sans text-sm leading-6 text-ink-600">
{appUpdateState.releaseNotes}
{displayedReleaseNotes}
</pre>
</details>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/lib/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const THEMES: ThemeOption[] = [
{ id: 'tokyo-night-storm', label: 'Storm', family: 'tokyo-night', mode: 'dark' }
]

export const DEFAULT_THEME_ID = 'apple-light'
export const DEFAULT_THEME_ID = 'dark-hard'

export function findTheme(id: string): ThemeOption {
return THEMES.find((t) => t.id === id) ?? THEMES[1] // fallback: light-medium
Expand Down
18 changes: 9 additions & 9 deletions src/renderer/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,20 @@ const DEFAULT_PREFS: Prefs = {
ripgrepBinaryPath: null,
fzfBinaryPath: null,
livePreview: true,
tabsEnabled: false,
tabsEnabled: true,
themeId: DEFAULT_THEME_ID,
themeFamily: 'apple',
themeMode: 'auto',
themeFamily: 'gruvbox',
themeMode: 'dark',
editorFontSize: 16,
editorLineHeight: 1.7,
previewMaxWidth: 920,
lineNumberMode: 'off',
// Ship with SF Mono everywhere — gives the app a single, consistent
// typographic identity out of the box. Users can still change any of
// the three slots from Settings → Fonts.
interfaceFont: 'SF Mono',
textFont: 'SF Mono',
monoFont: 'SF Mono',
// Leave all font slots on the built-in "Default" path. That lets the
// shipped CSS fallbacks choose sensible system fonts on each machine
// instead of forcing a specific family that may not exist.
interfaceFont: null,
textFont: null,
monoFont: null,
sidebarWidth: 232,
noteListWidth: 300,
noteSortOrder: 'none',
Expand Down
Loading