Skip to content
This repository was archived by the owner on Jun 15, 2026. It is now read-only.

Commit 16a0a7b

Browse files
committed
v0.2.10: update notification popup on startup
On launch, fetches latest release from GitHub and shows a toast in the bottom-right corner when a newer version is available. Dismiss is remembered per version (localStorage) so it only shows once per release.
1 parent fba17d4 commit 16a0a7b

5 files changed

Lines changed: 134 additions & 2 deletions

File tree

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "qnote"
3-
version = "0.2.9"
3+
version = "0.2.10"
44
description = "A minimal, beautiful notepad"
55
authors = ["Omi"]
66
edition = "2021"

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "qnote",
4-
"version": "0.2.9",
4+
"version": "0.2.10",
55
"identifier": "com.suicore.qnote",
66
"build": {
77
"beforeDevCommand": "pnpm dev",

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Editor } from "./components/Editor";
55
import { Settings } from "./components/Settings";
66
import { VersionHistory } from "./components/VersionHistory";
77
import { DebugConsole } from "./components/DebugConsole";
8+
import { UpdateToast } from "./components/UpdateToast";
89
import { useStore } from "./store/useStore";
910
import { api } from "./lib/api";
1011
import { openFile, saveFile, saveFileAs } from "./lib/fileOps";
@@ -93,6 +94,7 @@ export default function App() {
9394
</div>
9495
<Settings />
9596
<VersionHistory />
97+
<UpdateToast />
9698
</div>
9799
);
98100
}

src/components/UpdateToast.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useEffect, useState } from "react";
2+
import { getVersion } from "@tauri-apps/api/app";
3+
import { openUrl } from "@tauri-apps/plugin-opener";
4+
import { motion, AnimatePresence } from "framer-motion";
5+
import { X } from "lucide-react";
6+
7+
const DISMISS_KEY = "qnote_dismissed_update";
8+
const RELEASES_URL = "https://github.com/Omibranch/qnote/releases/latest";
9+
10+
export function UpdateToast() {
11+
const [installed, setInstalled] = useState("");
12+
const [latest, setLatest] = useState("");
13+
const [visible, setVisible] = useState(false);
14+
15+
useEffect(() => {
16+
getVersion().then((v) => {
17+
setInstalled(v);
18+
fetch("https://api.github.com/repos/Omibranch/qnote/releases/latest")
19+
.then((r) => r.json())
20+
.then((d) => {
21+
const tag = (d.tag_name as string).replace(/^v/, "");
22+
if (tag !== v && localStorage.getItem(DISMISS_KEY) !== tag) {
23+
setLatest(tag);
24+
setVisible(true);
25+
}
26+
})
27+
.catch(() => {});
28+
});
29+
}, []);
30+
31+
const dismiss = () => {
32+
localStorage.setItem(DISMISS_KEY, latest);
33+
setVisible(false);
34+
};
35+
36+
return (
37+
<AnimatePresence>
38+
{visible && (
39+
<motion.div
40+
className="update-toast"
41+
initial={{ opacity: 0, y: 16, scale: 0.96 }}
42+
animate={{ opacity: 1, y: 0, scale: 1 }}
43+
exit={{ opacity: 0, y: 16, scale: 0.96 }}
44+
transition={{ type: "spring", stiffness: 420, damping: 36 }}
45+
>
46+
<div className="update-toast-body">
47+
<span className="update-toast-title">Update available</span>
48+
<span className="update-toast-versions">{installed}{latest}</span>
49+
</div>
50+
<button
51+
className="update-toast-link"
52+
onClick={() => openUrl(RELEASES_URL)}
53+
>
54+
Release notes
55+
</button>
56+
<button className="icon-btn update-toast-close" onClick={dismiss}>
57+
<X size={14} />
58+
</button>
59+
</motion.div>
60+
)}
61+
</AnimatePresence>
62+
);
63+
}

src/index.css

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,3 +1340,70 @@ body {
13401340
.color-reset-btn:hover {
13411341
color: var(--text-2);
13421342
}
1343+
1344+
/* Update toast */
1345+
.update-toast {
1346+
position: fixed;
1347+
bottom: 20px;
1348+
right: 20px;
1349+
z-index: 9999;
1350+
display: flex;
1351+
align-items: center;
1352+
gap: 8px;
1353+
padding: 10px 8px 10px 14px;
1354+
background: var(--bg-elevated);
1355+
border: 1px solid var(--accent);
1356+
border-radius: var(--radius);
1357+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
1358+
max-width: 300px;
1359+
pointer-events: all;
1360+
}
1361+
1362+
.update-toast-body {
1363+
display: flex;
1364+
flex-direction: column;
1365+
gap: 3px;
1366+
flex: 1;
1367+
min-width: 0;
1368+
}
1369+
1370+
.update-toast-title {
1371+
font-size: 12.5px;
1372+
font-weight: 600;
1373+
color: var(--text);
1374+
}
1375+
1376+
.update-toast-versions {
1377+
font-size: 11.5px;
1378+
font-family: var(--font-mono);
1379+
color: var(--accent);
1380+
}
1381+
1382+
.update-toast-link {
1383+
font-size: 11.5px;
1384+
color: var(--accent);
1385+
background: none;
1386+
border: 1px solid transparent;
1387+
cursor: pointer;
1388+
padding: 4px 8px;
1389+
border-radius: var(--radius-sm);
1390+
white-space: nowrap;
1391+
font-family: var(--font-ui);
1392+
transition: background var(--t), border-color var(--t);
1393+
flex-shrink: 0;
1394+
}
1395+
1396+
.update-toast-link:hover {
1397+
background: var(--bg-elevated2);
1398+
border-color: var(--border-strong);
1399+
}
1400+
1401+
.update-toast-close {
1402+
flex-shrink: 0;
1403+
opacity: 0.6;
1404+
transition: opacity var(--t);
1405+
}
1406+
1407+
.update-toast-close:hover {
1408+
opacity: 1;
1409+
}

0 commit comments

Comments
 (0)