Compare commits

...
2 Commits
Author SHA1 Message Date
Riyyi 6ac81c61d9 Cleanup 2025-04-04 07:21:06 +02:00
Riyyi 64a8aecf25 Persistent color theme 2025-04-04 07:21:06 +02:00
12 changed files with 192 additions and 142 deletions
+16 -36
View File
@@ -9,7 +9,7 @@ Website, written in Typescript.
Install dependencies: Install dependencies:
#+BEGIN_SRC sh #+BEGIN_SRC sh
$ bun install $ npm install
#+END_SRC #+END_SRC
** Development Server ** Development Server
@@ -17,8 +17,8 @@ $ bun install
Start the development server on =http://localhost:3000=: Start the development server on =http://localhost:3000=:
#+BEGIN_SRC sh #+BEGIN_SRC sh
$ bun run dev $ npm run dev
$ bun run dev -o # automatically open in the browser $ npm run dev -- -o # automatically open in the browser
#+END_SRC #+END_SRC
** Production ** Production
@@ -26,14 +26,14 @@ $ bun run dev -o # automatically open in the browser
Build for production: Build for production:
#+BEGIN_SRC sh #+BEGIN_SRC sh
$ bun run build # for server-side rendering or hybrid $ npm run build # for server-side rendering or hybrid
$ bun run generate # for static site generation $ npm run generate # for static site generation
#+END_SRC #+END_SRC
Locally preview production build: Locally preview production build:
#+BEGIN_SRC sh #+BEGIN_SRC sh
$ bun run preview # requires building first! $ npm run preview # requires building first!
#+END_SRC #+END_SRC
Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-rendering][nuxt/docs/deployment#client-side-only-rendering]] Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-rendering][nuxt/docs/deployment#client-side-only-rendering]]
@@ -43,38 +43,18 @@ Reference: [[https://nuxt.com/docs/getting-started/deployment#client-side-only-r
- [[https://github.com/vuejs/core][Vue3]] JavaScript framework - [[https://github.com/vuejs/core][Vue3]] JavaScript framework
- [[https://github.com/nuxt/nuxt][Nuxt]] Vue framework - [[https://github.com/nuxt/nuxt][Nuxt]] Vue framework
- [[https://github.com/vuejs/pinia][Pinia]] State management - [[https://github.com/vuejs/pinia][Pinia]] State management
- [[https://github.com/primefaces/primevue][PrimeVue]] Vue component library - [[https://github.com/twbs/bootstrap][Bootstrap5]] CSS library
- [[https://github.com/colinhacks/zod][Zod]] Schema validation - [[https://github.com/colinhacks/zod][Zod]] Schema validation
- [[https://github.com/microsoft/TypeScript][Typescript]] Static types in JavaScript - [[https://github.com/microsoft/TypeScript][Typescript]] Static types in JavaScript
- [[https://github.com/eslint/eslint][ESLint]] Code analyses tool - [[https://github.com/eslint/eslint][ESLint]] Code analyses tool
** Package Reproduction * Todo
The following bun commands have been run to fill the =package.json=: - [ ] dark-mode
- [ ] persistence in localstorage
#+BEGIN_SRC sh - [X] <h> tag scroll fix
$ bun x nuxi@latest init website - [ ] pull request? https://github.com/nuxt-modules/mdc/tree/main/src/runtime/components/prose
- [X] table of contents: https://github.com/hanyujie2002/nuxt-toc/blob/main/src/runtime/components/TableOfContents.vue
# Development - [X] nuke: bootstrap-vue-next
$ bun nuxi module add eslint - [ ] update readme: bun -> npm, primevue -> bootstrap
$ bun install --dev typescript - [X] better popup code copy button
$ bun install --dev @vue/language-server
$ bun install --dev @vue/typescript-plugin
$ bun install -g typescript
$ bun install -g @vue/language-server
$ bun install -g @vue/typescript-plugin
# Pinia
$ bun install @pinia/nuxt # also add it to nuxt.config modules!
$ bun install pinia-plugin-persistedstate # same as above
# PrimeVue
$ bun install primevue primeicons @primevue/themes @primevue/forms
$ bun install --dev @primevue/nuxt-module
# Zod
$ bun install zod
# UUID
$ bun install uuid
#+END_SRC
-1
View File
@@ -47,7 +47,6 @@ export default defineNuxtConfig({
}, },
piniaPluginPersistedstate: { piniaPluginPersistedstate: {
debug: process.env.NODE_ENV === "development", // log error to console debug: process.env.NODE_ENV === "development", // log error to console
storage: "cookies",
cookieOptions: { cookieOptions: {
sameSite: "lax", // prevent CSRF sameSite: "lax", // prevent CSRF
secure: process.env.NODE_ENV !== "development" // only send over HTTPS secure: process.env.NODE_ENV !== "development" // only send over HTTPS
+5 -30
View File
@@ -23,8 +23,6 @@
"@nuxt/content": "^3.4.0", "@nuxt/content": "^3.4.0",
"@nuxt/eslint": "^1.2.0", "@nuxt/eslint": "^1.2.0",
"@types/bootstrap": "^5.2.10", "@types/bootstrap": "^5.2.10",
"@types/bun": "latest",
"@vue/compiler-sfc": "^3.5.13",
"@vue/language-server": "^2.1.10", "@vue/language-server": "^2.1.10",
"@vue/typescript-plugin": "^2.1.10", "@vue/typescript-plugin": "^2.1.10",
"nuxt": "^3.14.1592", "nuxt": "^3.14.1592",
@@ -2258,16 +2256,6 @@
"@popperjs/core": "^2.9.2" "@popperjs/core": "^2.9.2"
} }
}, },
"node_modules/@types/bun": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.8.tgz",
"integrity": "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"bun-types": "1.2.7"
}
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
"dev": true, "dev": true,
@@ -2315,6 +2303,8 @@
"version": "22.13.14", "version": "22.13.14",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.20.0" "undici-types": "~6.20.0"
} }
@@ -2339,14 +2329,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/ws": {
"version": "8.18.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.28.0", "version": "8.28.0",
"dev": true, "dev": true,
@@ -3552,15 +3534,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/bun-types": {
"version": "1.2.7",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"@types/ws": "*"
}
},
"node_modules/bundle-name": { "node_modules/bundle-name": {
"version": "4.1.0", "version": "4.1.0",
"dev": true, "dev": true,
@@ -11233,7 +11206,9 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.20.0", "version": "6.20.0",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"optional": true,
"peer": true
}, },
"node_modules/unenv": { "node_modules/unenv": {
"version": "2.0.0-rc.15", "version": "2.0.0-rc.15",
-2
View File
@@ -26,8 +26,6 @@
"@nuxt/content": "^3.4.0", "@nuxt/content": "^3.4.0",
"@nuxt/eslint": "^1.2.0", "@nuxt/eslint": "^1.2.0",
"@types/bootstrap": "^5.2.10", "@types/bootstrap": "^5.2.10",
"@types/bun": "latest",
"@vue/compiler-sfc": "^3.5.13",
"@vue/language-server": "^2.1.10", "@vue/language-server": "^2.1.10",
"@vue/typescript-plugin": "^2.1.10", "@vue/typescript-plugin": "^2.1.10",
"nuxt": "^3.14.1592", "nuxt": "^3.14.1592",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+3
View File
@@ -12,6 +12,9 @@ import { useStateStore } from "@/stores/stateStore";
const router = useRouter(); const router = useRouter();
const store = useStateStore(); const store = useStateStore();
// Set dark theme
store.applyColorMode();
useHead({ useHead({
titleTemplate: (titleChunk: string | undefined): string | null => { titleTemplate: (titleChunk: string | undefined): string | null => {
return titleChunk ? `${titleChunk} - website-vue` : 'website-vue'; return titleChunk ? `${titleChunk} - website-vue` : 'website-vue';
+45 -1
View File
@@ -1,6 +1,16 @@
<!-- https://github.com/barelyhuman/snips/blob/dev/pages/css-loader.md --> <!-- https://github.com/barelyhuman/snips/blob/dev/pages/css-loader.md -->
<div class="loader"></div> <!DOCTYPE html>
<html lang="en">
<head>
<style> <style>
body {
background-color: #fff;
}
.dark {
background-color: #1a1d20;
}
.loader { .loader {
display: block; display: block;
position: fixed; position: fixed;
@@ -21,6 +31,13 @@
animation: loader 400ms linear infinite; animation: loader 400ms linear infinite;
} }
.dark .loader {
border-top-color: #fff !important;
border-left-color: #fff !important;
border-bottom-color: #444 !important;
border-right-color: #444 !important;
}
@-webkit-keyframes loader { @-webkit-keyframes loader {
0% { 0% {
-webkit-transform: translate(-50%, -50%) rotate(0deg); -webkit-transform: translate(-50%, -50%) rotate(0deg);
@@ -39,3 +56,30 @@
} }
} }
</style> </style>
</head>
<body>
<div class="loader"></div>
<script>
let css = "light";
let state = null;
const json = localStorage.getItem("state");
if (json !== null) {
state = JSON.parse(json).colorMode;
}
if (state === null || state === "auto") {
const preferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (preferDark) {
css = "dark";
}
}
else if (state === "dark") {
css = "dark";
}
const element = document.querySelector("html");
element.classList.add(css);
</script>
</body>
</html>
+2
View File
@@ -10,6 +10,7 @@ declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
IBi0Circle: typeof import('~icons/bi/0-circle')['default'] IBi0Circle: typeof import('~icons/bi/0-circle')['default']
IBiActivity: typeof import('~icons/bi/activity')['default'] IBiActivity: typeof import('~icons/bi/activity')['default']
IFaAdjust: typeof import('~icons/fa/adjust')['default']
IFaAngellist: typeof import('~icons/fa/angellist')['default'] IFaAngellist: typeof import('~icons/fa/angellist')['default']
IFaCheck: typeof import('~icons/fa/check')['default'] IFaCheck: typeof import('~icons/fa/check')['default']
IFaClone: typeof import('~icons/fa/clone')['default'] IFaClone: typeof import('~icons/fa/clone')['default']
@@ -22,6 +23,7 @@ declare module 'vue' {
IFaLink: typeof import('~icons/fa/link')['default'] IFaLink: typeof import('~icons/fa/link')['default']
IFaLinkedinSquare: typeof import('~icons/fa/linkedin-square')['default'] IFaLinkedinSquare: typeof import('~icons/fa/linkedin-square')['default']
IFaMoonO: typeof import('~icons/fa/moon-o')['default'] IFaMoonO: typeof import('~icons/fa/moon-o')['default']
IFaSolidMoon: typeof import('~icons/fa-solid/moon')['default']
IFaSolidSun: typeof import('~icons/fa-solid/sun')['default'] IFaSolidSun: typeof import('~icons/fa-solid/sun')['default']
IMdiAccountBox: typeof import('~icons/mdi/account-box')['default'] IMdiAccountBox: typeof import('~icons/mdi/account-box')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
+56
View File
@@ -0,0 +1,56 @@
<template>
<div class="dropup position-fixed corner">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<template v-if="store.colorMode === 'light'">
<IFaSolidSun />
</template>
<template v-else-if="store.colorMode === 'dark'">
<IFaSolidMoon />
</template>
<template v-else>
<IFaAdjust />
</template>
</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" :class="store.colorMode === 'light' ? 'active' : ''" @click="store.setColorMode('light')">
<IFaSolidSun /> Light &nbsp;
<IFaCheck v-if="store.colorMode === 'light'" class="font-smaller float-right" />
</a>
</li>
<li>
<a class="dropdown-item" :class="store.colorMode === 'dark' ? 'active' : ''" @click="store.setColorMode('dark')">
<IFaSolidMoon /> Dark &nbsp;
<IFaCheck v-if="store.colorMode === 'dark'" class="font-smaller float-right" />
</a>
</li>
<li>
<a class="dropdown-item" :class="store.colorMode === 'auto' ? 'active' : ''" @click="store.setColorMode('auto')">
<IFaAdjust width="1.3em" /> Auto &nbsp;
<IFaCheck v-if="store.colorMode === 'auto'" class="font-smaller float-right" />
</a>
</li>
</ul>
</div>
</template>
<style scoped>
.corner {
right: 10px;
bottom: 10px;
}
.dropdown-menu {
min-width: 125px;
}
.font-smaller {
font-size: .6rem;
}
</style>
<script setup lang="ts">
import { useStateStore } from "@/stores/stateStore";
const store = useStateStore();
</script>
-23
View File
@@ -1,23 +0,0 @@
<template>
<div class="position-fixed corner">
<template v-if="store.colorMode === 'dark'">
<IFaSolidSun @click="store.toggleColorMode" />
</template>
<template v-else>
<IFaMoonO @click="store.toggleColorMode" />
</template>
</div>
</template>
<style scoped>
.corner {
right: 10px;
bottom: 10px;
}
</style>
<script setup lang="ts">
import { useStateStore } from "@/stores/stateStore";
const store = useStateStore();
</script>
+1 -1
View File
@@ -10,7 +10,7 @@
</div> </div>
<SharedFooter /> <SharedFooter />
</div> </div>
<ToggleColorMode /> <ColorMode />
</template> </template>
<style scoped> <style scoped>
+27 -11
View File
@@ -6,21 +6,34 @@ import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min";
export const useStateStore = defineStore("state", () => { export const useStateStore = defineStore("state", () => {
const colorMode = ref<string>("light"); const colorMode = ref<string>("auto");
const toggleColorMode = (): void => { const setColorMode = (newMode: string): void => {
colorMode.value = colorMode.value === "dark" ? "light" : "dark"; colorMode.value = newMode;
const html = document.querySelector("html") as HTMLElement; applyColorMode();
};
const applyColorMode = (): void => {
let css = "light";
if (colorMode.value === "auto") {
const preferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (preferDark) {
css = "dark";
}
}
else if (colorMode.value == "dark") {
css = "dark";
}
const html = document.documentElement as HTMLElement;
// Theme used by Bootstrap // Theme used by Bootstrap
html.setAttribute('data-bs-theme', colorMode.value); html.setAttribute('data-bs-theme', css);
// Theme class used by shiki syntax highlighting // Theme class used by shiki syntax highlighting
if (colorMode.value === "dark") {
html.classList.add("dark");
} else {
html.classList.remove("dark"); html.classList.remove("dark");
if (css === "dark") {
html.classList.add("dark");
}
} }
};
const popoverList = ref<any[]>([]); const popoverList = ref<any[]>([]);
const tooltipList = ref<any[]>([]); const tooltipList = ref<any[]>([]);
@@ -37,7 +50,10 @@ export const useStateStore = defineStore("state", () => {
tooltipList.value = [...tooltipTriggerList].map(tooltip => new bootstrap.Tooltip(tooltip)); tooltipList.value = [...tooltipTriggerList].map(tooltip => new bootstrap.Tooltip(tooltip));
}; };
return { colorMode, toggleColorMode, initBootstrap } return { colorMode, setColorMode, applyColorMode, initBootstrap }
}, { }, {
persist: process.env.NODE_ENV === 'development' ? true : false, persist: {
pick: ["colorMode"],
storage: localStorage
}
}) })