kmcom-nuxt-layers 2.2.5 → 2.2.8
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/docs/FEEDS.md +197 -0
- package/docs/LAYERS-FIXES.md +101 -0
- package/docs/MIGRATION.md +627 -0
- package/docs/feed-layer.md +374 -0
- package/docs/patch-picture-provider-type.md +52 -0
- package/docs/shaderGuide.md +2071 -0
- package/docs/types-architecture.md +234 -0
- package/layers/animations/app/components/Motion/CountUp.vue +1 -1
- package/layers/animations/app/components/Motion/Magnetic.vue +1 -1
- package/layers/animations/app/components/Motion/Marquee.vue +3 -2
- package/layers/animations/app/components/Motion/MarqueeText.vue +3 -2
- package/layers/animations/app/components/Motion/Tilt.vue +1 -1
- package/layers/animations/app/composables/useCountUp.ts +4 -1
- package/layers/animations/app/composables/useMagneticElement.ts +2 -4
- package/layers/animations/app/composables/useMarqueeCopies.ts +4 -4
- package/layers/animations/app/composables/useTiltEffect.ts +1 -1
- package/layers/animations/app/types/animations.ts +8 -0
- package/layers/animations/app/types/index.ts +1 -0
- package/layers/animations/package.json +4 -1
- package/layers/canvas/app/components/ShaderCanvas.vue +4 -4
- package/layers/canvas/app/composables/useRendererCapabilities.ts +19 -15
- package/layers/canvas/app/types/index.ts +1 -0
- package/layers/canvas/package.json +2 -1
- package/layers/canvas/tsconfig.json +2 -1
- package/layers/content/app/components/Blog/Card.vue +5 -5
- package/layers/content/app/components/Gallery/AmbientImage.vue +3 -3
- package/layers/content/app/components/Gallery/Card.vue +3 -3
- package/layers/content/app/components/Gallery/Lightbox.vue +5 -1
- package/layers/content/app/components/NuxtContent/Detail.vue +5 -1
- package/layers/content/app/components/NuxtContent/Surround.vue +5 -3
- package/layers/content/app/components/NuxtContent/Toc.vue +1 -1
- package/layers/content/app/components/Portfolio/Card.vue +5 -5
- package/layers/content/app/components/content/Figure.vue +3 -3
- package/layers/content/app/composables/useBlogPosts.ts +2 -2
- package/layers/content/app/composables/useCollectionItem.ts +1 -3
- package/layers/content/app/composables/useGalleryItems.ts +2 -2
- package/layers/content/app/types/index.ts +1 -0
- package/layers/content/package.json +2 -1
- package/layers/core/app/composables/useCache.ts +0 -1
- package/layers/core/app/composables/useErrorLog.ts +9 -11
- package/layers/core/app/plugins/error-handler.ts +36 -36
- package/layers/core/app/plugins/feature-detection.client.ts +15 -15
- package/layers/core/app/plugins/init.ts +121 -129
- package/layers/core/app/plugins/loading.client.ts +27 -27
- package/layers/core/app/plugins/scroll-guard.client.ts +26 -26
- package/layers/core/app/utils/helpers.ts +14 -12
- package/layers/core/nuxt.config.ts +1 -0
- package/layers/feeds/app/plugins/feed-head.ts +62 -63
- package/layers/feeds/package.json +2 -1
- package/layers/feeds/server/routes/feed/discovery.get.ts +1 -2
- package/layers/feeds/server/utils/content-adapter.ts +3 -2
- package/layers/forms/app/components/Form/Field.vue +4 -4
- package/layers/forms/app/types/index.ts +1 -0
- package/layers/forms/package.json +2 -1
- package/layers/forms/server/api/contact.post.ts +1 -1
- package/layers/layout/app/components/Layout/Grid/Item.vue +33 -19
- package/layers/layout/app/components/Layout/Page/Container.vue +11 -11
- package/layers/layout/app/components/Layout/Page/Header.vue +1 -1
- package/layers/layout/app/components/Layout/Page/index.vue +1 -1
- package/layers/layout/app/components/Layout/Section/Gallery.vue +6 -1
- package/layers/layout/app/components/Layout/Section/Title.vue +1 -1
- package/layers/layout/app/types/index.ts +1 -0
- package/layers/layout/package.json +2 -1
- package/layers/mailer/app/types/index.ts +1 -0
- package/layers/mailer/app/types/mailer.ts +25 -0
- package/layers/mailer/package.json +2 -1
- package/layers/motion/package.json +2 -1
- package/layers/navigation/app/components/Links/Group.vue +1 -0
- package/layers/navigation/app/components/Links/Named.vue +2 -0
- package/layers/navigation/app/types/index.ts +1 -0
- package/layers/navigation/package.json +4 -1
- package/layers/page-transitions/app/plugins/page-transitions.client.ts +9 -9
- package/layers/page-transitions/package.json +4 -1
- package/layers/routing/app/plugins/feature-flags.client.ts +9 -9
- package/layers/routing/app/types/app-config.d.ts +3 -1
- package/layers/routing/app/types/index.ts +1 -0
- package/layers/routing/package.json +2 -1
- package/layers/scripts/app/composables/useGtm.ts +1 -1
- package/layers/scripts/app/types/index.ts +1 -0
- package/layers/scripts/app/types/scripts.ts +14 -0
- package/layers/scripts/package.json +2 -1
- package/layers/scroll/app/components/Motion/ScrollScene.vue +3 -1
- package/layers/scroll/app/composables/useScrollSteps.ts +2 -9
- package/layers/scroll/app/composables/useSectionProgress.ts +2 -1
- package/layers/scroll/app/plugins/locomotive-scroll.client.ts +54 -61
- package/layers/scroll/app/types/index.ts +1 -0
- package/layers/scroll/app/types/scroll.ts +32 -0
- package/layers/scroll/package.json +3 -0
- package/layers/seo/package.json +2 -1
- package/layers/shader/app/components/Material/Fresnel.client.vue +1 -1
- package/layers/shader/app/components/Material/Image.client.vue +1 -1
- package/layers/shader/app/components/Material/Node.client.vue +7 -7
- package/layers/shader/app/components/Material/Noise.client.vue +1 -1
- package/layers/shader/app/components/Node/Color.client.vue +17 -20
- package/layers/shader/app/components/Node/Noise.client.vue +31 -34
- package/layers/shader/app/components/Pipeline/Aurora.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/BilinearGradient.client.vue +8 -11
- package/layers/shader/app/components/Pipeline/BillowNoise.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/BrightnessContrast.client.vue +6 -2
- package/layers/shader/app/components/Pipeline/CellularNoise.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Checkerboard.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Circle.client.vue +5 -8
- package/layers/shader/app/components/Pipeline/Clouds.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/ColorBurnBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/ColorDodgeBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/ColourRamp.client.vue +5 -9
- package/layers/shader/app/components/Pipeline/ConicGradient.client.vue +5 -8
- package/layers/shader/app/components/Pipeline/Cross.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/CurlNoise.client.vue +3 -7
- package/layers/shader/app/components/Pipeline/DarkenBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/DayNightCycle.client.vue +5 -8
- package/layers/shader/app/components/Pipeline/DiagonalGradient.client.vue +5 -8
- package/layers/shader/app/components/Pipeline/DiamondGradient.client.vue +5 -8
- package/layers/shader/app/components/Pipeline/DifferenceBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/DomainWarpedNoise.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Dots.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/DuoTone.client.vue +5 -9
- package/layers/shader/app/components/Pipeline/ExclusionBlend.client.vue +3 -23
- package/layers/shader/app/components/Pipeline/ExponentialFog.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/FilmBurn.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/Flame.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/FocalGradient.client.vue +5 -8
- package/layers/shader/app/components/Pipeline/GodRays.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/GradientNoise.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Grid.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Halation.client.vue +3 -7
- package/layers/shader/app/components/Pipeline/HardLightBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/Haze.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/Hexagon.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/LensFlare.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/LightenBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/LinearGradient4.client.vue +2 -2
- package/layers/shader/app/components/Pipeline/Marble.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/MonochromeTint.client.vue +3 -7
- package/layers/shader/app/components/Pipeline/MultiplyBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/NoisyGradient.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/NoisyGradientBlend.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/OverlayBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/Polygon.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/RaymarchTunnel.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/Rectangle.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/RidgedNoise.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Ring.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/ScreenBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/SkyAtmosphere.client.vue +6 -9
- package/layers/shader/app/components/Pipeline/SoftLightBlend.client.vue +2 -19
- package/layers/shader/app/components/Pipeline/SplitTone.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Star.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Stripes.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Tint.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/Triangle.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/ValueNoise.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/VoronoiEdges.client.vue +4 -8
- package/layers/shader/app/components/Pipeline/Water.client.vue +5 -8
- package/layers/shader/app/components/Pipeline/WaveBendLayer.client.vue +4 -7
- package/layers/shader/app/components/Pipeline/WaveColourLayer.client.vue +3 -7
- package/layers/shader/app/components/Pipeline/Wood.client.vue +4 -8
- package/layers/shader/app/components/Preset/Aurora.client.vue +15 -21
- package/layers/shader/app/components/Preset/Flow.client.vue +2 -1
- package/layers/shader/app/components/Preset/GradientMesh.client.vue +2 -1
- package/layers/shader/app/components/Preset/Nebula.client.vue +2 -1
- package/layers/shader/app/components/Preset/Ocean.client.vue +2 -1
- package/layers/shader/app/components/Preset/ThemeAurora.client.vue +30 -90
- package/layers/shader/app/components/Preset/ThemeBubble.client.vue +30 -91
- package/layers/shader/app/components/Preset/ThemeFlow.client.vue +30 -90
- package/layers/shader/app/components/Preset/ThemeGradient.client.vue +30 -91
- package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +30 -90
- package/layers/shader/app/components/Preset/ThemePlasma.client.vue +30 -90
- package/layers/shader/app/components/Preset/ThemeWave.client.vue +30 -90
- package/layers/shader/app/components/Shader/Background.client.vue +4 -4
- package/layers/shader/app/components/Shader/Host.client.vue +31 -33
- package/layers/shader/app/components/Shader/Runtime.client.vue +15 -23
- package/layers/shader/app/composables/useAmbientMaterials.ts +53 -51
- package/layers/shader/app/composables/useShaderMixBlend.ts +26 -0
- package/layers/shader/app/composables/useThemePreset.ts +75 -0
- package/layers/shader/app/plugins/shader.client.ts +21 -21
- package/layers/shader/app/shaders/common/noise.ts +2 -7
- package/layers/shader/app/shaders/types.ts +6 -6
- package/layers/shader/app/types/tsl.ts +7 -25
- package/layers/shader/app/types/uniforms.ts +2 -1
- package/layers/shader/app/utils/tsl/color.ts +7 -1
- package/layers/shader/package.json +2 -1
- package/layers/theme/app/components/ThemePicker/Colors.vue +1 -3
- package/layers/theme/app/plugins/theme.client.ts +54 -51
- package/layers/theme/app/types/app-config.d.ts +4 -2
- package/layers/theme/app/types/index.ts +1 -0
- package/layers/theme/app/types/theme.ts +3 -18
- package/layers/theme/package.json +2 -1
- package/layers/theme/server/plugins/theme-fouc.ts +1 -1
- package/layers/transitions/package.json +4 -1
- package/layers/typography/app/components/Typography/CodeBlock.vue +2 -2
- package/layers/typography/app/components/Typography/Headline.vue +2 -2
- package/layers/typography/app/components/Typography/HeadlineScreen.vue +1 -1
- package/layers/typography/app/components/Typography/QuoteBlock.vue +4 -1
- package/layers/typography/app/components/Typography/TextStroke.vue +2 -0
- package/layers/typography/app/components/Typography/index.vue +36 -27
- package/layers/typography/app/composables/typography.ts +27 -21
- package/layers/typography/app/types/colors.ts +9 -29
- package/layers/typography/app/types/index.ts +2 -0
- package/layers/typography/package.json +4 -1
- package/layers/ui/package.json +2 -1
- package/layers/visual/app/app.config.ts +5 -2
- package/layers/visual/app/components/Accent/Blob.vue +20 -20
- package/layers/visual/app/components/Accent/Scene.vue +2 -2
- package/layers/visual/app/components/Base/Modal.vue +2 -2
- package/layers/visual/app/components/Gradient/Background.vue +2 -2
- package/layers/visual/app/components/Gradient/Text.vue +2 -2
- package/layers/visual/app/components/Media/Picture.vue +3 -1
- package/layers/visual/app/components/Progress/Bar.vue +6 -6
- package/layers/visual/app/components/Tint/Overlay.vue +14 -14
- package/layers/visual/app/composables/accent.ts +10 -8
- package/layers/visual/app/composables/gradient.ts +2 -3
- package/layers/visual/app/composables/tint.ts +7 -7
- package/layers/visual/app/types/index.ts +6 -0
- package/layers/visual/app/types/media.ts +4 -2
- package/layers/visual/app/types/tint.ts +2 -1
- package/layers/visual/package.json +4 -1
- package/package.json +5 -2
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# 📡 Production Feed Layer (Nuxt 3)
|
|
2
|
+
|
|
3
|
+
A fully decoupled, format-agnostic syndication system for Nuxt Content.
|
|
4
|
+
Provides:
|
|
5
|
+
|
|
6
|
+
- RSS 2.0
|
|
7
|
+
- Atom 1.0
|
|
8
|
+
- JSON Feed 1.1
|
|
9
|
+
- XSLT human-readable feed UI
|
|
10
|
+
- Tailwind-styled feed interface
|
|
11
|
+
- Collection-aware feed endpoints
|
|
12
|
+
- Fully cacheable server outputs
|
|
13
|
+
Built for Nuxt 3 and Nuxt Content.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# 🧠 Core Principles
|
|
18
|
+
|
|
19
|
+
## 1. Canonical model first
|
|
20
|
+
|
|
21
|
+
All formats derive from a single structure:
|
|
22
|
+
|
|
23
|
+
> `FeedItem[]`
|
|
24
|
+
|
|
25
|
+
## No format-specific logic exists in content queries.
|
|
26
|
+
|
|
27
|
+
## 2. Format adapters are pure functions
|
|
28
|
+
|
|
29
|
+
Each format is isolated:
|
|
30
|
+
|
|
31
|
+
- RSS → `feed` npm package
|
|
32
|
+
- Atom → manual XML
|
|
33
|
+
- JSON Feed → JSON builder
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 3. UI is separate from feed semantics
|
|
38
|
+
|
|
39
|
+
- XSLT transforms RSS → HTML
|
|
40
|
+
- Tailwind styles only the human view
|
|
41
|
+
- Feed consumers never see UI logic
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 4. Routes are thin orchestration layers
|
|
46
|
+
|
|
47
|
+
## All logic lives in runtime/services.
|
|
48
|
+
|
|
49
|
+
## 5. Fully cacheable outputs
|
|
50
|
+
|
|
51
|
+
Feeds are deterministic and safe for:
|
|
52
|
+
|
|
53
|
+
- CDN caching
|
|
54
|
+
- ETags
|
|
55
|
+
- edge caching
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
# 📁 Folder Structure
|
|
60
|
+
|
|
61
|
+
layers/feed/
|
|
62
|
+
│
|
|
63
|
+
├── server/
|
|
64
|
+
│ └── routes/
|
|
65
|
+
│ └── feed/
|
|
66
|
+
│ ├── index.get.ts
|
|
67
|
+
│ ├── rss.get.ts
|
|
68
|
+
│ ├── atom.get.ts
|
|
69
|
+
│ ├── json.get.ts
|
|
70
|
+
│ │
|
|
71
|
+
│ └── [collection]/
|
|
72
|
+
│ ├── rss.get.ts
|
|
73
|
+
│ ├── atom.get.ts
|
|
74
|
+
│ └── json.get.ts
|
|
75
|
+
│
|
|
76
|
+
├── runtime/
|
|
77
|
+
│ ├── feed.service.ts
|
|
78
|
+
│ ├── content.adapter.ts
|
|
79
|
+
│ ├── feed.builder.ts
|
|
80
|
+
│ └── cache.ts
|
|
81
|
+
│
|
|
82
|
+
├── formats/
|
|
83
|
+
│ ├── rss.ts
|
|
84
|
+
│ ├── atom.ts
|
|
85
|
+
│ └── json.ts
|
|
86
|
+
│
|
|
87
|
+
├── ui/
|
|
88
|
+
│ ├── feed.css
|
|
89
|
+
│ └── rss.xsl
|
|
90
|
+
│
|
|
91
|
+
├── types/
|
|
92
|
+
│ └── feed.ts
|
|
93
|
+
│
|
|
94
|
+
└── README.md
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
# 🧱 Canonical Feed Model
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
export interface FeedItem {
|
|
102
|
+
title: string
|
|
103
|
+
description?: string
|
|
104
|
+
link: string
|
|
105
|
+
id: string
|
|
106
|
+
date: Date
|
|
107
|
+
author?: string
|
|
108
|
+
tags?: string[]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
⸻
|
|
113
|
+
|
|
114
|
+
🔌 Content Adapter Layer
|
|
115
|
+
|
|
116
|
+
Fetches content from Nuxt Content and maps to canonical model.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { queryContent } from '#content/server'
|
|
120
|
+
|
|
121
|
+
export async function getFeedItems(collection?: string) {
|
|
122
|
+
const query = queryContent(collection || '')
|
|
123
|
+
const items = await query.where({ _draft: false }).sort({ date: -1 }).limit(30).find()
|
|
124
|
+
return items.map((p) => ({
|
|
125
|
+
title: p.title,
|
|
126
|
+
description: p.description,
|
|
127
|
+
link: p._path,
|
|
128
|
+
id: p._path,
|
|
129
|
+
date: new Date(p.date),
|
|
130
|
+
tags: p.tags,
|
|
131
|
+
}))
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
⸻
|
|
136
|
+
|
|
137
|
+
⚙️ Feed Service Layer
|
|
138
|
+
|
|
139
|
+
Central orchestration layer.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
export async function buildFeed(collection?: string) {
|
|
143
|
+
return await getFeedItems(collection)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
⸻
|
|
148
|
+
|
|
149
|
+
📦 Format Adapters
|
|
150
|
+
|
|
151
|
+
⸻
|
|
152
|
+
|
|
153
|
+
RSS (feed npm package)
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { Feed } from 'feed'
|
|
157
|
+
|
|
158
|
+
export function toRSS(items, config) {
|
|
159
|
+
const feed = new Feed({
|
|
160
|
+
title: config.title,
|
|
161
|
+
description: config.description,
|
|
162
|
+
id: config.siteUrl,
|
|
163
|
+
link: config.siteUrl,
|
|
164
|
+
updated: new Date(),
|
|
165
|
+
})
|
|
166
|
+
for (const item of items) {
|
|
167
|
+
feed.addItem({
|
|
168
|
+
title: item.title,
|
|
169
|
+
id: item.id,
|
|
170
|
+
link: `${config.siteUrl}${item.link}`,
|
|
171
|
+
description: item.description,
|
|
172
|
+
date: item.date,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
return feed.rss2()
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
⸻
|
|
180
|
+
|
|
181
|
+
Atom (manual XML)
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
export function toAtom(items, config) {
|
|
185
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
186
|
+
<feed xmlns="http://www.w3.org/2005/Atom">
|
|
187
|
+
|
|
188
|
+
<title>${config.title}</title>
|
|
189
|
+
<id>${config.siteUrl}</id>
|
|
190
|
+
<updated>${new Date().toISOString()}</updated>
|
|
191
|
+
${items
|
|
192
|
+
.map(
|
|
193
|
+
(i) => `
|
|
194
|
+
<entry>
|
|
195
|
+
<title>${i.title}</title>
|
|
196
|
+
<id>${i.id}</id>
|
|
197
|
+
<link href="${config.siteUrl}${i.link}" />
|
|
198
|
+
<updated>${i.date.toISOString()}</updated>
|
|
199
|
+
<summary>${i.description || ''}</summary>
|
|
200
|
+
</entry>
|
|
201
|
+
`
|
|
202
|
+
)
|
|
203
|
+
.join('')}
|
|
204
|
+
</feed>`
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
⸻
|
|
209
|
+
|
|
210
|
+
JSON Feed
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
export function toJSONFeed(items, config) {
|
|
214
|
+
return {
|
|
215
|
+
version: 'https://jsonfeed.org/version/1',
|
|
216
|
+
title: config.title,
|
|
217
|
+
home_page_url: config.siteUrl,
|
|
218
|
+
feed_url: `${config.siteUrl}/feed/json`,
|
|
219
|
+
items: items.map((i) => ({
|
|
220
|
+
id: i.id,
|
|
221
|
+
url: `${config.siteUrl}${i.link}`,
|
|
222
|
+
title: i.title,
|
|
223
|
+
content_text: i.description,
|
|
224
|
+
date_published: i.date,
|
|
225
|
+
})),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
⸻
|
|
231
|
+
|
|
232
|
+
🌍 Route Layer (Thin Orchestration)
|
|
233
|
+
|
|
234
|
+
Global RSS
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { toRSS } from '../../formats/rss'
|
|
238
|
+
import { buildFeed } from '../../runtime/feed.service'
|
|
239
|
+
|
|
240
|
+
export default defineEventHandler(async () => {
|
|
241
|
+
const items = await buildFeed()
|
|
242
|
+
return toRSS(items, {
|
|
243
|
+
title: 'Site Feed',
|
|
244
|
+
description: 'Latest content',
|
|
245
|
+
siteUrl: 'https://your-site.com',
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
⸻
|
|
251
|
+
|
|
252
|
+
Collection feeds
|
|
253
|
+
|
|
254
|
+
const collection = event.context.params.collection
|
|
255
|
+
const items = await buildFeed(collection)
|
|
256
|
+
|
|
257
|
+
Same logic applies to all formats.
|
|
258
|
+
|
|
259
|
+
⸻
|
|
260
|
+
|
|
261
|
+
🎨 UI Layer (Human View Only)
|
|
262
|
+
|
|
263
|
+
Feed stylesheet (feed.css)
|
|
264
|
+
|
|
265
|
+
- Tailwind-based utility styling
|
|
266
|
+
- only used in XSLT view
|
|
267
|
+
- not part of feed logic
|
|
268
|
+
|
|
269
|
+
⸻
|
|
270
|
+
|
|
271
|
+
XSLT responsibilities
|
|
272
|
+
|
|
273
|
+
- transform RSS → HTML
|
|
274
|
+
- apply /feed.css
|
|
275
|
+
- render:
|
|
276
|
+
- title
|
|
277
|
+
- feed items
|
|
278
|
+
- actions
|
|
279
|
+
|
|
280
|
+
⸻
|
|
281
|
+
|
|
282
|
+
Subscribe button
|
|
283
|
+
|
|
284
|
+
`feed://your-domain.com/feed/rss`
|
|
285
|
+
|
|
286
|
+
Fallback:
|
|
287
|
+
|
|
288
|
+
- `/feed/rss`
|
|
289
|
+
- `/feed/atom`
|
|
290
|
+
|
|
291
|
+
⸻
|
|
292
|
+
|
|
293
|
+
Copy to clipboard
|
|
294
|
+
|
|
295
|
+
`navigator.clipboard.writeText(window.location.href)`
|
|
296
|
+
|
|
297
|
+
⸻
|
|
298
|
+
|
|
299
|
+
⚡ Caching Strategy
|
|
300
|
+
|
|
301
|
+
ETags
|
|
302
|
+
|
|
303
|
+
`setHeader(event, 'ETag', hash(content))`
|
|
304
|
+
|
|
305
|
+
⸻
|
|
306
|
+
|
|
307
|
+
Cache control
|
|
308
|
+
|
|
309
|
+
`cache-control: public, max-age=300, s-maxage=3600`
|
|
310
|
+
|
|
311
|
+
⸻
|
|
312
|
+
|
|
313
|
+
Optional Nitro caching
|
|
314
|
+
|
|
315
|
+
- per collection caching
|
|
316
|
+
- invalidation on content update
|
|
317
|
+
|
|
318
|
+
⸻
|
|
319
|
+
|
|
320
|
+
🔐 Rules
|
|
321
|
+
|
|
322
|
+
- No UI logic in formatters
|
|
323
|
+
- No format coupling
|
|
324
|
+
- No mutation of FeedItem in adapters
|
|
325
|
+
- XSLT must not affect feed semantics
|
|
326
|
+
|
|
327
|
+
⸻
|
|
328
|
+
|
|
329
|
+
📡 Final API Surface
|
|
330
|
+
|
|
331
|
+
Global feeds
|
|
332
|
+
|
|
333
|
+
- `/feed/rss`
|
|
334
|
+
- `/feed/atom`
|
|
335
|
+
- `/feed/json`
|
|
336
|
+
|
|
337
|
+
Collection feeds
|
|
338
|
+
|
|
339
|
+
- `/feed/:collection/rss`
|
|
340
|
+
- `/feed/:collection/atom`
|
|
341
|
+
- `/feed/:collection/json`
|
|
342
|
+
|
|
343
|
+
Optional
|
|
344
|
+
|
|
345
|
+
- `/feed` → index of available feeds
|
|
346
|
+
|
|
347
|
+
⸻
|
|
348
|
+
|
|
349
|
+
🧭 Summary
|
|
350
|
+
|
|
351
|
+
This system provides:
|
|
352
|
+
|
|
353
|
+
- Multi-format content syndication
|
|
354
|
+
- Clean separation of concerns
|
|
355
|
+
- Fully cacheable outputs
|
|
356
|
+
- Human-readable XSL feed UI
|
|
357
|
+
- Tailwind-powered styling layer
|
|
358
|
+
- Collection-aware feed generation
|
|
359
|
+
|
|
360
|
+
⸻
|
|
361
|
+
|
|
362
|
+
🚀 Outcome
|
|
363
|
+
|
|
364
|
+
You now have a:
|
|
365
|
+
|
|
366
|
+
Production-grade syndication layer that behaves like an internal publishing API.
|
|
367
|
+
|
|
368
|
+
It is:
|
|
369
|
+
|
|
370
|
+
- extensible
|
|
371
|
+
- framework-aligned
|
|
372
|
+
- cache-friendly
|
|
373
|
+
- UI-enhanced
|
|
374
|
+
- format-agnostic
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Patch: Picture.vue provider type cast
|
|
2
|
+
|
|
3
|
+
**Package:** `kmcom-nuxt-layers`
|
|
4
|
+
**Target version:** `2.2.5` (or next patch)
|
|
5
|
+
**Status:** Workaround live via pnpm patch at `patches/kmcom-nuxt-layers@2.2.4.patch`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
`layers/visual/app/components/Media/Picture.vue` passes the `provider` prop directly to `<NuxtPicture>`:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
:provider
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`PictureProps.provider` is typed as `string | undefined` (correctly widened in 2.2.4), but `NuxtPicture`'s `provider` prop is generated from `ConfiguredImageProviders` — a module-augmentation interface that narrows to the app's actual configured provider names (e.g. `"netlify" | undefined`).
|
|
18
|
+
|
|
19
|
+
Passing `string` to `"netlify"` fails the type check:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
error TS2322: Type 'string | undefined' is not assignable to type '"netlify" | undefined'.
|
|
23
|
+
Type 'string' is not assignable to type '"netlify"'.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The error appears in every consuming app that configures a non-`ipx` provider.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Fix
|
|
31
|
+
|
|
32
|
+
**File:** `layers/visual/app/components/Media/Picture.vue`, line 43
|
|
33
|
+
|
|
34
|
+
```diff
|
|
35
|
+
- :provider
|
|
36
|
+
+ :provider="(provider as any)"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The cast is necessary because `NuxtPicture`'s prop type is dynamically generated per-app. The layer cannot know the consuming app's provider set at build time, so any typed pass-through will always fail for non-default providers.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Verification
|
|
44
|
+
|
|
45
|
+
After publishing, in a consuming app using a non-`ipx` provider (e.g. `netlify`):
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pnpm install
|
|
49
|
+
pnpm typecheck # should pass with zero errors
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Once the new version is published, bump `package.json` here and remove `patches/kmcom-nuxt-layers@2.2.4.patch`.
|