Merge pull request #4 from onixbyte/release/1.1.2

Release 1.1.2
This commit is contained in:
Jam'o Siu
2026-03-02 09:49:14 +08:00
committed by GitHub
13 changed files with 106 additions and 252 deletions
+2
View File
@@ -0,0 +1,2 @@
# SEO Site URL
VITE_SEO_SITE_URL=http://localhost:5173
+34
View File
@@ -0,0 +1,34 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).
## [1.1.2] - 2026-03-02
### Changed
- Refined sample datasets to better demonstrate JSON tree navigation and edge cases.
### Removed
- Removed the integrated Changelog page from the system UI.
## [1.1.1] - 2026-02-24
### Added
- Added syntax highlighting for JSONPath expressions to improve visual readability.
## [1.1.0] - 2026-02-24
### Added
- Implemented sortable table views for JSON arrays with CSV export functionality.
- Introduced a dedicated tool layout featuring a categorised sidebar menu for efficient switching.
- Added one-click functionality to copy JSONPath-selected data as raw JSON.
- Categorised tools into "JSON Processing" (Viewer, Grid) and "Daily Tools" (BMI Calculator).
## [1.0.0] - 2026-01-19
### Added
- Initial release.
- Full-featured JSON viewer with JSONPath query support and CSV export.
- BMI calculator with category guidance and health advice.
- Internationalisation support for British English and Simplified Chinese.
+18
View File
@@ -0,0 +1,18 @@
import { useTranslation } from "react-i18next"
import { useMemo } from "react"
import dayjs from "dayjs"
export default function Footer() {
const { t } = useTranslation()
const today = useMemo(() => dayjs(), [])
return (
<footer className="bg-white border-t shrink-0">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<p className="text-center text-sm text-gray-500">
{t("app.copyright", { year: today.year() })}
</p>
</div>
</footer>
)
}
+39
View File
@@ -0,0 +1,39 @@
import { Link } from "react-router-dom"
import { useTranslation } from "react-i18next"
import LanguageSwitcher from "@/components/language-switcher"
export default function Header() {
const { t } = useTranslation()
return (
<header className="bg-white shadow-sm border-b">
<div className="px-4">
<div className="flex justify-between items-center h-16">
<div className="flex items-center">
<h1 className="text-xl font-semibold text-gray-900">{t("app.title")}</h1>
</div>
<div className="flex items-center gap-4">
<nav className="flex space-x-8">
<Link
to="/"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
{t("navigation.home")}
</Link>
<Link
to="/about"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
{t("navigation.about")}
</Link>
<Link
to="/contact"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
{t("navigation.contact")}
</Link>
</nav>
<LanguageSwitcher />
</div>
</div>
</div>
</header>
)
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMemo, useRef } from "react" import React, { useMemo, useRef } from "react"
type JsonCodeEditorProps = { type JsonCodeEditorProps = {
value: string value: string
+1 -1
View File
@@ -7,7 +7,7 @@ type SeoProps = {
path: string path: string
} }
const SITE_URL = "https://dev-lab.onixbyte.dev" const SITE_URL = import.meta.env.VITE_SEO_SITE_URL
const DEFAULT_IMAGE = `${SITE_URL}/onixbyte.svg` const DEFAULT_IMAGE = `${SITE_URL}/onixbyte.svg`
function setMetaTag(selector: string, attr: string, value: string) { function setMetaTag(selector: string, attr: string, value: string) {
+4 -47
View File
@@ -3,6 +3,8 @@ import { useMemo } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import dayjs from "dayjs" import dayjs from "dayjs"
import LanguageSwitcher from "@/components/language-switcher" import LanguageSwitcher from "@/components/language-switcher"
import Header from "@/components/header"
import Footer from "@/components/footer"
/** /**
* Main application component that serves as the root layout. * Main application component that serves as the root layout.
@@ -15,46 +17,7 @@ export default function HeroLayout() {
return ( return (
<div className="h-screen bg-gray-50 flex flex-col overflow-hidden"> <div className="h-screen bg-gray-50 flex flex-col overflow-hidden">
{/* Navigation Header */} {/* Navigation Header */}
<header className="bg-white shadow-sm border-b"> <Header></Header>
<div className="px-4">
<div className="flex justify-between items-center h-16">
<div className="flex items-center">
<h1 className="text-xl font-semibold text-gray-900">
{t("app.title")}
</h1>
</div>
<div className="flex items-center gap-4">
<nav className="flex space-x-8">
<Link
to="/"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
{t("navigation.home")}
</Link>
<Link
to="/about"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
{t("navigation.about")}
</Link>
<Link
to="/contact"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
{t("navigation.contact")}
</Link>
<Link
to="/changelog"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
{t("navigation.changelog")}
</Link>
</nav>
<LanguageSwitcher />
</div>
</div>
</div>
</header>
{/* Main Content Area */} {/* Main Content Area */}
<main className="flex-1 p-4 overflow-hidden min-h-0"> <main className="flex-1 p-4 overflow-hidden min-h-0">
@@ -62,13 +25,7 @@ export default function HeroLayout() {
</main> </main>
{/* Footer */} {/* Footer */}
<footer className="bg-white border-t shrink-0"> <Footer></Footer>
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<p className="text-center text-sm text-gray-500">
{t("app.copyright", { year: today.year() })}
</p>
</div>
</footer>
</div> </div>
) )
} }
+4 -41
View File
@@ -3,6 +3,8 @@ import { Link, NavLink, Outlet } from "react-router-dom"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import dayjs from "dayjs" import dayjs from "dayjs"
import LanguageSwitcher from "@/components/language-switcher" import LanguageSwitcher from "@/components/language-switcher"
import Header from "@/components/header"
import Footer from "@/components/footer"
export default function ToolsLayout() { export default function ToolsLayout() {
const today = useMemo(() => dayjs(), []) const today = useMemo(() => dayjs(), [])
@@ -28,40 +30,7 @@ export default function ToolsLayout() {
return ( return (
<div className="h-screen bg-gray-50 flex flex-col overflow-hidden"> <div className="h-screen bg-gray-50 flex flex-col overflow-hidden">
<header className="bg-white shadow-sm border-b"> <Header></Header>
<div className="px-4">
<div className="flex justify-between items-center h-16">
<div className="flex items-center">
<h1 className="text-xl font-semibold text-gray-900">{t("app.title")}</h1>
</div>
<div className="flex items-center gap-4">
<nav className="flex space-x-8">
<Link
to="/"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
{t("navigation.home")}
</Link>
<Link
to="/about"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
{t("navigation.about")}
</Link>
<Link
to="/contact"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
{t("navigation.contact")}
</Link>
<Link
to="/changelog"
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
{t("navigation.changelog")}
</Link>
</nav>
<LanguageSwitcher />
</div>
</div>
</div>
</header>
<main className="flex-1 p-4 overflow-hidden min-h-0"> <main className="flex-1 p-4 overflow-hidden min-h-0">
<div className="h-full flex gap-4 overflow-hidden"> <div className="h-full flex gap-4 overflow-hidden">
@@ -120,13 +89,7 @@ export default function ToolsLayout() {
</div> </div>
</main> </main>
<footer className="bg-white border-t shrink-0"> <Footer></Footer>
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<p className="text-center text-sm text-gray-500">
{t("app.copyright", { year: today.year() })}
</p>
</div>
</footer>
</div> </div>
) )
} }
-153
View File
@@ -1,153 +0,0 @@
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import Seo from "@/components/seo"
interface ChangeEntry {
type: "feat" | "fix" | "refactor" | "chore"
title: string
description?: string
date?: string
}
interface ChangelogVersion {
version: string
date: string
entries: ChangeEntry[]
}
const CHANGELOG_DATA: ChangelogVersion[] = [
{
version: "1.1.1",
date: "2026-02-24",
entries: [
{
type: "feat",
title: "JSON Path Highlighting",
description: "Add highlighting for JSON Path expression."
}
]
},
{
version: "1.1.0",
date: "2026-02-24",
entries: [
{
type: "feat",
title: "JSON Grid",
description: "Convert JSON arrays into sortable table view with CSV export functionality"
},
{
type: "feat",
title: "Tools Layout with Collapsible Menu",
description: "Added a dedicated tools layout with categorised sidebar menu for quick tool switching",
},
{
type: "feat",
title: "Copy Selected JSON Feature",
description: "Added ability to copy JSONPath selected data as raw JSON with one-click action",
},
{
type: "feat",
title: "Tool Categories",
description: "Organised tools into JSON Processing (JSON Viewer, JSON Grid) and Daily Tools (BMI Calculator)",
},
{
type: "fix",
title: "Live Application Link",
description: "Updated live application link in README",
},
],
},
{
version: "1.0.0",
date: "2026-01-19",
entries: [
{
type: "feat",
title: "JSON Viewer with JSONPath",
description: "Full-featured JSON viewer with JSONPath query support and CSV export",
},
{
type: "feat",
title: "BMI Calculator",
description: "Calculate Body Mass Index with category guidance and health advice",
},
{
type: "feat",
title: "Internationalization",
description: "Support for English (GB) and Simplified Chinese with language switcher",
},
],
},
]
type ChangeTypeKey = "feat" | "fix" | "refactor" | "chore"
export default function Changelog() {
const { t } = useTranslation()
const changeTypeLabels = useMemo(() => {
const labels: Record<ChangeTypeKey, { label: string; colour: string }> = {
feat: { label: t("changelog.featureType"), colour: "bg-emerald-100 text-emerald-700" },
fix: { label: t("changelog.fixType"), colour: "bg-blue-100 text-blue-700" },
refactor: { label: t("changelog.refactorType"), colour: "bg-purple-100 text-purple-700" },
chore: { label: t("changelog.choreType"), colour: "bg-slate-100 text-slate-700" },
}
return labels
}, [t])
return (
<div className="min-h-screen bg-gray-50">
<Seo
title={t("seo.changelog.title")}
description={t("seo.changelog.description")}
path="/changelog"
/>
{/* Header */}
<div className="bg-white shadow-sm border-b">
<div className="max-w-4xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
<h1 className="text-4xl font-bold text-gray-900">{t("changelog.title")}</h1>
<p className="mt-2 text-lg text-gray-600">{t("changelog.description")}</p>
</div>
</div>
{/* Timeline */}
<div className="max-w-4xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
<div className="space-y-8">
{CHANGELOG_DATA.map((changelog) => (
<div key={changelog.version} className="relative">
{/* Version header */}
<div className="flex items-baseline gap-4 mb-6">
<h2 className="text-2xl font-bold text-gray-900">v{changelog.version}</h2>
<time className="text-sm text-gray-500">{changelog.date}</time>
</div>
{/* Changes list */}
<div className="space-y-4">
{changelog.entries.map((entry, idx) => {
const typeInfo = changeTypeLabels[entry.type as ChangeTypeKey]
return (
<div key={`${changelog.version}-${idx}`} className="bg-white rounded-lg border border-slate-200 p-4">
<div className="flex items-start gap-3">
<span className={`inline-block px-2.5 py-0.5 rounded text-xs font-semibold ${typeInfo.colour}`}>
{typeInfo.label}
</span>
<div className="flex-1">
<h3 className="text-base font-semibold text-gray-900">{entry.title}</h3>
{entry.description && (
<p className="mt-1 text-sm text-gray-600">{entry.description}</p>
)}
</div>
</div>
</div>
)
})}
</div>
</div>
))}
</div>
</div>
</div>
)
}
-1
View File
@@ -9,7 +9,6 @@ export default function JsonGrid() {
const { t } = useTranslation() const { t } = useTranslation()
const initialData = [ const initialData = [
{ id: 0, name: "TTY", role: "CEO", active: true },
{ id: 1, name: "Alice", role: "Developer", active: true }, { id: 1, name: "Alice", role: "Developer", active: true },
{ id: 2, name: "Bob", role: "Designer", active: false }, { id: 2, name: "Bob", role: "Designer", active: false },
{ id: 3, name: "Charlie", role: "Product Manager", active: true }, { id: 3, name: "Charlie", role: "Product Manager", active: true },
-1
View File
@@ -52,7 +52,6 @@ export default function JsonViewer() {
location: "London", location: "London",
is_active: true, is_active: true,
staff_members: [ staff_members: [
{ id: 100, name: "TTY", roles: ["CEO"] },
{ id: 101, name: "Alice", roles: ["Admin", "Manager"] }, { id: 101, name: "Alice", roles: ["Admin", "Manager"] },
{ id: 102, name: "Bob", roles: ["Developer"] }, { id: 102, name: "Bob", roles: ["Developer"] },
], ],
+1 -5
View File
@@ -35,11 +35,7 @@ const router = createBrowserRouter(
{ {
path: "contact", path: "contact",
lazy: lazy(() => import("@/page/contact")), lazy: lazy(() => import("@/page/contact")),
}, }
{
path: "changelog",
lazy: lazy(() => import("@/page/changelog")),
},
], ],
}, },
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv { interface ImportMetaEnv {
// todo add env properties here readonly VITE_SEO_SITE_URL: string
} }
interface ImportMeta { interface ImportMeta {