Clean up
This commit is contained in:
parent
d487282fa5
commit
dc6053fc71
@ -121,9 +121,9 @@ Checks and fix:
|
||||
`_fresh/server.js` and `_fresh/static/` are found (e.g. in systemd set
|
||||
`WorkingDirectory=` to the app directory).
|
||||
|
||||
3. For reproducible builds across machines, consider re-enabling the lockfile
|
||||
in `deno.json` (`"lock": true`) and committing `deno.lock`, then using the
|
||||
same lock on the server.
|
||||
3. For reproducible builds across machines, consider re-enabling the lockfile in
|
||||
`deno.json` (`"lock": true`) and committing `deno.lock`, then using the same
|
||||
lock on the server.
|
||||
|
||||
4. Do not use `esm.sh` for Preact (this project already uses `npm:preact`);
|
||||
duplicate Preact from mixed sources is a common cause.
|
||||
|
||||
11
deno.json
11
deno.json
@ -1,9 +1,8 @@
|
||||
{
|
||||
"lock": false,
|
||||
"tasks": {
|
||||
"check": "deno fmt && deno fmt --check . && deno lint . && deno check",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "deno install -r --allow-scripts && vite build",
|
||||
"start": "deno serve -A --port 8080 _fresh/server.js",
|
||||
"update": "deno run -A -r jsr:@fresh/update .",
|
||||
"e2e": "deno run -A npm:playwright test",
|
||||
@ -75,12 +74,8 @@
|
||||
],
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"test": {
|
||||
"exclude": ["node_modules/", "_fresh/"]
|
||||
},
|
||||
"fmt": {
|
||||
"exclude": ["node_modules/", "_fresh/"]
|
||||
},
|
||||
"test": { "exclude": ["node_modules/", "_fresh/"] },
|
||||
"fmt": { "exclude": ["node_modules/", "_fresh/"] },
|
||||
"nodeModulesDir": "auto",
|
||||
"unstable": ["kv"]
|
||||
}
|
||||
|
||||
@ -11,7 +11,8 @@ interface MemberHomeProps {
|
||||
}
|
||||
|
||||
export default function MemberHome(
|
||||
{ onboardingStatus, serverCalendarYear, serverCalendarMonth }: MemberHomeProps,
|
||||
{ onboardingStatus, serverCalendarYear, serverCalendarMonth }:
|
||||
MemberHomeProps,
|
||||
) {
|
||||
useEffect(() => {
|
||||
const lockRoute = localStorage.getItem("lock-route");
|
||||
|
||||
71
islands/ThemeHydration.tsx
Normal file
71
islands/ThemeHydration.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { useEffect } from "preact/hooks";
|
||||
|
||||
const THEME_KEY = "cyberhybrid-theme-preference";
|
||||
|
||||
function applyTheme(root: Element, mode: string): void {
|
||||
root.setAttribute("data-theme-mode", mode);
|
||||
}
|
||||
|
||||
function readTheme(): string {
|
||||
try {
|
||||
const stored = localStorage.getItem(THEME_KEY);
|
||||
if (stored === "light" || stored === "dark") return stored;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return "dark";
|
||||
}
|
||||
|
||||
function syncToggleLabels(root: Element): void {
|
||||
const isDark = root.getAttribute("data-theme-mode") !== "light";
|
||||
const label = isDark ? "Switch to light mode" : "Switch to dark mode";
|
||||
const toggles = root.querySelectorAll("[data-theme-toggle]");
|
||||
toggles.forEach((el) => {
|
||||
el.setAttribute("aria-label", label);
|
||||
el.setAttribute("title", label);
|
||||
});
|
||||
}
|
||||
|
||||
function persistTheme(mode: string): void {
|
||||
try {
|
||||
localStorage.setItem(THEME_KEY, mode);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-only island that applies saved theme and wires up theme toggle clicks.
|
||||
* Renders nothing; runs theme logic in useEffect to avoid dangerouslySetInnerHTML.
|
||||
*/
|
||||
export default function ThemeHydration() {
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
|
||||
applyTheme(root, readTheme());
|
||||
syncToggleLabels(root);
|
||||
|
||||
const handleClick = (event: Event) => {
|
||||
const target = event.target as Node;
|
||||
if (!target || !(target instanceof Element)) return;
|
||||
const toggle = target.closest("[data-theme-toggle]");
|
||||
if (!toggle) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const current = root.getAttribute("data-theme-mode") === "light"
|
||||
? "light"
|
||||
: "dark";
|
||||
const next = current === "dark" ? "light" : "dark";
|
||||
applyTheme(root, next);
|
||||
persistTheme(next);
|
||||
syncToggleLabels(root);
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClick, true);
|
||||
return () => document.removeEventListener("click", handleClick, true);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -13,7 +13,8 @@ import {
|
||||
* Native-rendered dropdown content for <details> menu.
|
||||
*/
|
||||
export default function NavigationMenu(
|
||||
{ isLoggedIn, currentRoute, navigationGroups, serverCalendarPath }: NavigationMenuProps,
|
||||
{ isLoggedIn, currentRoute, navigationGroups, serverCalendarPath }:
|
||||
NavigationMenuProps,
|
||||
) {
|
||||
function getItemStateClass(item: NavigationItem): string {
|
||||
if (item.disabled) return "navigation-menu-item--disabled";
|
||||
|
||||
@ -16,7 +16,14 @@ function normalizeRoute(path: string): string {
|
||||
* Main navigation bar with user info, encryption status, and menu.
|
||||
*/
|
||||
export default function WelcomeBar(
|
||||
{ publicId, user_name, currentRoute, isEncrypted, stateId, serverCalendarPath }: WelcomeBarProps,
|
||||
{
|
||||
publicId,
|
||||
user_name,
|
||||
currentRoute,
|
||||
isEncrypted,
|
||||
stateId,
|
||||
serverCalendarPath,
|
||||
}: WelcomeBarProps,
|
||||
) {
|
||||
const [activeRoute, setActiveRoute] = useState(normalizeRoute(currentRoute));
|
||||
const [state] = useState<WelcomeBarState>({
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// _app.tsx
|
||||
import { define, type PageProps } from "../utils.ts";
|
||||
import WelcomeBar from "../islands/WelcomeBar/WelcomeBar.tsx";
|
||||
import ThemeHydration from "../islands/ThemeHydration.tsx";
|
||||
import { createPublicId } from "../helpers.ts";
|
||||
import { SessionCookie } from "./auth/callback.ts";
|
||||
|
||||
@ -24,10 +25,12 @@ export default define.page(async function App(props: PageProps) {
|
||||
: "";
|
||||
const now = new Date();
|
||||
const serverCalendarPath =
|
||||
`/calendar/financial-calendar/${now.getFullYear()}/${(now.getMonth() + 1).toString().padStart(2, "0")}`;
|
||||
`/calendar/financial-calendar/${now.getFullYear()}/${
|
||||
(now.getMonth() + 1).toString().padStart(2, "0")
|
||||
}`;
|
||||
|
||||
return (
|
||||
<html>
|
||||
<html data-theme-mode="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
@ -45,63 +48,7 @@ export default define.page(async function App(props: PageProps) {
|
||||
<div class="page-offset">
|
||||
<Component />
|
||||
</div>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(function () {
|
||||
var KEY = "cyberhybrid-theme-preference";
|
||||
var root = document.documentElement;
|
||||
|
||||
function applyTheme(mode) {
|
||||
root.setAttribute("data-theme-mode", mode);
|
||||
}
|
||||
|
||||
function readTheme() {
|
||||
try {
|
||||
var stored = localStorage.getItem(KEY);
|
||||
if (stored === "light" || stored === "dark") return stored;
|
||||
} catch (_error) {}
|
||||
return "dark";
|
||||
}
|
||||
|
||||
function syncToggleLabels() {
|
||||
var isDark = root.getAttribute("data-theme-mode") !== "light";
|
||||
var label = isDark ? "Switch to light mode" : "Switch to dark mode";
|
||||
var toggles = document.querySelectorAll("[data-theme-toggle]");
|
||||
for (var i = 0; i < toggles.length; i++) {
|
||||
toggles[i].setAttribute("aria-label", label);
|
||||
toggles[i].setAttribute("title", label);
|
||||
}
|
||||
}
|
||||
|
||||
function persistTheme(mode) {
|
||||
try {
|
||||
localStorage.setItem(KEY, mode);
|
||||
} catch (_error) {}
|
||||
}
|
||||
|
||||
applyTheme(readTheme());
|
||||
syncToggleLabels();
|
||||
|
||||
document.addEventListener("click", function (event) {
|
||||
var target = event.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
var toggle = target.closest("[data-theme-toggle]");
|
||||
if (!toggle) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var current = root.getAttribute("data-theme-mode") === "light"
|
||||
? "light"
|
||||
: "dark";
|
||||
var next = current === "dark" ? "light" : "dark";
|
||||
applyTheme(next);
|
||||
persistTheme(next);
|
||||
syncToggleLabels();
|
||||
}, true);
|
||||
})();`,
|
||||
}}
|
||||
/>
|
||||
<ThemeHydration />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user