kmcom-nuxt-layers 2.3.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/docs/ARCHITECTURE.md +4 -0
- package/layers/auth/app/middleware/auth.ts +4 -0
- package/layers/auth/nuxt.config.ts +31 -0
- package/layers/auth/package.json +15 -0
- package/layers/auth/server/routes/auth/github.get.ts +13 -0
- package/layers/auth/tsconfig.json +7 -0
- package/layers/baseline/app/components/BaselineStatus.vue +17 -0
- package/layers/baseline/app/plugins/baseline-status.client.ts +3 -0
- package/layers/baseline/nuxt.config.ts +24 -0
- package/layers/baseline/package.json +14 -0
- package/layers/baseline/tsconfig.json +7 -0
- package/layers/content/app/composables/createPortfolioComposables.ts +2 -1
- package/layers/content/app/composables/useBlogPosts.ts +2 -1
- package/layers/content/app/composables/useGalleryItems.ts +14 -8
- package/layers/content/app/composables/usePortfolioItems.ts +18 -10
- package/layers/database/nuxt.config.ts +30 -0
- package/layers/database/package.json +16 -0
- package/layers/database/server/utils/drizzle.ts +5 -0
- package/layers/database/server/utils/sql.ts +7 -0
- package/layers/database/tsconfig.json +7 -0
- package/layers/feeds/server/utils/content-adapter.ts +6 -0
- package/layers/forms/app/components/Form/Contact.vue +4 -13
- package/layers/forms/app/utils/contact-schema.ts +9 -0
- package/layers/forms/server/api/contact.post.ts +23 -6
- package/layers/forms/server/api/forms/status.get.ts +1 -3
- package/layers/layout/app/utils/gridPlacementStyle.ts +10 -9
- package/layers/metadata/app/composables/useMetadataItem.ts +11 -0
- package/layers/metadata/app/composables/useMetadataSearch.ts +25 -0
- package/layers/metadata/nuxt.config.ts +27 -0
- package/layers/metadata/package.json +12 -0
- package/layers/metadata/server/api/metadata/lookup.get.ts +40 -0
- package/layers/metadata/server/api/metadata/providers.get.ts +7 -0
- package/layers/metadata/server/api/metadata/search.get.ts +17 -0
- package/layers/metadata/server/api/metadata/sync.post.ts +40 -0
- package/layers/metadata/server/utils/metadata/cache.ts +36 -0
- package/layers/metadata/server/utils/metadata/errors.ts +24 -0
- package/layers/metadata/server/utils/metadata/provider-registry.ts +15 -0
- package/layers/metadata/server/utils/metadata/search.ts +42 -0
- package/layers/metadata/tsconfig.json +7 -0
- package/layers/metadata-comicvine/nuxt.config.ts +36 -0
- package/layers/metadata-comicvine/package.json +12 -0
- package/layers/metadata-comicvine/server/plugins/comicvine-register.ts +3 -0
- package/layers/metadata-comicvine/server/utils/metadata-comicvine/client.ts +52 -0
- package/layers/metadata-comicvine/server/utils/metadata-comicvine/normalise.ts +55 -0
- package/layers/metadata-comicvine/server/utils/metadata-comicvine/provider.ts +39 -0
- package/layers/metadata-comicvine/server/utils/metadata-comicvine/types.ts +36 -0
- package/layers/metadata-comicvine/tsconfig.json +7 -0
- package/layers/metadata-google-books/nuxt.config.ts +32 -0
- package/layers/metadata-google-books/package.json +12 -0
- package/layers/metadata-google-books/server/plugins/google-books-register.ts +3 -0
- package/layers/metadata-google-books/server/utils/metadata-google-books/client.ts +23 -0
- package/layers/metadata-google-books/server/utils/metadata-google-books/normalise.ts +39 -0
- package/layers/metadata-google-books/server/utils/metadata-google-books/provider.ts +21 -0
- package/layers/metadata-google-books/server/utils/metadata-google-books/types.ts +25 -0
- package/layers/metadata-google-books/tsconfig.json +7 -0
- package/layers/metadata-openlibrary/nuxt.config.ts +18 -0
- package/layers/metadata-openlibrary/package.json +12 -0
- package/layers/metadata-openlibrary/server/plugins/openlibrary-register.ts +3 -0
- package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/client.ts +30 -0
- package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/normalise.ts +81 -0
- package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/provider.ts +25 -0
- package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/types.ts +48 -0
- package/layers/metadata-openlibrary/tsconfig.json +7 -0
- package/layers/routing/server/api/feature-flags.get.ts +6 -0
- package/layers/scroll/app/plugins/locomotive-scroll.client.ts +6 -1
- package/layers/starter/app/app.config.ts +10 -0
- package/layers/starter/app/components/StarterHome.vue +34 -25
- package/layers/theme/app/components/ThemePicker/Menu.vue +8 -2
- package/layers/theme/nuxt.config.ts +1 -1
- package/layers/theme/server/plugins/theme-fouc.ts +2 -57
- package/layers/theme/server/utils/fouc-config.ts +85 -0
- package/package.json +12 -3
- package/types/auth.ts +7 -0
- package/types/index.ts +2 -0
- package/layers/content/app/composables/useCollectionItems.ts +0 -28
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { MetadataRecord, MetadataCreator } from '#layers/metadata/shared/types'
|
|
2
|
+
import type { OpenLibrarySearchDoc, OpenLibraryWork, OpenLibraryEdition } from './types'
|
|
3
|
+
|
|
4
|
+
// fallow-ignore-next-line complexity
|
|
5
|
+
export function normaliseOpenLibrarySearchDoc(doc: OpenLibrarySearchDoc): MetadataRecord {
|
|
6
|
+
const creators: MetadataCreator[] = (doc.author_name ?? []).map((name, i) => ({
|
|
7
|
+
name,
|
|
8
|
+
role: 'author',
|
|
9
|
+
providerId: doc.author_key?.[i]?.replace('/authors/', '') ?? undefined,
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const id = doc.key.replace('/works/', '')
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
id: `openlibrary:work:${id}`,
|
|
16
|
+
provider: 'openlibrary',
|
|
17
|
+
providerId: id,
|
|
18
|
+
mediaType: 'book',
|
|
19
|
+
title: doc.title,
|
|
20
|
+
subtitle: doc.subtitle,
|
|
21
|
+
creators: creators.length ? creators : undefined,
|
|
22
|
+
publisher: doc.publisher?.[0] ?? undefined,
|
|
23
|
+
publishedAt: doc.first_publish_year ? String(doc.first_publish_year) : undefined,
|
|
24
|
+
coverUrl: doc.cover_i ? openLibraryCoverUrl(doc.cover_i) : undefined,
|
|
25
|
+
identifiers: {
|
|
26
|
+
isbn10: doc.isbn?.find((i) => i.length === 10),
|
|
27
|
+
isbn13: doc.isbn?.find((i) => i.length === 13),
|
|
28
|
+
openLibraryId: id,
|
|
29
|
+
},
|
|
30
|
+
sourceUrl: `https://openlibrary.org${doc.key}`,
|
|
31
|
+
raw: doc,
|
|
32
|
+
lastSyncedAt: new Date().toISOString(),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// fallow-ignore-next-line complexity
|
|
37
|
+
export function normaliseOpenLibraryWork(work: OpenLibraryWork): MetadataRecord {
|
|
38
|
+
const id = work.key.replace('/works/', '')
|
|
39
|
+
const desc = typeof work.description === 'string' ? work.description : work.description?.value
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
id: `openlibrary:work:${id}`,
|
|
43
|
+
provider: 'openlibrary',
|
|
44
|
+
providerId: id,
|
|
45
|
+
mediaType: 'book',
|
|
46
|
+
title: work.title,
|
|
47
|
+
subtitle: work.subtitle,
|
|
48
|
+
description: desc,
|
|
49
|
+
coverUrl: work.covers?.[0] ? openLibraryCoverUrl(work.covers[0]) : undefined,
|
|
50
|
+
publishedAt: work.first_publish_date,
|
|
51
|
+
identifiers: { openLibraryId: id },
|
|
52
|
+
sourceUrl: `https://openlibrary.org${work.key}`,
|
|
53
|
+
raw: work,
|
|
54
|
+
lastSyncedAt: new Date().toISOString(),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// fallow-ignore-next-line complexity
|
|
59
|
+
export function normaliseOpenLibraryEdition(edition: OpenLibraryEdition): MetadataRecord {
|
|
60
|
+
const id = edition.key.replace('/books/', '')
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
id: `openlibrary:edition:${id}`,
|
|
64
|
+
provider: 'openlibrary',
|
|
65
|
+
providerId: id,
|
|
66
|
+
mediaType: 'book',
|
|
67
|
+
title: edition.title,
|
|
68
|
+
subtitle: edition.subtitle,
|
|
69
|
+
publisher: edition.publishers?.[0] ?? undefined,
|
|
70
|
+
publishedAt: edition.publish_date,
|
|
71
|
+
coverUrl: edition.covers?.[0] ? openLibraryCoverUrl(edition.covers[0]) : undefined,
|
|
72
|
+
identifiers: {
|
|
73
|
+
isbn10: edition.isbn_10?.[0],
|
|
74
|
+
isbn13: edition.isbn_13?.[0],
|
|
75
|
+
openLibraryId: id,
|
|
76
|
+
},
|
|
77
|
+
sourceUrl: `https://openlibrary.org${edition.key}`,
|
|
78
|
+
raw: edition,
|
|
79
|
+
lastSyncedAt: new Date().toISOString(),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MetadataProvider } from '#layers/metadata/shared/types'
|
|
2
|
+
|
|
3
|
+
export const openLibraryProvider: MetadataProvider = {
|
|
4
|
+
id: 'openlibrary',
|
|
5
|
+
label: 'Open Library',
|
|
6
|
+
mediaTypes: ['book', 'graphic-novel', 'collected-edition'],
|
|
7
|
+
|
|
8
|
+
async search({ query, limit = 10 }) {
|
|
9
|
+
const res = await searchOpenLibrary(query, limit)
|
|
10
|
+
return res.docs.map(normaliseOpenLibrarySearchDoc)
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async lookup({ providerId, resourceType = 'work' }) {
|
|
14
|
+
if (resourceType === 'edition') {
|
|
15
|
+
const edition = await lookupOpenLibraryEdition(providerId)
|
|
16
|
+
return normaliseOpenLibraryEdition(edition)
|
|
17
|
+
}
|
|
18
|
+
const work = await lookupOpenLibraryWork(providerId)
|
|
19
|
+
return normaliseOpenLibraryWork(work)
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async sync({ providerId, resourceType = 'work' }) {
|
|
23
|
+
return (await openLibraryProvider.lookup!({ provider: 'openlibrary', providerId, resourceType }))!
|
|
24
|
+
},
|
|
25
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type OpenLibrarySearchDoc = {
|
|
2
|
+
key: string
|
|
3
|
+
title: string
|
|
4
|
+
subtitle?: string
|
|
5
|
+
author_name?: string[]
|
|
6
|
+
author_key?: string[]
|
|
7
|
+
publisher?: string[]
|
|
8
|
+
first_publish_year?: number
|
|
9
|
+
isbn?: string[]
|
|
10
|
+
cover_i?: number
|
|
11
|
+
number_of_pages_median?: number
|
|
12
|
+
language?: string[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type OpenLibrarySearchResponse = {
|
|
16
|
+
numFound: number
|
|
17
|
+
docs: OpenLibrarySearchDoc[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type OpenLibraryWork = {
|
|
21
|
+
key: string
|
|
22
|
+
title: string
|
|
23
|
+
subtitle?: string
|
|
24
|
+
description?: { value: string } | string
|
|
25
|
+
authors?: Array<{ author: { key: string }; type: { key: string } }>
|
|
26
|
+
first_publish_date?: string
|
|
27
|
+
covers?: number[]
|
|
28
|
+
subjects?: string[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type OpenLibraryEdition = {
|
|
32
|
+
key: string
|
|
33
|
+
title: string
|
|
34
|
+
subtitle?: string
|
|
35
|
+
publishers?: string[]
|
|
36
|
+
publish_date?: string
|
|
37
|
+
isbn_13?: string[]
|
|
38
|
+
isbn_10?: string[]
|
|
39
|
+
covers?: number[]
|
|
40
|
+
works?: Array<{ key: string }>
|
|
41
|
+
number_of_pages?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type OpenLibraryAuthor = {
|
|
45
|
+
key: string
|
|
46
|
+
name: string
|
|
47
|
+
personal_name?: string
|
|
48
|
+
}
|
|
@@ -47,7 +47,12 @@ export default defineNuxtPlugin({
|
|
|
47
47
|
|
|
48
48
|
if (smoothScroll === true) {
|
|
49
49
|
nuxtApp.hook('app:mounted', () => nextTick(init))
|
|
50
|
-
nuxtApp.hook('page:finish', () =>
|
|
50
|
+
nuxtApp.hook('page:finish', () =>
|
|
51
|
+
nextTick(() => {
|
|
52
|
+
instance.value?.scrollTo(0, { immediate: true })
|
|
53
|
+
ScrollTrigger.refresh()
|
|
54
|
+
})
|
|
55
|
+
)
|
|
51
56
|
} else if (Array.isArray(smoothScroll)) {
|
|
52
57
|
addRouteMiddleware((to, from) => {
|
|
53
58
|
if (smoothScroll.includes(to.path)) {
|
|
@@ -11,36 +11,45 @@ gsap.registerPlugin(ScrollTrigger)
|
|
|
11
11
|
const heroRef = ref<HTMLElement | null>(null)
|
|
12
12
|
const sectionsRef = ref<HTMLElement | null>(null)
|
|
13
13
|
|
|
14
|
+
let ctx: ReturnType<typeof gsap.context> | null = null
|
|
15
|
+
|
|
14
16
|
onMounted(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
ctx = gsap.context(() => {
|
|
18
|
+
if (heroRef.value) {
|
|
19
|
+
gsap.from(Array.from(heroRef.value.children), {
|
|
20
|
+
y: 24,
|
|
21
|
+
opacity: 0,
|
|
22
|
+
duration: 0.7,
|
|
23
|
+
stagger: 0.1,
|
|
24
|
+
ease: 'power3.out',
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (sectionsRef.value) {
|
|
29
|
+
gsap.from(sectionsRef.value.querySelectorAll('.layer-card'), {
|
|
30
|
+
scrollTrigger: {
|
|
31
|
+
trigger: sectionsRef.value,
|
|
32
|
+
start: 'top 85%',
|
|
33
|
+
toggleActions: 'play none none none',
|
|
34
|
+
},
|
|
35
|
+
y: 32,
|
|
36
|
+
opacity: 0,
|
|
37
|
+
scale: 0.97,
|
|
38
|
+
duration: 0.5,
|
|
39
|
+
stagger: { amount: 0.5 },
|
|
40
|
+
ease: 'power3.out',
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
})
|
|
24
45
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
trigger: sectionsRef.value,
|
|
29
|
-
start: 'top 85%',
|
|
30
|
-
toggleActions: 'play none none none',
|
|
31
|
-
},
|
|
32
|
-
y: 32,
|
|
33
|
-
opacity: 0,
|
|
34
|
-
scale: 0.97,
|
|
35
|
-
duration: 0.5,
|
|
36
|
-
stagger: { amount: 0.5 },
|
|
37
|
-
ease: 'power3.out',
|
|
38
|
-
})
|
|
39
|
-
}
|
|
46
|
+
onUnmounted(() => {
|
|
47
|
+
ctx?.revert()
|
|
48
|
+
ctx = null
|
|
40
49
|
})
|
|
41
50
|
|
|
42
51
|
const appConfig = useAppConfig()
|
|
43
|
-
const activeLayers = appConfig.layers
|
|
52
|
+
const activeLayers = appConfig.layers ?? {}
|
|
44
53
|
|
|
45
54
|
type LayerDef = {
|
|
46
55
|
name: string
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useThemePreferenceModels } from '../../composables/useThemePreferenceModels'
|
|
3
3
|
|
|
4
|
-
const {
|
|
5
|
-
|
|
4
|
+
const {
|
|
5
|
+
contrastOverride,
|
|
6
|
+
motionOverride,
|
|
7
|
+
transparencyOverride,
|
|
8
|
+
contrastModel,
|
|
9
|
+
motionModel,
|
|
10
|
+
transparencyModel,
|
|
11
|
+
} = useThemePreferenceModels()
|
|
6
12
|
</script>
|
|
7
13
|
|
|
8
14
|
<template>
|
|
@@ -1,67 +1,12 @@
|
|
|
1
1
|
import { buildAccentCSS } from '../utils/accent-css'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Nitro render hook — prevents FOUC for all theme preferences by injecting:
|
|
5
|
-
*
|
|
6
|
-
* 1. A `<style>` tag with CSS rules mapping each `[data-theme-colour='X']` to
|
|
7
|
-
* the correct `--ui-color-primary-*`, `--ui-color-secondary-*`, and
|
|
8
|
-
* `--ui-color-info-*` values. Each accent selects a coordinated three-colour
|
|
9
|
-
* palette so primary, secondary, and accent (info) all change together.
|
|
10
|
-
*
|
|
11
|
-
* 2. A blocking `<script>` that reads localStorage and restores all
|
|
12
|
-
* `data-theme-*` attributes on `<html>` before first paint:
|
|
13
|
-
* - `data-theme-colour` from `theme-colour` (defaults to 'blue')
|
|
14
|
-
* - `data-theme-contrast` from `theme-contrast`
|
|
15
|
-
* - `data-theme-motion` from `theme-motion`
|
|
16
|
-
* - `data-theme-transparency` from `theme-transparency`
|
|
17
|
-
* - `data-theme-mode` from `theme-mode` (raw string, not JSON — set by Nuxt Color Mode)
|
|
18
|
-
*
|
|
19
|
-
* Our script runs before CSS link tags (via head.unshift), so setting data-theme-mode
|
|
20
|
-
* here prevents the dark-mode FOUC that would otherwise occur when Nuxt Color Mode's
|
|
21
|
-
* script runs after stylesheets have downloaded. Nuxt Color Mode's script will then
|
|
22
|
-
* set the same value again — this is idempotent and safe.
|
|
23
|
-
*/
|
|
2
|
+
import { buildThemeInitScript } from '../utils/fouc-config'
|
|
24
3
|
|
|
25
4
|
const accentCSS = buildAccentCSS()
|
|
26
5
|
|
|
27
|
-
// Blocking init script — restores data-* attributes from localStorage before first paint.
|
|
28
|
-
// Written as a self-invoking function to avoid polluting the global scope.
|
|
29
|
-
// JSON.parse handles the quoted string that useLocalStorage writes.
|
|
30
|
-
// Note: theme-mode is stored as a raw string by Nuxt Color Mode (no JSON.stringify).
|
|
31
|
-
function buildInitScript(defaultAccent: string) {
|
|
32
|
-
return `(function(){
|
|
33
|
-
try{
|
|
34
|
-
var h=document.documentElement;
|
|
35
|
-
var c=localStorage.getItem('theme-colour');
|
|
36
|
-
h.setAttribute('data-theme-colour',c?JSON.parse(c):'${defaultAccent}');
|
|
37
|
-
var ct=localStorage.getItem('theme-contrast');
|
|
38
|
-
var ctv=ct?JSON.parse(ct):'system';
|
|
39
|
-
if(ctv==='on'){h.setAttribute('data-theme-contrast','high')}
|
|
40
|
-
else if(ctv==='off'){h.setAttribute('data-theme-contrast','standard')}
|
|
41
|
-
else{h.setAttribute('data-theme-contrast',(window.matchMedia&&window.matchMedia('(prefers-contrast:more)').matches)?'high':'standard')}
|
|
42
|
-
var m=localStorage.getItem('theme-motion');
|
|
43
|
-
var mv=m?JSON.parse(m):'system';
|
|
44
|
-
if(mv==='on'){h.setAttribute('data-theme-motion','reduced')}
|
|
45
|
-
else if(mv==='off'){h.setAttribute('data-theme-motion','full')}
|
|
46
|
-
else{h.setAttribute('data-theme-motion',(window.matchMedia&&window.matchMedia('(prefers-reduced-motion:reduce)').matches)?'reduced':'full')}
|
|
47
|
-
var t=localStorage.getItem('theme-transparency');
|
|
48
|
-
var tv=t?JSON.parse(t):'system';
|
|
49
|
-
if(tv==='on'){h.setAttribute('data-theme-transparency','reduced')}
|
|
50
|
-
else if(tv==='off'){h.setAttribute('data-theme-transparency','full')}
|
|
51
|
-
else{h.setAttribute('data-theme-transparency',(window.matchMedia&&window.matchMedia('(prefers-reduced-transparency:reduce)').matches)?'reduced':'full')}
|
|
52
|
-
var dm=localStorage.getItem('theme-mode');
|
|
53
|
-
var dmv=dm||'system';
|
|
54
|
-
if(dmv==='dark'){h.setAttribute('data-theme-mode','dark')}
|
|
55
|
-
else if(dmv==='light'){h.setAttribute('data-theme-mode','light')}
|
|
56
|
-
else{h.setAttribute('data-theme-mode',(window.matchMedia&&window.matchMedia('(prefers-color-scheme:dark)').matches)?'dark':'light')}
|
|
57
|
-
}catch(e){}
|
|
58
|
-
})()`.replace(/\n\s*/g, '')
|
|
59
|
-
}
|
|
60
|
-
|
|
61
6
|
export default defineNitroPlugin((nitroApp) => {
|
|
62
7
|
nitroApp.hooks.hook('render:html', (html, { event }) => {
|
|
63
8
|
const config = useRuntimeConfig(event)
|
|
64
|
-
const initScript =
|
|
9
|
+
const initScript = buildThemeInitScript(config.public.themeDefaultAccent || 'blue')
|
|
65
10
|
html.head.unshift(
|
|
66
11
|
`<style id="theme-accent-css">${accentCSS}</style><script>${initScript}</script>`
|
|
67
12
|
)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
type TriStatePreference = {
|
|
2
|
+
varName: string
|
|
3
|
+
storageKey: string
|
|
4
|
+
attribute: string
|
|
5
|
+
jsonParsed: boolean
|
|
6
|
+
conditionA: string
|
|
7
|
+
valueA: string
|
|
8
|
+
conditionB: string
|
|
9
|
+
valueB: string
|
|
10
|
+
mediaQuery: string
|
|
11
|
+
mediaTrue: string
|
|
12
|
+
mediaFalse: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Each entry produces one tri-state block in the FOUC init script.
|
|
16
|
+
// Adding a new theme preference = adding one entry here.
|
|
17
|
+
const THEME_PREFERENCES: TriStatePreference[] = [
|
|
18
|
+
{
|
|
19
|
+
varName: 'ct',
|
|
20
|
+
storageKey: 'theme-contrast',
|
|
21
|
+
attribute: 'data-theme-contrast',
|
|
22
|
+
jsonParsed: true,
|
|
23
|
+
conditionA: 'on', valueA: 'high',
|
|
24
|
+
conditionB: 'off', valueB: 'standard',
|
|
25
|
+
mediaQuery: '(prefers-contrast:more)',
|
|
26
|
+
mediaTrue: 'high', mediaFalse: 'standard',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
varName: 'm',
|
|
30
|
+
storageKey: 'theme-motion',
|
|
31
|
+
attribute: 'data-theme-motion',
|
|
32
|
+
jsonParsed: true,
|
|
33
|
+
conditionA: 'on', valueA: 'reduced',
|
|
34
|
+
conditionB: 'off', valueB: 'full',
|
|
35
|
+
mediaQuery: '(prefers-reduced-motion:reduce)',
|
|
36
|
+
mediaTrue: 'reduced', mediaFalse: 'full',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
varName: 't',
|
|
40
|
+
storageKey: 'theme-transparency',
|
|
41
|
+
attribute: 'data-theme-transparency',
|
|
42
|
+
jsonParsed: true,
|
|
43
|
+
conditionA: 'on', valueA: 'reduced',
|
|
44
|
+
conditionB: 'off', valueB: 'full',
|
|
45
|
+
mediaQuery: '(prefers-reduced-transparency:reduce)',
|
|
46
|
+
mediaTrue: 'reduced', mediaFalse: 'full',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
varName: 'dm',
|
|
50
|
+
storageKey: 'theme-mode',
|
|
51
|
+
attribute: 'data-theme-mode',
|
|
52
|
+
jsonParsed: false,
|
|
53
|
+
conditionA: 'dark', valueA: 'dark',
|
|
54
|
+
conditionB: 'light', valueB: 'light',
|
|
55
|
+
mediaQuery: '(prefers-color-scheme:dark)',
|
|
56
|
+
mediaTrue: 'dark', mediaFalse: 'light',
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
function buildPrefFragment(p: TriStatePreference): string {
|
|
61
|
+
const readValue = p.jsonParsed
|
|
62
|
+
? `${p.varName}?JSON.parse(${p.varName}):'system'`
|
|
63
|
+
: `${p.varName}||'system'`
|
|
64
|
+
return (
|
|
65
|
+
`var ${p.varName}=localStorage.getItem('${p.storageKey}');` +
|
|
66
|
+
`var ${p.varName}v=${readValue};` +
|
|
67
|
+
`if(${p.varName}v==='${p.conditionA}'){h.setAttribute('${p.attribute}','${p.valueA}')}` +
|
|
68
|
+
`else if(${p.varName}v==='${p.conditionB}'){h.setAttribute('${p.attribute}','${p.valueB}')}` +
|
|
69
|
+
`else{h.setAttribute('${p.attribute}',(window.matchMedia&&window.matchMedia('${p.mediaQuery}').matches)?'${p.mediaTrue}':'${p.mediaFalse}')}`
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function buildThemeInitScript(defaultAccent: string): string {
|
|
74
|
+
const colourBlock =
|
|
75
|
+
`var c=localStorage.getItem('theme-colour');` +
|
|
76
|
+
`h.setAttribute('data-theme-colour',c?JSON.parse(c):'${defaultAccent}');`
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
'(function(){try{' +
|
|
80
|
+
'var h=document.documentElement;' +
|
|
81
|
+
colourBlock +
|
|
82
|
+
THEME_PREFERENCES.map(buildPrefFragment).join('') +
|
|
83
|
+
'}catch(e){}})()'
|
|
84
|
+
)
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kmcom-nuxt-layers",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.5.0",
|
|
5
5
|
"description": "Composable Nuxt 4 layers for building scalable Vue applications",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./layers/core": "./layers/core/nuxt.config.ts",
|
|
@@ -21,11 +21,17 @@
|
|
|
21
21
|
"./layers/shader": "./layers/shader/nuxt.config.ts",
|
|
22
22
|
"./layers/mailer": "./layers/mailer/nuxt.config.ts",
|
|
23
23
|
"./layers/forms": "./layers/forms/nuxt.config.ts",
|
|
24
|
+
"./layers/database": "./layers/database/nuxt.config.ts",
|
|
25
|
+
"./layers/auth": "./layers/auth/nuxt.config.ts",
|
|
24
26
|
"./layers/theme": "./layers/theme/nuxt.config.ts",
|
|
25
27
|
"./layers/content": "./layers/content/nuxt.config.ts",
|
|
26
28
|
"./layers/feeds": "./layers/feeds/nuxt.config.ts",
|
|
27
29
|
"./layers/routing": "./layers/routing/nuxt.config.ts",
|
|
28
|
-
"./layers/starter": "./layers/starter/nuxt.config.ts"
|
|
30
|
+
"./layers/starter": "./layers/starter/nuxt.config.ts",
|
|
31
|
+
"./layers/metadata": "./layers/metadata/nuxt.config.ts",
|
|
32
|
+
"./layers/metadata-comicvine": "./layers/metadata-comicvine/nuxt.config.ts",
|
|
33
|
+
"./layers/metadata-openlibrary": "./layers/metadata-openlibrary/nuxt.config.ts",
|
|
34
|
+
"./layers/metadata-google-books": "./layers/metadata-google-books/nuxt.config.ts"
|
|
29
35
|
},
|
|
30
36
|
"files": [
|
|
31
37
|
"layers/*/nuxt.config.ts",
|
|
@@ -159,6 +165,7 @@
|
|
|
159
165
|
"@webgpu/glslang": "^0.0.15",
|
|
160
166
|
"@webgpu/types": "^0.1.70",
|
|
161
167
|
"browserslist": "^4.28.2",
|
|
168
|
+
"browserslist-config-baseline": "^0.5.0",
|
|
162
169
|
"changesets": "^1.0.2",
|
|
163
170
|
"csstype": "^3.2.3",
|
|
164
171
|
"cypress": "^15.16.0",
|
|
@@ -207,7 +214,7 @@
|
|
|
207
214
|
"last 2 Firefox major versions",
|
|
208
215
|
"last 2 Safari major versions",
|
|
209
216
|
"last 2 Edge major versions",
|
|
210
|
-
"baseline
|
|
217
|
+
"extends browserslist-config-baseline"
|
|
211
218
|
],
|
|
212
219
|
"dependencies": {
|
|
213
220
|
"node-gyp": "^12.3.0",
|
|
@@ -228,6 +235,8 @@
|
|
|
228
235
|
"dev:ui": "PLAYGROUND_LAYERS=core,ui pnpm -F playground dev",
|
|
229
236
|
"dev:layout": "PLAYGROUND_LAYERS=core,ui,layout pnpm -F playground dev",
|
|
230
237
|
"dev:motion": "PLAYGROUND_LAYERS=core,motion pnpm -F playground dev",
|
|
238
|
+
"dev:database": "PLAYGROUND_LAYERS=core,database pnpm -F playground dev",
|
|
239
|
+
"dev:auth": "PLAYGROUND_LAYERS=core,auth pnpm -F playground dev",
|
|
231
240
|
"build": "turbo run build",
|
|
232
241
|
"lint": "turbo run lint",
|
|
233
242
|
"lint:fix": "turbo run lint -- --fix",
|
package/types/auth.ts
ADDED
package/types/index.ts
CHANGED
|
@@ -17,3 +17,5 @@ export type {
|
|
|
17
17
|
export type { ScrollLockEvent, LoadingStateEvent, LayerEvent } from './events'
|
|
18
18
|
export type { MotionConfig, TransitionConfig } from './motion'
|
|
19
19
|
export type { PublicRuntimeConfig } from './runtime'
|
|
20
|
+
// fallow-ignore-next-line unused-type
|
|
21
|
+
export type { SessionUser } from './auth'
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Collections } from '@nuxt/content'
|
|
2
|
-
|
|
3
|
-
type CollectionItem<TCollection extends keyof Collections> = Collections[TCollection]
|
|
4
|
-
|
|
5
|
-
export function useCollectionItems<TCollection extends keyof Collections>(args: {
|
|
6
|
-
key: string
|
|
7
|
-
collection: TCollection
|
|
8
|
-
sort: (a: CollectionItem<TCollection>, b: CollectionItem<TCollection>) => number
|
|
9
|
-
options?: { limit?: number | undefined }
|
|
10
|
-
filter?: (item: CollectionItem<TCollection>) => boolean
|
|
11
|
-
}) {
|
|
12
|
-
const { key, collection, sort, options = {}, filter } = args
|
|
13
|
-
|
|
14
|
-
return useContentData(key, async () => {
|
|
15
|
-
let items = (await queryCollection(collection).all()) as CollectionItem<TCollection>[]
|
|
16
|
-
items = items.sort(sort)
|
|
17
|
-
|
|
18
|
-
if (filter) {
|
|
19
|
-
items = items.filter(filter)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (options.limit !== undefined) {
|
|
23
|
-
items = items.slice(0, options.limit)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return items
|
|
27
|
-
})
|
|
28
|
-
}
|