Skip to content

Commit 6e5f1f0

Browse files
committed
Add changelog
1 parent e457a15 commit 6e5f1f0

4 files changed

Lines changed: 112 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Changelog
2+
3+
## 2026-03-19 - Changelog added
4+
A changelog has been added to the app to keep you updated on new features and bug fixes.
5+
6+
## 2026-03-16 - Support for 0-results in aggregate questions
7+
Aggregate questions (e.g. COUNT) now also accept solutions that include rows with 0-results via OUTER JOIN or UNION. Both variants (with and without 0-rows) are accepted.
8+
9+
## 2026-03-11 - Multi-language support (Swedish & English)
10+
The validator now supports both Swedish and English. Select language in the menu above the schema.
11+
12+
## 2026-03-11 - Language check in save files
13+
Import/export of save files now checks that the language matches. A warning is shown if the save file was created with a different language.
14+
15+
## 2026-02-01 - Merge support for imports
16+
When importing save files you can now choose between overwriting or merging with existing answers.

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import initSqlJs from "sql.js";
3232
import ViewsTable, { View } from "./ViewsTable";
3333

3434
import { Info, Settings, XCircle, CheckCircle2, Eye, EyeOff } from "lucide-react";
35+
import ChangelogDialog from "./ChangelogDialog";
3536
import { Button } from "@/components/ui/button";
3637
import {
3738
DropdownMenu,
@@ -1034,6 +1035,7 @@ function App() {
10341035
<span>-</span>
10351036
<a href="https://github.com/Edwinexd/sql-validator?tab=GPL-3.0-1-ov-file" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">GPL-3.0</a>
10361037
<a href="https://github.com/Edwinexd/sql-validator/issues" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">Report Issue</a>
1038+
<ChangelogDialog />
10371039
<PrivacyNoticeToggle></PrivacyNoticeToggle>
10381040
</div>
10391041
</footer>

src/ChangelogDialog.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { useState, useEffect, useMemo } from "react";
2+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
3+
import { Button } from "@/components/ui/button";
4+
import { useLanguage } from "./i18n/context";
5+
import changelogRaw from "../CHANGELOG.md?raw";
6+
7+
interface ChangelogEntry {
8+
date: string;
9+
title: string;
10+
description: string;
11+
}
12+
13+
function parseChangelog(raw: string): ChangelogEntry[] {
14+
const entries: ChangelogEntry[] = [];
15+
for (const block of raw.split(/^## /m).slice(1)) {
16+
const [heading, ...rest] = block.split("\n");
17+
const match = heading.match(/^(\d{4}-\d{2}-\d{2})\s*-\s*(.+)$/);
18+
if (!match) continue;
19+
entries.push({
20+
date: match[1],
21+
title: match[2].trim(),
22+
description: rest.join("\n").trim(),
23+
});
24+
}
25+
return entries;
26+
}
27+
28+
const CHANGELOG_VERSION_KEY = "changelog-last-seen";
29+
30+
const ChangelogDialog = () => {
31+
const { t } = useLanguage();
32+
const [open, setOpen] = useState(false);
33+
const [hasNew, setHasNew] = useState(false);
34+
const entries = useMemo(() => parseChangelog(changelogRaw), []);
35+
36+
useEffect(() => {
37+
if (entries.length === 0) return;
38+
const lastSeen = localStorage.getItem(CHANGELOG_VERSION_KEY);
39+
if (!lastSeen) {
40+
localStorage.setItem(CHANGELOG_VERSION_KEY, entries[0].date);
41+
} else if (lastSeen < entries[0].date) {
42+
setHasNew(true);
43+
}
44+
}, [entries]);
45+
46+
const handleOpen = () => {
47+
setOpen(true);
48+
setHasNew(false);
49+
if (entries.length > 0) {
50+
localStorage.setItem(CHANGELOG_VERSION_KEY, entries[0].date);
51+
}
52+
};
53+
54+
return (
55+
<>
56+
<button
57+
onClick={handleOpen}
58+
className="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer bg-transparent border-0 p-0 relative"
59+
>
60+
{t("changelog")}
61+
{hasNew && (
62+
<span className="absolute -top-1.5 -right-3 bg-red-500 text-white text-[10px] font-bold px-1 rounded-full leading-tight">
63+
!
64+
</span>
65+
)}
66+
</button>
67+
<Dialog open={open} onOpenChange={setOpen}>
68+
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
69+
<DialogHeader>
70+
<DialogTitle className="text-2xl">{t("changelog")}</DialogTitle>
71+
</DialogHeader>
72+
<div className="space-y-4">
73+
{entries.map((entry, i) => (
74+
<div key={i} className="border-b border-gray-200 dark:border-slate-700 pb-3 last:border-0">
75+
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">{entry.date}</span>
76+
<h3 className="font-semibold text-base">{entry.title}</h3>
77+
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{entry.description}</p>
78+
</div>
79+
))}
80+
</div>
81+
<div className="text-right mt-2">
82+
<Button variant="ghost" onClick={() => setOpen(false)}>
83+
{t("close")}
84+
</Button>
85+
</div>
86+
</DialogContent>
87+
</Dialog>
88+
</>
89+
);
90+
};
91+
92+
export default ChangelogDialog;

src/i18n/ui-strings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const uiStrings: Record<string, Record<string, string>> = {
7676
conflicts: "conflicts",
7777
languageMismatchWarning: "Warning: This save file was created with a different language ({{fileLang}}). Importing may cause issues.",
7878
viewLabel: "View",
79+
changelog: "Changelog",
7980
},
8081

8182
en: {
@@ -150,5 +151,6 @@ export const uiStrings: Record<string, Record<string, string>> = {
150151
conflicts: "conflicts",
151152
languageMismatchWarning: "Warning: This save file was created with a different language ({{fileLang}}). Importing may cause issues.",
152153
viewLabel: "View",
154+
changelog: "Changelog",
153155
},
154156
};

0 commit comments

Comments
 (0)