forked from Mirrors/elk
fix: rework setup to improve SSR compatibility
This commit is contained in:
parent
fd7d30a38a
commit
d8d163dbd0
22 changed files with 137 additions and 73 deletions
1
app.vue
1
app.vue
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
setupI18n()
|
||||
setupFontSize()
|
||||
setupPageHeader()
|
||||
setupEmojis()
|
||||
provideGlobalCommands()
|
||||
|
|
|
@ -5,6 +5,7 @@ defineProps<{
|
|||
}>()
|
||||
|
||||
const dropdown = $ref<any>()
|
||||
const colorMode = useColorModeRef()
|
||||
|
||||
provide(dropdownContextKey, {
|
||||
hide: () => dropdown.hide(),
|
||||
|
@ -12,7 +13,7 @@ provide(dropdownContextKey, {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<VDropdown v-bind="$attrs" ref="dropdown" :class="{ dark: isDark }" :placement="placement || 'auto'">
|
||||
<VDropdown v-bind="$attrs" ref="dropdown" :class="colorMode" :placement="placement || 'auto'">
|
||||
<slot />
|
||||
<template #popper="scope">
|
||||
<slot name="popper" v-bind="scope" />
|
||||
|
|
|
@ -12,7 +12,7 @@ defineProps<{
|
|||
>
|
||||
<div flex justify-between px5 py4>
|
||||
<div flex gap-3 items-center overflow-hidden>
|
||||
<NuxtLink v-if="back" flex="~ gap1" items-center btn-text p-0 @click="$router.go(-1)">
|
||||
<NuxtLink v-show="back" flex="~ gap1" items-center btn-text p-0 @click="$router.go(-1)">
|
||||
<div i-ri:arrow-left-line />
|
||||
</NuxtLink>
|
||||
<div truncate>
|
||||
|
|
|
@ -6,6 +6,7 @@ const emits = defineEmits<{
|
|||
(event: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
const visible = useVModel(props, 'modelValue', emits, { passive: true })
|
||||
const colorMode = useColorModeRef()
|
||||
|
||||
function changeShow() {
|
||||
visible.value = !visible.value
|
||||
|
@ -22,6 +23,10 @@ function clickEvent(mouse: MouseEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleDark() {
|
||||
colorMode.value = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
|
||||
watch(visible, (val) => {
|
||||
if (val && typeof document !== 'undefined')
|
||||
document.addEventListener('click', clickEvent)
|
||||
|
@ -79,7 +84,7 @@ onBeforeUnmount(() => {
|
|||
@click="toggleDark()"
|
||||
>
|
||||
<span class="i-ri:sun-line dark:i-ri:moon-line flex-shrink-0 text-xl mr-4 !align-middle" />
|
||||
{{ !isDark ? $t('menu.toggle_theme.dark') : $t('menu.toggle_theme.light') }}
|
||||
{{ colorMode === 'light' ? $t('menu.toggle_theme.dark') : $t('menu.toggle_theme.light') }}
|
||||
</button>
|
||||
<NuxtLink
|
||||
flex flex-row items-center
|
||||
|
|
|
@ -5,6 +5,11 @@ const timeAgoOptions = useTimeAgoOptions()
|
|||
|
||||
const buildTimeDate = new Date(buildInfo.time)
|
||||
const buildTimeAgo = useTimeAgo(buildTimeDate, timeAgoOptions)
|
||||
|
||||
const colorMode = useColorModeRef()
|
||||
function toggleDark() {
|
||||
colorMode.value = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -9,12 +9,13 @@ const emit = defineEmits<{
|
|||
|
||||
const el = $ref<HTMLElement>()
|
||||
let picker = $ref<Picker>()
|
||||
const colorMode = useColorModeRef()
|
||||
|
||||
async function openEmojiPicker() {
|
||||
await updateCustomEmojis()
|
||||
if (picker) {
|
||||
picker.update({
|
||||
theme: isDark.value ? 'dark' : 'light',
|
||||
theme: colorMode.value,
|
||||
custom: customEmojisData.value,
|
||||
})
|
||||
}
|
||||
|
@ -28,7 +29,7 @@ async function openEmojiPicker() {
|
|||
? emit('select', native)
|
||||
: emit('selectCustom', { src, alt, 'data-emoji-id': name })
|
||||
},
|
||||
theme: isDark.value ? 'dark' : 'light',
|
||||
theme: colorMode.value,
|
||||
custom: customEmojisData.value,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
function setDark(v: boolean) {
|
||||
isDark.value = v
|
||||
import type { ColorMode } from '~/types'
|
||||
|
||||
const colorMode = useColorModeRef()
|
||||
|
||||
function setColorMode(mode: ColorMode) {
|
||||
colorMode.value = mode
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -8,16 +12,16 @@ function setDark(v: boolean) {
|
|||
<div flex="~ gap4" w-full>
|
||||
<button
|
||||
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base
|
||||
:class="isDark ? 'pointer-events-none' : 'filter-saturate-0'"
|
||||
@click="setDark(true)"
|
||||
:class="colorMode === 'dark' ? 'pointer-events-none' : 'filter-saturate-0'"
|
||||
@click="setColorMode('dark')"
|
||||
>
|
||||
<div i-ri:moon-line />
|
||||
Dark Mode
|
||||
</button>
|
||||
<button
|
||||
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base
|
||||
:class="!isDark ? 'pointer-events-none' : 'filter-saturate-0'"
|
||||
@click="setDark(false)"
|
||||
:class="colorMode === 'light' ? 'pointer-events-none' : 'filter-saturate-0'"
|
||||
@click="setColorMode('light')"
|
||||
>
|
||||
<div i-ri:sun-line />
|
||||
Light Mode
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts" setup>
|
||||
import type { FontSize } from '~/composables/fontSize'
|
||||
import type { FontSize } from '~/types'
|
||||
|
||||
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as FontSize[]
|
||||
const fontSize = getFontSize()
|
||||
const fontSize = useFontSizeRef()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -121,13 +121,13 @@ onMounted(async () => {
|
|||
text-left z-10 shadow of-auto
|
||||
>
|
||||
<div
|
||||
v-for="server, idx in filteredServers"
|
||||
:key="server"
|
||||
:value="server"
|
||||
v-for="name, idx in filteredServers"
|
||||
:key="name"
|
||||
:value="name"
|
||||
px-2 py1 font-mono
|
||||
:class="autocompleteIndex === idx ? 'text-primary font-bold' : null"
|
||||
>
|
||||
{{ server }}
|
||||
{{ name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -206,6 +206,7 @@ export const provideGlobalCommands = () => {
|
|||
const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> }
|
||||
const users = useUsers()
|
||||
const masto = useMasto()
|
||||
const colorMode = useColorModeRef()
|
||||
|
||||
useCommand({
|
||||
scope: 'Actions',
|
||||
|
@ -225,10 +226,10 @@ export const provideGlobalCommands = () => {
|
|||
scope: 'Preferences',
|
||||
|
||||
name: () => t('command.toggle_dark_mode'),
|
||||
icon: () => isDark.value ? 'i-ri:sun-line' : 'i-ri:moon-line',
|
||||
icon: () => colorMode.value === 'light' ? 'i-ri:sun-line' : 'i-ri:moon-line',
|
||||
|
||||
onActivate() {
|
||||
toggleDark()
|
||||
colorMode.value = colorMode.value === 'light' ? 'dark' : 'light'
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
export const isDark = useDark()
|
||||
export const toggleDark = useToggle(isDark)
|
|
@ -1,39 +0,0 @@
|
|||
import type { InjectionKey, Ref } from 'vue'
|
||||
import { STORAGE_KEY_FONT_SIZE } from '~/constants'
|
||||
|
||||
const InjectionKeyFontSize = Symbol('fontSize') as InjectionKey<Ref<FontSize>>
|
||||
const DEFAULT = 'md'
|
||||
|
||||
export type FontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||
|
||||
export function getFontSize() {
|
||||
return inject(InjectionKeyFontSize)!
|
||||
}
|
||||
|
||||
const fontSizeMap = {
|
||||
xs: '13px',
|
||||
sm: '14px',
|
||||
md: '15px',
|
||||
lg: '16px',
|
||||
xl: '17px',
|
||||
}
|
||||
|
||||
export async function setupFontSize() {
|
||||
const fontSize = useCookie<FontSize>(STORAGE_KEY_FONT_SIZE, { default: () => DEFAULT })
|
||||
getCurrentInstance()?.appContext.app.provide(InjectionKeyFontSize, fontSize)
|
||||
|
||||
if (!process.server) {
|
||||
watchEffect(() => {
|
||||
document.documentElement.style.setProperty('--font-size', fontSizeMap[fontSize.value || DEFAULT])
|
||||
})
|
||||
}
|
||||
else {
|
||||
useHead({
|
||||
style: [
|
||||
{
|
||||
innerHTML: `:root { --font-size: ${fontSizeMap[fontSize.value || DEFAULT]}; }`,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
14
composables/injections.ts
Normal file
14
composables/injections.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { InjectionKeyColorMode, InjectionKeyFontSize } from '~/constants/symbols'
|
||||
|
||||
export function useFontSizeRef() {
|
||||
return inject(InjectionKeyFontSize)!
|
||||
}
|
||||
|
||||
export function useColorModeRef() {
|
||||
return inject(InjectionKeyColorMode)!
|
||||
}
|
||||
|
||||
export function toggleColorMode() {
|
||||
const colorMode = useColorModeRef()
|
||||
colorMode.value = colorMode.value === 'light' ? 'dark' : 'light'
|
||||
}
|
|
@ -1,15 +1,10 @@
|
|||
import { useRegisterSW } from 'virtual:pwa-register/vue'
|
||||
|
||||
export const usePWA = () => {
|
||||
export function usePWA() {
|
||||
const online = useOnline()
|
||||
|
||||
useHead({
|
||||
meta: [{ id: 'theme-color', name: 'theme-color', content: computed(() => isDark.value ? '#111111' : '#ffffff') }],
|
||||
})
|
||||
|
||||
const {
|
||||
needRefresh,
|
||||
updateServiceWorker,
|
||||
needRefresh, updateServiceWorker,
|
||||
} = useRegisterSW({
|
||||
immediate: true,
|
||||
onRegisteredSW(swUrl, r) {
|
||||
|
|
|
@ -45,9 +45,6 @@ export function setupPageHeader() {
|
|||
titleTemplate: title => `${title ? `${title} | ` : ''}${APP_NAME}${isDev ? ' (dev)' : isPreview ? ' (preview)' : ''}`,
|
||||
link,
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
isDark.value
|
||||
}
|
||||
|
||||
export async function setupI18n() {
|
||||
|
|
|
@ -44,7 +44,7 @@ export function useHightlighter(lang: Lang) {
|
|||
}
|
||||
|
||||
export function useShikiTheme() {
|
||||
return isDark.value ? 'vitesse-dark' : 'vitesse-light'
|
||||
return useColorModeRef().value ? 'vitesse-dark' : 'vitesse-light'
|
||||
}
|
||||
|
||||
export function highlightCode(code: string, lang: Lang) {
|
||||
|
|
|
@ -11,7 +11,6 @@ export const STORAGE_KEY_NOTIFY_TAB = 'elk-notify-tab'
|
|||
export const STORAGE_KEY_FIRST_VISIT = 'elk-first-visit'
|
||||
export const STORAGE_KEY_ZEN_MODE = 'elk-zenmode'
|
||||
export const STORAGE_KEY_LANG = 'elk-lang'
|
||||
export const STORAGE_KEY_FONT_SIZE = 'elk-font-size'
|
||||
export const STORAGE_KEY_FEATURE_FLAGS = 'elk-feature-flags'
|
||||
export const STORAGE_KEY_CUSTOM_EMOJIS = 'elk-custom-emojis'
|
||||
export const STORAGE_KEY_HIDE_EXPLORE_POSTS_TIPS = 'elk-hide-explore-posts-tips'
|
||||
|
@ -20,4 +19,7 @@ export const STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS = 'elk-hide-explore-tags-tips'
|
|||
export const STORAGE_KEY_NOTIFICATION = 'elk-notification'
|
||||
export const STORAGE_KEY_NOTIFICATION_POLICY = 'elk-notification-policy'
|
||||
|
||||
export const COOKIE_KEY_FONT_SIZE = 'elk-font-size'
|
||||
export const COOKIE_KEY_COLOR_MODE = 'elk-color-mode'
|
||||
|
||||
export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/
|
||||
|
|
7
constants/options.ts
Normal file
7
constants/options.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export const fontSizeMap = {
|
||||
xs: '13px',
|
||||
sm: '14px',
|
||||
md: '15px',
|
||||
lg: '16px',
|
||||
xl: '17px',
|
||||
}
|
5
constants/symbols.ts
Normal file
5
constants/symbols.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import type { InjectionKey, Ref } from 'vue'
|
||||
import type { ColorMode, FontSize } from '~/types'
|
||||
|
||||
export const InjectionKeyFontSize = Symbol('font-size') as InjectionKey<Ref<FontSize>>
|
||||
export const InjectionKeyColorMode = Symbol('color-mode') as InjectionKey<Ref<ColorMode>>
|
41
plugins/setup-color-mode.ts
Normal file
41
plugins/setup-color-mode.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import type { ColorMode } from '~/types'
|
||||
import { InjectionKeyColorMode } from '~/constants/symbols'
|
||||
import { COOKIE_KEY_COLOR_MODE } from '~/constants'
|
||||
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
const cookieColorMode = useCookie<ColorMode | null>(COOKIE_KEY_COLOR_MODE, { default: () => null })
|
||||
|
||||
const preferColorMode = process.server ? computed(() => 'light') : usePreferredColorScheme()
|
||||
const colorMode = computed<ColorMode>({
|
||||
get() {
|
||||
return cookieColorMode.value || preferColorMode.value as ColorMode
|
||||
},
|
||||
set(value) {
|
||||
cookieColorMode.value = value
|
||||
},
|
||||
})
|
||||
|
||||
nuxt.vueApp.provide(InjectionKeyColorMode, colorMode)
|
||||
|
||||
if (process.server) {
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
class: colorMode,
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
watchEffect(() => {
|
||||
document.documentElement.classList.toggle('dark', colorMode.value === 'dark')
|
||||
document.documentElement.classList.toggle('light', colorMode.value === 'light')
|
||||
})
|
||||
}
|
||||
|
||||
useHead({
|
||||
meta: [{
|
||||
id: 'theme-color',
|
||||
name: 'theme-color',
|
||||
content: computed(() => colorMode.value === 'dark' ? '#111111' : '#ffffff'),
|
||||
}],
|
||||
})
|
||||
})
|
25
plugins/setup-font-size.ts
Normal file
25
plugins/setup-font-size.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import type { FontSize } from '~/types'
|
||||
import { InjectionKeyFontSize } from '~/constants/symbols'
|
||||
import { COOKIE_KEY_FONT_SIZE } from '~/constants'
|
||||
import { fontSizeMap } from '~/constants/options'
|
||||
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
const DEFAULT = 'md'
|
||||
const cookieFontSize = useCookie<FontSize>(COOKIE_KEY_FONT_SIZE, { default: () => DEFAULT })
|
||||
nuxt.vueApp.provide(InjectionKeyFontSize, cookieFontSize)
|
||||
|
||||
if (!process.server) {
|
||||
watchEffect(() => {
|
||||
document.documentElement.style.setProperty('--font-size', fontSizeMap[cookieFontSize.value || DEFAULT])
|
||||
})
|
||||
}
|
||||
else {
|
||||
useHead({
|
||||
style: [
|
||||
{
|
||||
innerHTML: `:root { --font-size: ${fontSizeMap[cookieFontSize.value || DEFAULT]}; }`,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
})
|
|
@ -72,3 +72,6 @@ export interface BuildInfo {
|
|||
time: number
|
||||
branch: string
|
||||
}
|
||||
|
||||
export type FontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||
export type ColorMode = 'light' | 'dark'
|
||||
|
|
Loading…
Reference in a new issue