diff --git a/src/module.ts b/src/module.ts index 73dd146..bd402ae 100644 --- a/src/module.ts +++ b/src/module.ts @@ -13,23 +13,7 @@ import { import { useContentStorage } from './runtime/storage/StorageManagement' import { updateCheck } from './runtime/update' - -export interface CmsUser { - name: string - password: string -} - -export interface ModuleOptions { - users: CmsUser[] - secret: string - projectLocation: string - storage: { - type: 'azure-app-configuration' | 'cloudflare-kv-binding' | 'fs' | 'github' | 'mongodb' | 'netlify-blobs' | 'planetscale' | 'redis' | 'vercel-kv' - options: object - } - storageKey: string - deployHookURL?: string -} +import type { ModuleOptions } from './runtime/types/ModuleTypes' export default defineNuxtModule({ meta: { @@ -48,8 +32,13 @@ export default defineNuxtModule({ }, }, storageKey: 'katze_content.json', + addons: { + deviceRecognition: { + defaultUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36', + }, + }, }, - async setup(_options, _nuxt) { + async setup(_options: ModuleOptions, _nuxt) { const resolver = createResolver(import.meta.url) updateCheck().then( ({ currentVersion, latestVersion }) => { @@ -78,6 +67,9 @@ export default defineNuxtModule({ _nuxt.options.runtimeConfig.projectLocation = _options.projectLocation + (_options.projectLocation.endsWith('/') ? '' : '/') _nuxt.options.runtimeConfig.deployHookURL = _options.deployHookURL + // ADDONS + _nuxt.options.runtimeConfig.public.deviceRecognition = _options.addons.deviceRecognition + // LOAD CONTENT STORAGE const contentStorage = await useContentStorage(_nuxt.options.runtimeConfig) let content = await contentStorage.getItem(_options.storageKey) diff --git a/src/runtime/composables/useDeviceType.ts b/src/runtime/composables/useDeviceType.ts new file mode 100644 index 0000000..0650f26 --- /dev/null +++ b/src/runtime/composables/useDeviceType.ts @@ -0,0 +1,46 @@ +import { onMounted, onUnmounted, ref, useNuxtApp, useRuntimeConfig } from '#imports' +import type { DeviceTypes } from '~/src/runtime/types/DeviceTypes' +import type { AddonDeviceRecognition } from '~/src/runtime/types/ModuleTypes' + +export const useDeviceType = () => { + const deviceType = useNuxtApp().$device as DeviceTypes + const { isTablet, isDesktop } = deviceType + + const runtimeConfig = useRuntimeConfig() + const deviceRecognition = runtimeConfig.public.deviceRecognition as AddonDeviceRecognition + const { responsiveContainerClass } = deviceRecognition + + const isMobile = ref(deviceType.isMobile) + const resizeListener = (containerClass: string) => { + // get div @container width + const container = document.querySelector(containerClass) + if (container) { + const containerWidth = container.clientWidth + isMobile.value = containerWidth < 768 + } + } + + const addResizeListener = (containerClass: string) => { + // add resize listener to .page-size element + const resizeObserver = new ResizeObserver(() => resizeListener(containerClass)) + const container = document.querySelector(containerClass) + if (container) { + resizeObserver.observe(container) + } + resizeListener(containerClass) + } + if (responsiveContainerClass) { + const resizeEvent = () => resizeListener(responsiveContainerClass) + onMounted(() => { + if (responsiveContainerClass) { + addResizeListener(responsiveContainerClass) + window.addEventListener('resize', resizeEvent) + } + }) + + onUnmounted(() => { + window.removeEventListener('resize', resizeEvent) + }) + } + return { isMobile, isTablet, isDesktop } +} diff --git a/src/runtime/plugins/device.plugin.ts b/src/runtime/plugins/device.plugin.ts new file mode 100644 index 0000000..ed7b0e4 --- /dev/null +++ b/src/runtime/plugins/device.plugin.ts @@ -0,0 +1,66 @@ +import { reactive } from 'vue' +import type { AddonDeviceRecognition } from '../types/ModuleTypes' +import { defineNuxtPlugin, useRuntimeConfig, useRequestHeaders } from '#imports' +import type { DeviceTypes } from '~/src/runtime/types/DeviceTypes' + +export default defineNuxtPlugin((nuxtApp) => { + const runtimeConfig = useRuntimeConfig() + + const deviceRecognition = runtimeConfig.public.deviceRecognition as AddonDeviceRecognition + const { defaultUserAgent } = deviceRecognition + + // Server Side + if (nuxtApp.ssrContext) { + const headers = useRequestHeaders() + const userAgent = headers['user-agent'] || defaultUserAgent + + const types = reactive(getDeviceType(userAgent, headers)) + + return { + provide: { + device: types, + }, + } + } + + // Client Side + const userAgent = navigator.userAgent || defaultUserAgent + const types = reactive(getDeviceType(userAgent)) + + return { + provide: { + device: types, + }, + } +}) + +const getDeviceType = (userAgent: string, headers?: Record): DeviceTypes => { + let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent) + let isTablet = /iPad/i.test(userAgent) + let isDesktop = !isMobile && !isTablet + + if (!headers) { + return { + isMobile, + isTablet, + isDesktop, + } + } + + if (userAgent === 'Amazon CloudFront') { + isMobile = headers['cloudfront-is-mobile-viewer'] === 'true' + isTablet = headers['cloudfront-is-tablet-viewer'] === 'true' + isDesktop = headers['cloudfront-is-desktop-viewer'] === 'true' + } + else if (headers['cf-device-type']) { // Cloudflare + isMobile = headers['cf-device-type'] === 'mobile' + isTablet = headers['cf-device-type'] === 'tablet' + isDesktop = headers['cf-device-type'] === 'desktop' + } + + return { + isMobile, + isTablet, + isDesktop, + } +} diff --git a/src/runtime/types/DeviceTypes.ts b/src/runtime/types/DeviceTypes.ts new file mode 100644 index 0000000..588951b --- /dev/null +++ b/src/runtime/types/DeviceTypes.ts @@ -0,0 +1,5 @@ +export interface DeviceTypes { + isMobile: boolean + isTablet: boolean + isDesktop: boolean +} diff --git a/src/runtime/types/ModuleTypes.ts b/src/runtime/types/ModuleTypes.ts new file mode 100644 index 0000000..0d474d0 --- /dev/null +++ b/src/runtime/types/ModuleTypes.ts @@ -0,0 +1,24 @@ +export interface CmsUser { + name: string + password: string +} + +export interface ModuleOptions { + users: CmsUser[] + secret: string + projectLocation: string + storage: { + type: 'azure-app-configuration' | 'cloudflare-kv-binding' | 'fs' | 'github' | 'mongodb' | 'netlify-blobs' | 'planetscale' | 'redis' | 'vercel-kv' + options: object + } + storageKey: string + deployHookURL?: string + addons: { + deviceRecognition: AddonDeviceRecognition + } +} + +export interface AddonDeviceRecognition { + defaultUserAgent: string + responsiveContainerClass?: string +}