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
603 changes: 495 additions & 108 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
"lodash": "^4.17.21",
"moment": "^2.30.1",
"morgan": "^1.10.0",
"next": "^14.2.14",
"next": "^15.3.2",
"next-mdx-remote": "^5.0.0",
"path-to-regexp": "^8.2.0",
"qs": "^6.14.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.4.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/app/_components/main_frame/switch.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
display: inline-block;
color: currentColor;
border-radius: 50%;
border: 1.5px dashed currentColor;
border: 1.6px dashed currentColor;
cursor: pointer;
--size: 24px;
height: var(--size);
Expand Down
129 changes: 53 additions & 76 deletions src/app/_components/main_frame/theme-switcher.tsx
Original file line number Diff line number Diff line change
@@ -1,115 +1,92 @@
"use client";
'use client';

import styles from "./switch.module.css";
import { memo, useEffect, useState } from "react";
import { memo, useEffect, useState } from 'react';
import styles from './switch.module.css';

declare global {
var updateDOM: () => void;
}
const STORAGE_KEY = 'this-theme';
const MODES = ['system', 'dark', 'light'] as const;
type ColorSchemePreference = typeof MODES[number];

type ColorSchemePreference = "system" | "dark" | "light";

const STORAGE_KEY = "this-theme";
const modes: ColorSchemePreference[] = ["system", "dark", "light"];

/** to reuse updateDOM function defined inside injected script */

