fix: rework setup to improve SSR compatibility

main
Anthony Fu 2022-12-28 02:06:54 +01:00
parent fd7d30a38a
commit d8d163dbd0
22 changed files with 137 additions and 73 deletions

View File

@ -1,6 +1,5 @@
<script setup lang="ts">
setupI18n()
setupFontSize()
setupPageHeader()
setupEmojis()
provideGlobalCommands()

View File

@ -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" />

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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,
})
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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'
},
})

View File

@ -1,2 +0,0 @@
export const isDark = useDark()
export const toggleDark = useToggle(isDark)

View File

@ -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
View 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'
}

View File

@ -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) {

View File

@ -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() {

View File

@ -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) {

View File

@ -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
View File

@ -0,0 +1,7 @@
export const fontSizeMap = {
xs: '13px',
sm: '14px',
md: '15px',
lg: '16px',
xl: '17px',
}

5
constants/symbols.ts Normal file
View 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>>

View 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'),
}],
})
})

View 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]}; }`,
},
],
})
}
})

View File

@ -72,3 +72,6 @@ export interface BuildInfo {
time: number
branch: string
}
export type FontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
export type ColorMode = 'light' | 'dark'