Set up content layer

This commit is contained in:
Dominik Milacher 2025-10-05 15:09:19 +02:00
parent b90285a425
commit 2a1ba854ba
24 changed files with 297 additions and 38 deletions

View File

@ -1,5 +1,3 @@
<template>
<div>
<HelloWorld />
</div>
<NuxtPage/>
</template>

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
</script>
<template>
<LocaleSwitcher/>
<br/>
<VariantSwitcher/>
</template>

View File

@ -2,5 +2,5 @@
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
extends: [ '@layers/base' ]
extends: [ '@layers/content' ]
})

View File

@ -13,6 +13,6 @@
"nuxt": "^4.1.2",
"vue": "^3.5.22",
"vue-router": "^4.5.1",
"@layers/base": "workspace:0.0.0"
"@layers/content": "workspace:0.0.0"
}
}

View File

@ -1,14 +0,0 @@
export default defineAppConfig({
myLayer: {
name: 'Hello from Nuxt layer'
}
})
declare module '@nuxt/schema' {
interface AppConfigInput {
myLayer?: {
/** Project name */
name?: string
}
}
}

View File

@ -1,10 +0,0 @@
<script setup lang="ts">
const { myLayer } = useAppConfig()
</script>
<template>
<div>
<h1>Hello World!</h1>
<pre>{{ myLayer }}</pre>
</div>
</template>

View File

@ -1,3 +1,3 @@
<template>
<HelloWorld />
<NuxtPage/>
</template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
const { locale, locales, variant, variants, c } = useContent()
</script>
<template>
<p>locale {{ locale }}</p>
<p>locales {{ locales }}</p>
<p>variant {{ variant }}</p>
<p>variants {{ variants }}</p>
<LocaleSwitcher/>
<br/>
<VariantSwitcher/>
<p>{{ c.cookies.title }}</p>
</template>

View File

@ -0,0 +1,24 @@
export default defineAppConfig({
content: {
defaultLocale: 'de',
locales: [
{code: 'de', name: {'de': 'Deutsch', 'en': 'German'}},
{code: 'en', name: {'de': 'Englisch', 'en': 'English'}},
],
defaultVariant: 'summer',
variants: [
{code: 'summer', name: {'de': 'Sommer', 'en': 'Summer'}, icon: 'i-lucide-sun'},
{code: 'winter', name: {'de': 'Winter', 'en': 'Winter'}, icon: 'i-lucide-snowflake'}
],
cookieMaxAge: 60 * 60 * 24 * 365
}
})
declare module '@nuxt/schema' {
interface AppConfigInput {
myLayer?: {
/** Project name */
name?: string
}
}
}

View File

@ -0,0 +1,7 @@
cookies:
title:
de: "Ihre Privatsphäre ist uns wichtig"
en: "We value your privacy"
description:
de: "Wir verwenden keine Tracking-Cookies. Genießen Sie Ihren Aufenthalt auf unserer Website!"
en: "We do not use any tracking cookies. Enjoy your stay on our website!"

View File

@ -0,0 +1,10 @@
<script setup lang="ts">
const { locale, locales, matrixPath } = useContent()
const candidates = computed(() => locales.filter(l => l !== locale.value))
</script>
<template>
<NuxtLink v-for="candidate in candidates" :key="candidate" :to="matrixPath({locale: candidate})">
{{ candidate.toUpperCase() }}
</NuxtLink>
</template>

View File

@ -0,0 +1,10 @@
<script setup lang="ts">
const { variant, variants, matrixPath } = useContent()
const candidates = computed(() => variants.filter(v => v !== variant.value))
</script>
<template>
<NuxtLink v-for="candidate in candidates" :key="candidate" :to="matrixPath({variant: candidate})">
{{ candidate.toUpperCase() }}
</NuxtLink>
</template>

View File