/** function to be injected in script tag for avoiding FOUC (Flash of Unstyled Content) */
const NoFOUCScript = (storageKey: string) => {
/* can not use outside constants or function as this script will be injected in a different context */
const [SYSTEM, DARK, LIGHT] = ["system", "dark", "light"];
// Injects theme script before hydration to avoid FOUC
const noFOUCScript = (storageKey: string) => {
const [SYSTEM, DARK, LIGHT] = ['system', 'dark', 'light'];
const media = matchMedia(`(prefers-color-scheme: ${DARK})`);

/** Modify transition globally to avoid patched transitions */
const modifyTransition = () => {
const css = document.createElement("style");
css.textContent = "*,*:after,*:before{transition:none !important;}";
document.head.appendChild(css);
const disableTransitionsTemporarily = () => {
const style = document.createElement('style');
style.textContent = '*, *::before, *::after { transition: none !important; }';
document.head.appendChild(style);

return () => {
/* Force restyle */
getComputedStyle(document.body);
/* Wait for next tick before removing */
setTimeout(() => document.head.removeChild(css), 1);
setTimeout(() => document.head.removeChild(style), 1);
};
};

const media = matchMedia(`(prefers-color-scheme: ${DARK})`);

/** function to add remove dark class */
window.updateDOM = () => {
const restoreTransitions = modifyTransition();
const mode = localStorage.getItem(storageKey) ?? SYSTEM;
const applyTheme = () => {
const restoreTransitions = disableTransitionsTemporarily();
const storedMode = localStorage.getItem(storageKey) ?? SYSTEM;
const systemMode = media.matches ? DARK : LIGHT;
const resolvedMode = mode === SYSTEM ? systemMode : mode;
const resolvedMode = storedMode === SYSTEM ? systemMode : storedMode;

const classList = document.documentElement.classList;
if (resolvedMode === DARK) classList.add(DARK);
else classList.remove(DARK);
document.documentElement.setAttribute("data-mode", mode);
classList.toggle(DARK, resolvedMode === DARK);
document.documentElement.setAttribute('data-mode', storedMode);
restoreTransitions();
};
window.updateDOM();
media.addEventListener("change", window.updateDOM);
};

let updateDOM: () => void;
applyTheme();
media.addEventListener('change', applyTheme);
};

/**
* Switch button to quickly toggle user preference.
*/
const Switch = () => {
const [mode, setMode] = useState<ColorSchemePreference>(
() =>
((typeof localStorage !== "undefined" &&
localStorage.getItem(STORAGE_KEY)) ??
"system") as ColorSchemePreference,
);
const [mode, setMode] = useState<ColorSchemePreference>(() => {
return (typeof localStorage !== 'undefined'
? (localStorage.getItem(STORAGE_KEY) as ColorSchemePreference)
: 'system') ?? 'system';
});

useEffect(() => {
// store global functions to local variables to avoid any interference
updateDOM = window.updateDOM;
/** Sync the tabs */
addEventListener("storage", (e: StorageEvent): void => {
e.key === STORAGE_KEY && setMode(e.newValue as ColorSchemePreference);
});
const storedMode = localStorage.getItem(STORAGE_KEY) as ColorSchemePreference;
setMode(storedMode ?? 'system');
}, []);

useEffect(() => {
localStorage.setItem(STORAGE_KEY, mode);
updateDOM();
const systemMode = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const resolvedMode = mode === 'system' ? systemMode : mode;
const classList = document.documentElement.classList;
classList.toggle('dark', resolvedMode === 'dark');
document.documentElement.setAttribute('data-mode', mode);
}, [mode]);

/** toggle mode */
const handleModeSwitch = () => {
const index = modes.indexOf(mode);
setMode(modes[(index + 1) % modes.length]);
const handleToggle = () => {
const nextIndex = (MODES.indexOf(mode) + 1) % MODES.length;
setMode(MODES[nextIndex]);
};

return (
<button
suppressHydrationWarning={true}
suppressHydrationWarning
className={styles.switch}
aria-label="ThemeSwitch"
onClick={handleModeSwitch}
>
</button>
aria-label="Toggle theme"
onClick={handleToggle}
/>
);
};

const Script = memo(() => (
<script
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `(${NoFOUCScript.toString()})('${STORAGE_KEY}')`,
__html: `(${noFOUCScript.toString()})('${STORAGE_KEY}')`,
}}
/>
));

/**
* This component wich applies classes and transitions.
*/
export const ThemeSwitcher = () => {
return (
<>
<Script />
<Switch />
</>
);
};
export const ThemeSwitcher = () => (
<>
<Script />
<Switch />
</>
);
13 changes: 11 additions & 2 deletions src/app/blog/posts/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// pull from private repo: [tkokhing/blog_post/_posts] MDX_FOLDER
import { Metadata } from "next";
import { notFound } from "next/navigation";
import Container from "@/app/_components/container";
import { PostHeader } from "@/app/_components/post-header";
Expand All @@ -9,7 +10,14 @@ import { generatePageStaticParams } from "@/lib/generatePageStaticParams";

const MDX_FOLDER = "_posts";

export default async function Post({ params }: { params: { slug: string } }) {
type Params = {
params: Promise<{
slug: string;
}>;
};

export default async function Post(props: Params) {
const params = await props.params;
const post = getPostBySlug(params.slug, MDX_FOLDER);
if (!post) return notFound();

Expand All @@ -32,7 +40,8 @@ export default async function Post({ params }: { params: { slug: string } }) {
);
}

export async function generateMetadata({ params }: { params: { slug: string } }) {
export async function generateMetadata(props: Params): Promise<Metadata> {
const params = await props.params;
return generatePageMetadata(params.slug, MDX_FOLDER);
}

Expand Down
6 changes: 3 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning={true}>
<body
className={cn(inter.className, "bg-slate-50 text-sky-700 dark:bg-slate-900 dark:text-slate-300")}
>
<AlertBar />
<ThemeSwitcher />
<NavigationProvider>
<AlertBar />
<Navigationbar />
<ThemeSwitcher />
<SubpageHeader />
<div className="min-h-screen">{children}</div>
<Footer />
Expand Down
14 changes: 11 additions & 3 deletions src/app/topics/AtoZ/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// pull from private repo: [tkokhing/topic_post/_topics] MDX_FOLDER
import { Metadata } from "next";
import { notFound } from "next/navigation";
import Container from "@/app/_components/container";
import { PostHeader } from "@/app/_components/post-header";
Expand All @@ -9,8 +10,14 @@ import { generatePageStaticParams } from "@/lib/generatePageStaticParams";

const MDX_FOLDER = "_topics";

export default async function Post({ params }: { params: { slug: string } }) {
const post = getPostBySlug(params.slug, MDX_FOLDER);
type Params = {
params: Promise<{
slug: string;
}>;
};

export default async function Post(props: Params) {
const params = await props.params; const post = getPostBySlug(params.slug, MDX_FOLDER);
if (!post) return notFound();

return (
Expand All @@ -32,7 +39,8 @@ export default async function Post({ params }: { params: { slug: string } }) {
);
}

export async function generateMetadata({ params }: { params: { slug: string } }) {
export async function generateMetadata(props: Params): Promise<Metadata> {
const params = await props.params;
return generatePageMetadata(params.slug, MDX_FOLDER);
}

Expand Down
13 changes: 11 additions & 2 deletions src/app/topics/AtoZ/terrified-by-linux/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// pull from private repo: [tkokhing/linux_post/_linux] MDX_FOLDER
import { Metadata } from "next";
import { notFound } from "next/navigation";
import Container from "@/app/_components/container";
import { PostHeader } from "@/app/_components/post-header";
Expand All @@ -13,7 +14,14 @@ import Tip from "@/app/_components/blog_frame/tip";

const MDX_FOLDER = "_linux";

export default async function Post({ params }: { params: { slug: string } }) {
type Params = {
params: Promise<{
slug: string;
}>;
};

export default async function Post(props: Params) {
const params = await props.params;
const post = getPostBySlug(params.slug, MDX_FOLDER);
const LinuxBlogComponents = {
Tip,
Expand Down Expand Up @@ -41,7 +49,8 @@ export default async function Post({ params }: { params: { slug: string } }) {
);
}

export async function generateMetadata({ params }: { params: { slug: string } }) {
export async function generateMetadata(props: Params): Promise<Metadata> {
const params = await props.params;
return generatePageMetadata(params.slug, MDX_FOLDER);
}

Expand Down