@ -0,0 +1,171 @@
import { computed } from 'vue'
import { useRoute } from '#app'
import content from '~/assets/content.yaml'
const specializations: Record<string, object> = {}
function buildSpecialization(locale: string, variant: string): object {
const config = useAppConfig()
const locales = config.content.locales.map(l => l.code)
const variants = config.content.variants.map(v => v.code)
const allTags = new Set([...locales, ...variants])
function split(key: string): {base?: string; tags: string[]} {
const parts = key.split('@')
if (allTags.has(parts[0])) {
return {base: undefined, tags: parts}
}
return {base: parts[0], tags: parts.slice(1)}
}
function relevant(tags: string[]): boolean {
if (tags.length < 1) {
return true
}
for (const tag of tags) {
if (tag !== locale && tag !== variant) {
return false
}
}
return true
}
function filterObject(object: any): any {
if (Array.isArray(object)) {
return object.map(e => filterObject(e)).filter(e => e !== undefined)
}
if (typeof object !== 'object') {
return object
}
const copy: Record<string, any> = {}
let anonymous: [number, any][] = []
for (const key in object) {
const {base, tags} = split(key)
if (!relevant(tags)) {
continue
}
const value = filterObject(object[key])
if (base === undefined) {
anonymous.push([tags.length, value])
} else {
copy[base] = value
}
}
if (Object.keys(copy).length > 0 && anonymous.length > 0) {
console.warn(`Invalid mixing of keys in object ${object}`)
}
if (Object.keys(copy).length > 0) {
return copy
}
anonymous.sort((a, b) => b[0] - a[0])
if (anonymous[0][0] > 2) {
console.warn(`More than two tags per key in object ${object}`)
}
return anonymous[0][1]
}
return filterObject(content)
}
function getSpecialization(locale: string, variant: string): object {
const key = locale + '/' + variant
if (!(key in specializations)) {
specializations[key] = buildSpecialization(locale, variant)
}
return specializations[key]
}
export function useContent() {
const config = useAppConfig()
const route = useRoute()
console.log("miau")
console.log(route)
const locales = config.content.locales.map(l => l.code)
const variants = config.content.variants.map(v => v.code)
const localeCookie = useCookie<string>('locale', {
sameSite: 'lax',
maxAge: config.content.cookieMaxAge
})
const variantCookie = useCookie<string>('variant', {
sameSite: 'lax',
maxAge: config.content.cookieMaxAge
})
const currentLocale = computed<string>(() => route.params.locale || localeCookie.value || config.content.defaultLocale)
const currentVariant = computed<string>(() => route.params.variant || variantCookie.value || config.content.defaultVariant)
function preferLocale(locale: string) {
if (locale in locales) {
localeCookie.value = locale
}
}
function preferVariant(variant: string) {
if (variant in variants) {
variantCookie.value = variant
}
}
function matrixPath(options: { page?: string; anchor?: string; locale?: string; variant?: string }): string {
let prefix: string[] = []
const showLocale = locales.length > 1
const showVariant = variants.length > 1
if (showLocale) {
prefix.push(options.locale ?? currentLocale.value)
}
if (showVariant) {
prefix.push(options.variant ?? currentVariant.value)
}
// TODO preserve anchor when taking route.path
let page = (options.page ?? route.path).split('/').filter(Boolean)
if (!options.page) {
page = page.slice([showLocale, showVariant].filter(Boolean).length)
}
const base = '/' + [...prefix, ...page].join('/')
return options.anchor ? `${base}#${options.anchor}` : base
}
function p(page: string, anchor?: string): string {
return matrixPath({page: page, anchor: anchor})
}
const specialization = computed<any>(() => getSpecialization(currentLocale.value, currentVariant.value))
return {
locale: currentLocale,
locales: locales,
preferLocale: preferLocale,
variant: currentVariant,
variants: variants,
preferVariant: preferVariant,
matrixPath: matrixPath,
p: p,
c: specialization,
}
}

View File

@ -1,4 +1,10 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
import yaml from '@rollup/plugin-yaml'
export default defineNuxtConfig({
devtools: { enabled: true },
vite: {
plugins: [ yaml() ]
}
})

View File

@ -1,5 +1,5 @@
{
"name": "@layers/base",
"name": "@layers/content",
"type": "module",
"version": "0.0.0",
"main": "./nuxt.config.ts",
@ -11,8 +11,12 @@
"preview": "nuxt preview .playground",
"lint": "eslint ."
},
"dependencies": {
"vue-router": "^4.5.1"
},
"devDependencies": {
"@nuxt/eslint": "latest",
"@rollup/plugin-yaml": "^4.1.2",
"eslint": "^9.36.0",
"nuxt": "^4.1.2",
"typescript": "^5.9.2",

44
pnpm-lock.yaml generated
View File

@ -20,9 +20,9 @@ importers:
apps/panoramablick-saalbach.at:
dependencies:
'@layers/base':
'@layers/content':
specifier: workspace:0.0.0
version: link:../../packages/layers/base
version: link:../../packages/layers/content
nuxt:
specifier: ^4.1.2
version: 4.1.2(@parcel/watcher@2.5.1)(@types/node@22.15.3)(@vue/compiler-sfc@3.5.22)(db0@0.3.4)(eslint@9.37.0(jiti@2.6.1))(ioredis@5.8.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.52.4)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.9(@types/node@22.15.3)(jiti@2.6.1)(terser@5.44.0)(yaml@2.8.1))(yaml@2.8.1)
@ -33,11 +33,18 @@ importers:
specifier: ^4.5.1
version: 4.5.1(vue@3.5.22(typescript@5.9.2))
packages/layers/base:
packages/layers/content:
dependencies:
vue-router:
specifier: ^4.5.1
version: 4.5.1(vue@3.5.22(typescript@5.9.2))
devDependencies:
'@nuxt/eslint':
specifier: latest
version: 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.22)(eslint@9.37.0(jiti@2.6.1))(magicast@0.3.5)(typescript@5.9.2)(vite@7.1.9(@types/node@22.15.3)(jiti@2.6.1)(terser@5.44.0)(yaml@2.8.1))
'@rollup/plugin-yaml':
specifier: ^4.1.2
version: 4.1.2(rollup@4.52.4)
eslint:
specifier: ^9.36.0
version: 9.37.0(jiti@2.6.1)
@ -1025,6 +1032,15 @@ packages:
rollup:
optional: true
'@rollup/plugin-yaml@4.1.2':
resolution: {integrity: sha512-RpupciIeZMUqhgFE97ba0s98mOFS7CWzN3EJNhJkqSv9XLlWYtwVdtE6cDw6ASOF/sZVFS7kRJXftaqM2Vakdw==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
@ -3464,6 +3480,10 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
tosource@2.0.0-alpha.3:
resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==}
engines: {node: '>=10'}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@ -4516,7 +4536,7 @@ snapshots:
'@nuxt/eslint-plugin@1.9.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2)':
dependencies:
'@typescript-eslint/types': 8.40.0
'@typescript-eslint/types': 8.45.0
'@typescript-eslint/utils': 8.40.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2)
eslint: 9.37.0(jiti@2.6.1)
transitivePeerDependencies:
@ -4974,6 +4994,14 @@ snapshots:
optionalDependencies:
rollup: 4.52.4
'@rollup/plugin-yaml@4.1.2(rollup@4.52.4)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.52.4)
js-yaml: 4.1.0
tosource: 2.0.0-alpha.3
optionalDependencies:
rollup: 4.52.4
'@rollup/pluginutils@5.3.0(rollup@4.52.4)':
dependencies:
'@types/estree': 1.0.8
@ -5116,7 +5144,7 @@ snapshots:
'@typescript-eslint/project-service@8.40.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2)
'@typescript-eslint/types': 8.40.0
'@typescript-eslint/types': 8.45.0
debug: 4.4.1
typescript: 5.9.2
transitivePeerDependencies:
@ -5936,14 +5964,14 @@ snapshots:
eslint-plugin-import-lite@0.3.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2):
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1))
'@typescript-eslint/types': 8.40.0
'@typescript-eslint/types': 8.45.0
eslint: 9.37.0(jiti@2.6.1)
optionalDependencies:
typescript: 5.9.2
eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.40.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.37.0(jiti@2.6.1)):
dependencies:
'@typescript-eslint/types': 8.40.0
'@typescript-eslint/types': 8.45.0
comment-parser: 1.4.1
debug: 4.4.1
eslint: 9.37.0(jiti@2.6.1)
@ -7647,6 +7675,8 @@ snapshots:
toidentifier@1.0.1: {}
tosource@2.0.0-alpha.3: {}
totalist@3.0.1: {}
tr46@0.0.3: {}