create-cogenta 0.2.1 → 0.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/dist/blueprint-defaults.d.ts.map +1 -1
- package/dist/blueprint-defaults.js +6 -0
- package/dist/blueprint-defaults.js.map +1 -1
- package/dist/blueprints/assets/photos/association/community-cleanup.jpg +0 -0
- package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
- package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
- package/dist/blueprints/assets/photos/association/harvest-food-drive.jpg +0 -0
- package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
- package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/blog-runs-itself.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/editing.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/plain-text-editor.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/reading-nonfiction.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/small-tool.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/writing-schedule.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/business-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/business-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/business.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/culture-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/culture-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/culture.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/news-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/news-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/news.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/opinion-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/opinion-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/opinion.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/adatum-publishing.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/contoso-mobile-app.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fabrikam-annual-report.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/litware-signage.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/northwind-rebrand.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/proseware-launch.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tailspin-streaming.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/wingtip-wayfinding.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/charred-octopus.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/creme-brulee.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/house-red.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/house-white.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/pan-seared-trout.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/roasted-beet-salad.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/slow-roast-duck-leg.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/soup-of-the-day.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/sparkling-water.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/wild-mushroom-risotto.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/avatar-1.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/store/camp-blanket.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/ceramic-pour-over-set.jpg +0 -0
- package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
- package/dist/blueprints/assets/photos/store/everyday-tee.jpg +0 -0
- package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
- package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
- package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
- package/dist/blueprints/assets/photos/store/trail-tote.jpg +0 -0
- package/dist/blueprints/assets/photos/store/wool-beanie.jpg +0 -0
- package/dist/blueprints/assets/photos/store/wool-overshirt.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-1.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-2.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-3.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/dashboard.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/hero.jpg +0 -0
- package/dist/blueprints/association.d.ts +93 -15
- package/dist/blueprints/association.d.ts.map +1 -1
- package/dist/blueprints/association.js +637 -130
- package/dist/blueprints/association.js.map +1 -1
- package/dist/blueprints/blog.d.ts +81 -94
- package/dist/blueprints/blog.d.ts.map +1 -1
- package/dist/blueprints/blog.js +513 -177
- package/dist/blueprints/blog.js.map +1 -1
- package/dist/blueprints/content-pack.d.ts +83 -2
- package/dist/blueprints/content-pack.d.ts.map +1 -1
- package/dist/blueprints/content-pack.js +54 -1
- package/dist/blueprints/content-pack.js.map +1 -1
- package/dist/blueprints/content-packs.d.ts +4 -3
- package/dist/blueprints/content-packs.d.ts.map +1 -1
- package/dist/blueprints/content-packs.js +6 -3
- package/dist/blueprints/content-packs.js.map +1 -1
- package/dist/blueprints/demo-media.d.ts +39 -0
- package/dist/blueprints/demo-media.d.ts.map +1 -0
- package/dist/blueprints/demo-media.js +30 -0
- package/dist/blueprints/demo-media.js.map +1 -0
- package/dist/blueprints/documentation.d.ts +78 -15
- package/dist/blueprints/documentation.d.ts.map +1 -1
- package/dist/blueprints/documentation.js +585 -104
- package/dist/blueprints/documentation.js.map +1 -1
- package/dist/blueprints/magazine.d.ts +81 -15
- package/dist/blueprints/magazine.d.ts.map +1 -1
- package/dist/blueprints/magazine.js +431 -85
- package/dist/blueprints/magazine.js.map +1 -1
- package/dist/blueprints/menus.d.ts +25 -0
- package/dist/blueprints/menus.d.ts.map +1 -0
- package/dist/blueprints/menus.js +33 -0
- package/dist/blueprints/menus.js.map +1 -0
- package/dist/blueprints/photo-assets.d.ts +3 -0
- package/dist/blueprints/photo-assets.d.ts.map +1 -0
- package/dist/blueprints/photo-assets.js +33 -0
- package/dist/blueprints/photo-assets.js.map +1 -0
- package/dist/blueprints/portfolio.d.ts +81 -13
- package/dist/blueprints/portfolio.d.ts.map +1 -1
- package/dist/blueprints/portfolio.js +605 -102
- package/dist/blueprints/portfolio.js.map +1 -1
- package/dist/blueprints/registry.d.ts +3 -1
- package/dist/blueprints/registry.d.ts.map +1 -1
- package/dist/blueprints/registry.js +4 -1
- package/dist/blueprints/registry.js.map +1 -1
- package/dist/blueprints/restaurant.d.ts +65 -20
- package/dist/blueprints/restaurant.d.ts.map +1 -1
- package/dist/blueprints/restaurant.js +350 -117
- package/dist/blueprints/restaurant.js.map +1 -1
- package/dist/blueprints/saas.d.ts +81 -14
- package/dist/blueprints/saas.d.ts.map +1 -1
- package/dist/blueprints/saas.js +469 -132
- package/dist/blueprints/saas.js.map +1 -1
- package/dist/blueprints/site-settings-seed.d.ts +17 -0
- package/dist/blueprints/site-settings-seed.d.ts.map +1 -0
- package/dist/blueprints/site-settings-seed.js +45 -0
- package/dist/blueprints/site-settings-seed.js.map +1 -0
- package/dist/blueprints/starting-skins.d.ts +19 -0
- package/dist/blueprints/starting-skins.d.ts.map +1 -0
- package/dist/blueprints/starting-skins.js +301 -0
- package/dist/blueprints/starting-skins.js.map +1 -0
- package/dist/blueprints/store.d.ts +173 -0
- package/dist/blueprints/store.d.ts.map +1 -0
- package/dist/blueprints/store.js +643 -0
- package/dist/blueprints/store.js.map +1 -0
- package/dist/blueprints/vitrine.d.ts +94 -16
- package/dist/blueprints/vitrine.d.ts.map +1 -1
- package/dist/blueprints/vitrine.js +421 -142
- package/dist/blueprints/vitrine.js.map +1 -1
- package/dist/demo-art/compositions.d.ts +48 -0
- package/dist/demo-art/compositions.d.ts.map +1 -0
- package/dist/demo-art/compositions.js +690 -0
- package/dist/demo-art/compositions.js.map +1 -0
- package/dist/demo-art/index.d.ts +8 -0
- package/dist/demo-art/index.d.ts.map +1 -0
- package/dist/demo-art/index.js +5 -0
- package/dist/demo-art/index.js.map +1 -0
- package/dist/demo-art/oklch.d.ts +39 -0
- package/dist/demo-art/oklch.d.ts.map +1 -0
- package/dist/demo-art/oklch.js +79 -0
- package/dist/demo-art/oklch.js.map +1 -0
- package/dist/demo-art/png.d.ts +24 -0
- package/dist/demo-art/png.d.ts.map +1 -0
- package/dist/demo-art/png.js +110 -0
- package/dist/demo-art/png.js.map +1 -0
- package/dist/demo-art/render.d.ts +228 -0
- package/dist/demo-art/render.d.ts.map +1 -0
- package/dist/demo-art/render.js +478 -0
- package/dist/demo-art/render.js.map +1 -0
- package/dist/llm-setup.d.ts +10 -1
- package/dist/llm-setup.d.ts.map +1 -1
- package/dist/llm-setup.js +12 -2
- package/dist/llm-setup.js.map +1 -1
- package/dist/playground-reset.d.ts.map +1 -1
- package/dist/playground-reset.js +22 -4
- package/dist/playground-reset.js.map +1 -1
- package/dist/prompts.d.ts +12 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +32 -0
- package/dist/prompts.js.map +1 -1
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +6 -0
- package/dist/recap.js.map +1 -1
- package/dist/scaffold.d.ts +22 -2
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +185 -23
- package/dist/scaffold.js.map +1 -1
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +4 -1
- package/dist/wizard.js.map +1 -1
- package/package.json +14 -11
package/dist/blueprints/blog.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
import { createContentStore, createTaxonomyStore, defineCollection, defineTaxonomy, f, validateCollectionSet, validateTaxonomySet, } from '@cogenta/schema';
|
|
3
|
+
import { avatarArt, coverArt, heroArt, logoArt } from '../demo-art/compositions.js';
|
|
4
|
+
import { SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
|
|
5
|
+
import { STARTING_SKINS } from './starting-skins.js';
|
|
3
6
|
/**
|
|
4
|
-
* The `blog` blueprint's content model (L9 task 3).
|
|
7
|
+
* The `blog` blueprint's content model (L9 task 3, richened for L25 D4).
|
|
5
8
|
*
|
|
6
9
|
* `post` reuses `SystemFields.status`/`createdAt` for publish state and
|
|
7
10
|
* publish date rather than declaring its own — contract A already carries
|
|
8
11
|
* both on every entry. Authorship reuses `SystemFields.createdBy`, which
|
|
9
12
|
* points at the real user/actor system, rather than a separate author
|
|
10
13
|
* collection this blueprint would have to invent and keep in sync.
|
|
14
|
+
*
|
|
15
|
+
* `category`/`tag` are `defineTaxonomy()` declarations, not collections
|
|
16
|
+
* (audit fiche 04, T02/T10 — `schema@2.0`, ADR-0022, already figé). `category`
|
|
17
|
+
* stays hierarchical (its default); `tag` is declared flat.
|
|
11
18
|
*/
|
|
12
|
-
export const category =
|
|
19
|
+
export const category = defineTaxonomy({
|
|
13
20
|
name: 'category',
|
|
14
|
-
labels: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
name: f.text({ required: true, max: 80 }),
|
|
18
|
-
slug: f.slug({ from: 'name', unique: true }),
|
|
21
|
+
labels: {
|
|
22
|
+
singular: { en: 'Category', fr: 'Catégorie' },
|
|
23
|
+
plural: { en: 'Categories', fr: 'Catégories' },
|
|
19
24
|
},
|
|
20
|
-
indexes: [['slug']],
|
|
21
25
|
permissions: {
|
|
22
26
|
read: ['public'],
|
|
23
27
|
create: ['editor', 'admin'],
|
|
@@ -25,14 +29,13 @@ export const category = defineCollection({
|
|
|
25
29
|
delete: ['admin'],
|
|
26
30
|
},
|
|
27
31
|
});
|
|
28
|
-
export const tag =
|
|
32
|
+
export const tag = defineTaxonomy({
|
|
29
33
|
name: 'tag',
|
|
30
|
-
labels: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
slug: f.slug({ from: 'name', unique: true }),
|
|
34
|
+
labels: {
|
|
35
|
+
singular: { en: 'Tag', fr: 'Étiquette' },
|
|
36
|
+
plural: { en: 'Tags', fr: 'Étiquettes' },
|
|
34
37
|
},
|
|
35
|
-
|
|
38
|
+
hierarchical: false,
|
|
36
39
|
permissions: {
|
|
37
40
|
read: ['public'],
|
|
38
41
|
create: ['editor', 'admin'],
|
|
@@ -48,13 +51,25 @@ export const post = defineCollection({
|
|
|
48
51
|
fields: {
|
|
49
52
|
title: f.text({ required: true, max: 200 }),
|
|
50
53
|
slug: f.slug({ from: 'title', unique: true }),
|
|
51
|
-
excerpt: f.text({ max: 300, multiline: true }),
|
|
52
54
|
body: f.richText({ required: true }),
|
|
55
|
+
excerpt: f.text({ max: 300, multiline: true }),
|
|
56
|
+
// L25 D4: a cover image, read by `entryImage` (`@cogenta/theme-kit`)
|
|
57
|
+
// for the "Latest"/"From the archive" cards and the post's own
|
|
58
|
+
// `renderEntryHeader` cover.
|
|
53
59
|
coverImage: f.media({ accept: ['image'] }),
|
|
54
|
-
category: f.
|
|
55
|
-
tags: f.
|
|
60
|
+
category: f.taxonomy({ of: 'category', many: false }),
|
|
61
|
+
tags: f.taxonomy({ of: 'tag', many: true }),
|
|
62
|
+
// Declared (contract A's own `docs/04-contrats.md` example does the
|
|
63
|
+
// same on its `article` collection) so `createContentStore`'s own
|
|
64
|
+
// publish-time default (`store.ts`: only set when the collection
|
|
65
|
+
// declares this field) actually fires — without it every post's
|
|
66
|
+
// `renderEntryHeader` meta line silently drops the "date lisible" the
|
|
67
|
+
// brief asks for, since `theme-render.ts` only forwards `publishedAt`
|
|
68
|
+
// when it is non-null.
|
|
69
|
+
publishedAt: f.datetime(),
|
|
70
|
+
...SEO_FIELDS,
|
|
56
71
|
},
|
|
57
|
-
indexes: [['slug']],
|
|
72
|
+
indexes: [['publishedAt', 'desc'], ['slug']],
|
|
58
73
|
permissions: {
|
|
59
74
|
read: ['public'],
|
|
60
75
|
create: ['editor', 'admin'],
|
|
@@ -63,16 +78,6 @@ export const post = defineCollection({
|
|
|
63
78
|
publish: ['admin'],
|
|
64
79
|
},
|
|
65
80
|
});
|
|
66
|
-
/**
|
|
67
|
-
* The blueprint's "page types" (L9 task 4): standalone pages composed of
|
|
68
|
-
* vocabulary blocks rather than a bespoke template each — the same
|
|
69
|
-
* title-plus-block-zone shape `theme-canonical`'s own test fixtures already
|
|
70
|
-
* use for `{ collection: 'page', id: … }` internal-link targets, formalised
|
|
71
|
-
* here as a real `CollectionDefinition` for the first time. `blocks` uses
|
|
72
|
-
* `f.blocks()` (contract A), the field kind a `BlockZone` — an ordered list of
|
|
73
|
-
* vocabulary blocks — is stored under; `renderPage` (`@cogenta/theme-canonical`)
|
|
74
|
-
* takes exactly a title plus that list.
|
|
75
|
-
*/
|
|
76
81
|
export const page = defineCollection({
|
|
77
82
|
name: 'page',
|
|
78
83
|
labels: { singular: 'Page', plural: 'Pages' },
|
|
@@ -81,6 +86,7 @@ export const page = defineCollection({
|
|
|
81
86
|
title: f.text({ required: true, max: 200 }),
|
|
82
87
|
slug: f.slug({ from: 'title', unique: true }),
|
|
83
88
|
blocks: f.blocks({ required: true }),
|
|
89
|
+
...SEO_FIELDS,
|
|
84
90
|
},
|
|
85
91
|
indexes: [['slug']],
|
|
86
92
|
permissions: {
|
|
@@ -90,174 +96,299 @@ export const page = defineCollection({
|
|
|
90
96
|
delete: ['admin'],
|
|
91
97
|
},
|
|
92
98
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
* (`scaffold.ts`), so `loadCollections` (`@cogenta/cli`) reads back exactly
|
|
96
|
-
* these four collections.
|
|
97
|
-
*/
|
|
98
|
-
export const BLOG_COLLECTIONS = [post, category, tag, page];
|
|
99
|
-
// Cross-collection checks (duplicate names, dangling relation targets) run
|
|
100
|
-
// at import time, same as `defineCollection` itself: a mistake here costs a
|
|
101
|
-
// restart, not a database.
|
|
99
|
+
export const BLOG_COLLECTIONS = [post, page];
|
|
100
|
+
export const BLOG_TAXONOMIES = [category, tag];
|
|
102
101
|
validateCollectionSet(BLOG_COLLECTIONS);
|
|
102
|
+
validateTaxonomySet(BLOG_TAXONOMIES, BLOG_COLLECTIONS);
|
|
103
103
|
let paragraphKey = 0;
|
|
104
104
|
/** One `normal`-style rich-text paragraph, unmarked. */
|
|
105
105
|
function paragraph(text) {
|
|
106
106
|
paragraphKey += 1;
|
|
107
107
|
return {
|
|
108
|
-
_key: `demo-${paragraphKey}`,
|
|
108
|
+
_key: `demo-p-${paragraphKey}`,
|
|
109
|
+
_type: 'block',
|
|
110
|
+
style: 'normal',
|
|
111
|
+
children: [{ _key: `demo-p-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
|
|
112
|
+
markDefs: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function heading2(text) {
|
|
116
|
+
paragraphKey += 1;
|
|
117
|
+
return {
|
|
118
|
+
_key: `demo-h-${paragraphKey}`,
|
|
119
|
+
_type: 'block',
|
|
120
|
+
style: 'h2',
|
|
121
|
+
children: [{ _key: `demo-h-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
|
|
122
|
+
markDefs: [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function bulletItem(text) {
|
|
126
|
+
paragraphKey += 1;
|
|
127
|
+
return {
|
|
128
|
+
_key: `demo-l-${paragraphKey}`,
|
|
109
129
|
_type: 'block',
|
|
110
130
|
style: 'normal',
|
|
111
|
-
|
|
131
|
+
listItem: 'bullet',
|
|
132
|
+
level: 1,
|
|
133
|
+
children: [{ _key: `demo-l-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
|
|
134
|
+
markDefs: [],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function quoteLine(text) {
|
|
138
|
+
paragraphKey += 1;
|
|
139
|
+
return {
|
|
140
|
+
_key: `demo-q-${paragraphKey}`,
|
|
141
|
+
_type: 'block',
|
|
142
|
+
style: 'blockquote',
|
|
143
|
+
children: [{ _key: `demo-q-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
|
|
112
144
|
markDefs: [],
|
|
113
145
|
};
|
|
114
146
|
}
|
|
115
147
|
export const BLOG_DEMO_CATEGORIES = [
|
|
116
|
-
{ name: '
|
|
117
|
-
{ name: '
|
|
148
|
+
{ name: 'Reading', slug: 'reading' },
|
|
149
|
+
{ name: 'Building', slug: 'building' },
|
|
150
|
+
{ name: 'Writing', slug: 'writing' },
|
|
151
|
+
{ name: 'Craft', slug: 'craft' },
|
|
118
152
|
];
|
|
153
|
+
// L26 D1: `name` is the display label shown as-is on a tag archive's <h1>
|
|
154
|
+
// (`renderTermArchive`) — lowercase read as unfinished placeholder text
|
|
155
|
+
// next to `category`'s title-cased labels ("Writing", not "writing").
|
|
156
|
+
// `slug` is untouched: it is the URL and the join-table key.
|
|
119
157
|
export const BLOG_DEMO_TAGS = [
|
|
120
|
-
{ name: '
|
|
121
|
-
{ name: '
|
|
122
|
-
{ name: '
|
|
158
|
+
{ name: 'Process', slug: 'process' },
|
|
159
|
+
{ name: 'Tools', slug: 'tools' },
|
|
160
|
+
{ name: 'Habits', slug: 'habits' },
|
|
161
|
+
{ name: 'Editors', slug: 'editors' },
|
|
162
|
+
{ name: 'Notebooks', slug: 'notebooks' },
|
|
163
|
+
{ name: 'Focus', slug: 'focus' },
|
|
164
|
+
{ name: 'Drafts', slug: 'drafts' },
|
|
165
|
+
{ name: 'Revision', slug: 'revision' },
|
|
123
166
|
];
|
|
124
167
|
export const BLOG_DEMO_POSTS = [
|
|
125
168
|
{
|
|
126
|
-
title: '
|
|
127
|
-
slug: '
|
|
128
|
-
excerpt: '
|
|
169
|
+
title: 'Why I still write in a plain-text editor',
|
|
170
|
+
slug: 'plain-text-editor',
|
|
171
|
+
excerpt: 'Ten years of trying every tool that promised to make writing easier.',
|
|
172
|
+
categorySlug: 'writing',
|
|
173
|
+
tagSlugs: ['tools', 'editors'],
|
|
174
|
+
body: [
|
|
175
|
+
paragraph('I have paid for at least nine different writing apps since 2016. Each one promised the same ' +
|
|
176
|
+
'thing: a calmer place to think, fewer distractions, a nicer font. Every single one eventually ' +
|
|
177
|
+
'added a sidebar, a sync indicator, and a subscription reminder, and I went back to a plain-text ' +
|
|
178
|
+
'editor within a month.'),
|
|
179
|
+
heading2('What a plain file actually gives you'),
|
|
180
|
+
bulletItem('It opens instantly, every time, on every machine I own.'),
|
|
181
|
+
bulletItem('It will still open in twenty years — the format is the text itself.'),
|
|
182
|
+
bulletItem('Nothing about the tool is trying to keep my attention.'),
|
|
183
|
+
paragraph('None of that is a complaint about design. It is a complaint about incentives: a writing tool that ' +
|
|
184
|
+
'has to grow a business ends up optimising for engagement, and engagement is the opposite of what ' +
|
|
185
|
+
'a first draft needs.'),
|
|
186
|
+
quoteLine('The best writing tool is the one that gets out of the way and stays out of the way.'),
|
|
187
|
+
paragraph('So the editor I actually finish drafts in has no plugins, no themes, and no update notifications. ' +
|
|
188
|
+
'It has a cursor and a blinking line, and that turns out to be enough.'),
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
title: 'The desk setup that finally stuck',
|
|
193
|
+
slug: 'desk-setup-that-stuck',
|
|
194
|
+
excerpt: 'Every previous version of this desk lasted about six weeks. This one is two years old.',
|
|
195
|
+
categorySlug: 'craft',
|
|
196
|
+
tagSlugs: ['tools', 'habits'],
|
|
197
|
+
body: [
|
|
198
|
+
paragraph('I used to rebuild my desk setup every time I read a good "how I work" post. New monitor arm, new ' +
|
|
199
|
+
'keyboard, new lamp, new chair — a little dopamine hit, a week of feeling productive, and then back ' +
|
|
200
|
+
'to the same problems.'),
|
|
201
|
+
paragraph('What actually fixed it was removing things rather than adding them: one monitor instead of three, a ' +
|
|
202
|
+
'closed door instead of noise-cancelling headphones, and a chair I stopped noticing entirely. The ' +
|
|
203
|
+
'setup that stuck is the one I stopped thinking about.'),
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title: 'What ten years of reading nonfiction taught me',
|
|
208
|
+
slug: 'ten-years-reading-nonfiction',
|
|
209
|
+
excerpt: 'Mostly that I was reading for the wrong reason for most of it.',
|
|
210
|
+
categorySlug: 'reading',
|
|
211
|
+
tagSlugs: ['habits', 'notebooks'],
|
|
212
|
+
body: [
|
|
213
|
+
paragraph('For the first several years I read to be able to say I had read the book. I finished things I ' +
|
|
214
|
+
'disliked out of a sense of obligation, and I remembered almost none of them a month later.'),
|
|
215
|
+
heading2('The one change that mattered'),
|
|
216
|
+
paragraph('I started keeping a single notebook — not a book journal, just a running list of the one idea from ' +
|
|
217
|
+
'each book that changed how I thought about something. The books I remember now are the ones that ' +
|
|
218
|
+
'earned a line in that notebook, and nothing else.'),
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
title: 'A small tool I built to stop losing drafts',
|
|
223
|
+
slug: 'tool-to-stop-losing-drafts',
|
|
224
|
+
excerpt: 'It is ugly, it has no users but me, and it has saved three articles this year alone.',
|
|
225
|
+
categorySlug: 'building',
|
|
226
|
+
tagSlugs: ['tools', 'drafts'],
|
|
129
227
|
body: [
|
|
130
|
-
paragraph('
|
|
131
|
-
'
|
|
132
|
-
'
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'content model, a skin, and a few real demo posts to look at instead of an empty admin screen.'),
|
|
228
|
+
paragraph('Every text editor I have used eventually loses a draft — a crash, a sync conflict, a laptop that ' +
|
|
229
|
+
'died mid-sentence. After the third time it happened to something I actually cared about, I wrote a ' +
|
|
230
|
+
'forty-line script that copies whatever is in my drafts folder to a second disk every five minutes.'),
|
|
231
|
+
paragraph('It has no interface, no settings, and no name beyond `backup.sh`. It is the single most useful piece ' +
|
|
232
|
+
'of software I have ever written, and it took less time to build than writing this post did.'),
|
|
136
233
|
],
|
|
137
|
-
categorySlug: 'announcements',
|
|
138
|
-
tagSlugs: ['cms', 'open-source'],
|
|
139
234
|
},
|
|
140
235
|
{
|
|
141
|
-
title: '
|
|
142
|
-
slug: '
|
|
143
|
-
excerpt: '
|
|
236
|
+
title: 'The one habit that fixed my writing schedule',
|
|
237
|
+
slug: 'habit-that-fixed-my-schedule',
|
|
238
|
+
excerpt: 'Not a morning routine. Not a word-count goal. Something much smaller.',
|
|
239
|
+
categorySlug: 'writing',
|
|
240
|
+
tagSlugs: ['habits', 'focus'],
|
|
144
241
|
body: [
|
|
145
|
-
paragraph('
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
'
|
|
242
|
+
paragraph('I tried the 5am wake-up. I tried a 1,000-word daily minimum. Both worked for about a week and then ' +
|
|
243
|
+
'collapsed the first time life got in the way, which made me feel worse than not trying at all.'),
|
|
244
|
+
paragraph('What actually stuck was absurdly small: open the draft file before doing anything else on the ' +
|
|
245
|
+
'computer, even if I only read the last paragraph and close it again. Some days that is all that ' +
|
|
246
|
+
'happens. Most days, once the file is open, I keep going.'),
|
|
150
247
|
],
|
|
151
|
-
categorySlug: 'guides',
|
|
152
|
-
tagSlugs: ['cms'],
|
|
153
248
|
},
|
|
154
249
|
{
|
|
155
|
-
title: '
|
|
156
|
-
slug: '
|
|
157
|
-
excerpt: '
|
|
250
|
+
title: 'Notebooks I have actually finished',
|
|
251
|
+
slug: 'notebooks-actually-finished',
|
|
252
|
+
excerpt: 'A short, honest list, after a drawer full of notebooks with four used pages each.',
|
|
253
|
+
categorySlug: 'reading',
|
|
254
|
+
tagSlugs: ['notebooks', 'habits'],
|
|
158
255
|
body: [
|
|
159
|
-
paragraph('
|
|
160
|
-
'
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
256
|
+
paragraph('I own a drawer of notebooks that got four pages of enthusiastic notes and then nothing. The ones I ' +
|
|
257
|
+
'actually filled share one property: they were too plain and too cheap to feel precious.'),
|
|
258
|
+
bulletItem('A notebook I am afraid to ruin is a notebook I will not open.'),
|
|
259
|
+
bulletItem('Grid paper beats lined paper for anything that is not a diary.'),
|
|
260
|
+
bulletItem('A pen that writes badly is worse for the habit than no pen at all.'),
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
title: 'Building a blog that runs itself',
|
|
265
|
+
slug: 'blog-that-runs-itself',
|
|
266
|
+
excerpt: 'What I actually automated, and — more importantly — what I deliberately did not.',
|
|
267
|
+
categorySlug: 'building',
|
|
268
|
+
tagSlugs: ['tools', 'process'],
|
|
269
|
+
body: [
|
|
270
|
+
paragraph('This blog is a static content model with a scheduler that checks for broken links, a spellchecker ' +
|
|
271
|
+
'that flags anything odd before it publishes, and a script that reminds me when a post has not been ' +
|
|
272
|
+
'revisited in two years.'),
|
|
273
|
+
paragraph('What it does not do is write anything, choose what to publish, or decide when a draft is ready. The ' +
|
|
274
|
+
'automation handles the boring parts so the writing stays mine.'),
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
title: 'Editing is where the writing happens',
|
|
279
|
+
slug: 'editing-is-where-writing-happens',
|
|
280
|
+
excerpt: 'A first draft is raw material. The real writing starts on the second pass.',
|
|
281
|
+
categorySlug: 'writing',
|
|
282
|
+
tagSlugs: ['revision', 'drafts'],
|
|
283
|
+
body: [
|
|
284
|
+
paragraph('I used to think of editing as cleanup — fixing typos, tightening a sentence here and there. Every ' +
|
|
285
|
+
'post that actually worked was rebuilt at least once, sometimes from a completely different opening.'),
|
|
286
|
+
heading2('What I look for on a second pass'),
|
|
287
|
+
bulletItem('The paragraph that is doing the least work — usually the second one.'),
|
|
288
|
+
bulletItem('A claim I made without an example to back it up.'),
|
|
289
|
+
bulletItem('The sentence I am proudest of, which is often the one that should go.'),
|
|
290
|
+
quoteLine('A draft is a question. An edit is the answer you are actually willing to publish.'),
|
|
164
291
|
],
|
|
165
|
-
categorySlug: 'guides',
|
|
166
|
-
tagSlugs: ['agents', 'open-source'],
|
|
167
292
|
},
|
|
168
293
|
];
|
|
169
294
|
const BLOCK_VERSION = '1.0.0';
|
|
170
295
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* `
|
|
174
|
-
*
|
|
175
|
-
* `@cogenta/theme-canonical`'s `renderPage`/`renderBlock` — no bespoke
|
|
176
|
-
* "home page" or "about page" template is needed for either.
|
|
296
|
+
* The blueprint's own starting skin (`starting-skins.js`) — asserted present
|
|
297
|
+
* with a real check, not a `!`, the same guard `store.ts`'s `storePalette`
|
|
298
|
+
* uses: `STARTING_SKINS` is keyed by blueprint id and TypeScript cannot see
|
|
299
|
+
* that this particular key is always populated.
|
|
177
300
|
*/
|
|
178
|
-
|
|
301
|
+
function blogPalette() {
|
|
302
|
+
const skin = STARTING_SKINS.blog;
|
|
303
|
+
if (skin === undefined) {
|
|
304
|
+
throw new CogentaError({
|
|
305
|
+
code: 'BLUEPRINT_REGISTRY_CORRUPT',
|
|
306
|
+
message: 'STARTING_SKINS.blog is missing.',
|
|
307
|
+
hint: 'The "blog" entry must stay declared in starting-skins.ts for this blueprint to render its demo art.',
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return skin.color;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Procedural visuals this blueprint seeds (L25 D4): a magazine-cover hero
|
|
314
|
+
* backdrop, one cover per demo post (keyed by the post's own slug), a reader
|
|
315
|
+
* avatar for the "reader's words" quote block, and five neutral wordmark
|
|
316
|
+
* stand-ins for the "As featured in" strip.
|
|
317
|
+
*/
|
|
318
|
+
/** Bundled photography (`assets/photos/blog/`), keyed by the same slug `BLOG_DEMO_POSTS` uses. */
|
|
319
|
+
const BLOG_POST_PHOTOS = {
|
|
320
|
+
'plain-text-editor': 'blog/plain-text-editor.jpg',
|
|
321
|
+
'desk-setup-that-stuck': 'blog/desk-setup.jpg',
|
|
322
|
+
'ten-years-reading-nonfiction': 'blog/reading-nonfiction.jpg',
|
|
323
|
+
'tool-to-stop-losing-drafts': 'blog/small-tool.jpg',
|
|
324
|
+
'habit-that-fixed-my-schedule': 'blog/writing-schedule.jpg',
|
|
325
|
+
'notebooks-actually-finished': 'blog/notebooks.jpg',
|
|
326
|
+
'blog-that-runs-itself': 'blog/blog-runs-itself.jpg',
|
|
327
|
+
'editing-is-where-writing-happens': 'blog/editing.jpg',
|
|
328
|
+
};
|
|
329
|
+
export const BLOG_MEDIA_SPECS = [
|
|
179
330
|
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
_key: 'demo-home-hero',
|
|
185
|
-
_type: 'hero',
|
|
186
|
-
_version: BLOCK_VERSION,
|
|
187
|
-
eyebrow: 'Cogenta',
|
|
188
|
-
title: 'A blog that runs itself',
|
|
189
|
-
subtitle: 'Scaffolded by create-cogenta with real demo posts, categories and tags — every one of them editable the moment you sign in.',
|
|
190
|
-
actions: [
|
|
191
|
-
{
|
|
192
|
-
label: 'Read the latest post',
|
|
193
|
-
target: { href: '/blog/welcome-to-cogenta' },
|
|
194
|
-
emphasis: 'primary',
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
_key: 'demo-home-recent-posts',
|
|
200
|
-
_type: 'collectionList',
|
|
201
|
-
_version: BLOCK_VERSION,
|
|
202
|
-
title: 'Latest posts',
|
|
203
|
-
collection: 'post',
|
|
204
|
-
// `publishedAt` is nullable (a draft has none) and was never in the
|
|
205
|
-
// real, frozen `SortField` union (`id`/`createdAt`/`updatedAt` only —
|
|
206
|
-
// cursor pagination needs a column that is never null). This block
|
|
207
|
-
// asked for it anyway and nothing exercised the query until
|
|
208
|
-
// `cogenta serve`'s theme-render fallback did, surfacing a real
|
|
209
|
-
// QUERY_INVALID on every request for the seeded home page.
|
|
210
|
-
sort: { field: 'createdAt', direction: 'desc' },
|
|
211
|
-
limit: 10,
|
|
212
|
-
layout: 'list',
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
_key: 'demo-home-what',
|
|
216
|
-
_type: 'featureGrid',
|
|
217
|
-
_version: BLOCK_VERSION,
|
|
218
|
-
title: 'What you are looking at',
|
|
219
|
-
items: [
|
|
220
|
-
{
|
|
221
|
-
_key: 'demo-what-1',
|
|
222
|
-
icon: 'blocks',
|
|
223
|
-
title: 'Blocks, not HTML',
|
|
224
|
-
text: 'Every section of this page is a block storing plain data. The theme decides what it looks like, so a new skin restyles all of it at once.',
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
_key: 'demo-what-2',
|
|
228
|
-
icon: 'content',
|
|
229
|
-
title: 'Real content, from the first run',
|
|
230
|
-
text: 'The posts, categories and tags below were seeded by the installer. Rename them, delete them — nothing here is special.',
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
_key: 'demo-what-3',
|
|
234
|
-
icon: 'zero-js',
|
|
235
|
-
title: 'No client JavaScript',
|
|
236
|
-
text: 'The accordion, the carousel and the dark mode are all CSS. There is no bundle to wait for on a slow connection.',
|
|
237
|
-
},
|
|
238
|
-
],
|
|
239
|
-
},
|
|
240
|
-
],
|
|
331
|
+
name: 'hero',
|
|
332
|
+
spec: heroArt(blogPalette(), 'radial', 7),
|
|
333
|
+
alt: 'A writer’s desk',
|
|
334
|
+
photo: 'blog/hero.jpg',
|
|
241
335
|
},
|
|
242
336
|
{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
337
|
+
name: 'quote-avatar',
|
|
338
|
+
spec: avatarArt(blogPalette(), 3),
|
|
339
|
+
alt: 'Abstract avatar mark for a reader quote',
|
|
340
|
+
},
|
|
341
|
+
// L26 D2/D3: the About page had nothing but two paragraphs of text and no
|
|
342
|
+
// visual at all — a different seed from `quote-avatar` (7, not 3) so the
|
|
343
|
+
// two abstract marks read as distinct rather than a copy-pasted image.
|
|
344
|
+
// No bundled photo fits an author portrait (the nine real photos in
|
|
345
|
+
// `assets/photos/blog/` are the hero backdrop and eight post covers, all
|
|
346
|
+
// already spoken for), and there is no image-generation key to make a new
|
|
347
|
+
// one — the procedural generator is the honest fallback here, exactly as
|
|
348
|
+
// it already is for `quote-avatar` and the five press logos below.
|
|
349
|
+
{
|
|
350
|
+
name: 'about-avatar',
|
|
351
|
+
spec: avatarArt(blogPalette(), 7),
|
|
352
|
+
alt: 'Abstract avatar mark for the About page',
|
|
259
353
|
},
|
|
354
|
+
...[1, 2, 3, 4, 5].map((n) => ({
|
|
355
|
+
name: `logo-${n}`,
|
|
356
|
+
spec: logoArt(n),
|
|
357
|
+
alt: `Neutral wordmark placeholder ${n}`,
|
|
358
|
+
})),
|
|
359
|
+
...BLOG_DEMO_POSTS.map((demo, index) => {
|
|
360
|
+
const photo = BLOG_POST_PHOTOS[demo.slug];
|
|
361
|
+
return {
|
|
362
|
+
name: `post-${demo.slug}`,
|
|
363
|
+
spec: coverArt(blogPalette(), index + 1),
|
|
364
|
+
alt: `Cover art for "${demo.title}"`,
|
|
365
|
+
...(photo === undefined ? {} : { photo }),
|
|
366
|
+
};
|
|
367
|
+
}),
|
|
260
368
|
];
|
|
369
|
+
/** Header/footer navigation and the header call-to-action button (L25 D4). */
|
|
370
|
+
export const BLOG_MENUS = {
|
|
371
|
+
header: [
|
|
372
|
+
{ label: 'Home' },
|
|
373
|
+
{ label: 'Writing', url: '/blog' },
|
|
374
|
+
{ label: 'About', url: '/about' },
|
|
375
|
+
],
|
|
376
|
+
footer: [
|
|
377
|
+
{ label: 'About', url: '/about' },
|
|
378
|
+
{ label: 'Archive', url: '/archive' },
|
|
379
|
+
{ label: 'RSS', url: '/feed.xml' },
|
|
380
|
+
],
|
|
381
|
+
headerAction: { label: 'Subscribe', url: '/#newsletter' },
|
|
382
|
+
};
|
|
383
|
+
export const BLOG_SITE_SETTINGS = {
|
|
384
|
+
'general.tagline': 'Notes on reading, building and writing things down.',
|
|
385
|
+
'general.socialLinks': [
|
|
386
|
+
{ label: 'Mastodon', url: 'https://mastodon.social/@example' },
|
|
387
|
+
{ label: 'X', url: 'https://x.com/example' },
|
|
388
|
+
{ label: 'GitHub', url: 'https://github.com/example' },
|
|
389
|
+
],
|
|
390
|
+
'general.footerNote': 'A personal blog, scaffolded by create-cogenta.',
|
|
391
|
+
};
|
|
261
392
|
export const BLOG_RECOMMENDED_AGENTS = [
|
|
262
393
|
{
|
|
263
394
|
name: 'seoAgent',
|
|
@@ -270,35 +401,234 @@ export const BLOG_RECOMMENDED_AGENTS = [
|
|
|
270
401
|
reason: 'Flags terminology drift and topic gaps across the post archive.',
|
|
271
402
|
},
|
|
272
403
|
];
|
|
404
|
+
/**
|
|
405
|
+
* `home` (the featured-post hero, a live "Latest" grid, the topics rail, a
|
|
406
|
+
* reader quote, an editorial "From the archive" list, a newsletter panel, a
|
|
407
|
+
* press strip and an FAQ — eight blocks, the brief's own order) and `about`.
|
|
408
|
+
* `media` (`SeedContext.media`, L25 task A0b) supplies the hero backdrop and
|
|
409
|
+
* the quote avatar; a function of it rather than a static const for the same
|
|
410
|
+
* reason `store.ts`'s `buildStoreDemoPages` is.
|
|
411
|
+
*/
|
|
412
|
+
export function buildBlogDemoPages(media) {
|
|
413
|
+
const featured = BLOG_DEMO_POSTS[0];
|
|
414
|
+
if (featured === undefined) {
|
|
415
|
+
throw new CogentaError({
|
|
416
|
+
code: 'BLUEPRINT_REGISTRY_CORRUPT',
|
|
417
|
+
message: 'BLOG_DEMO_POSTS is empty.',
|
|
418
|
+
hint: 'The blog blueprint needs at least one demo post to feature on its home page.',
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
const logoItems = [1, 2, 3, 4, 5]
|
|
422
|
+
.filter((n) => media[`logo-${n}`] !== undefined)
|
|
423
|
+
.map((n) => ({ _key: `demo-logo-${n}`, media: media[`logo-${n}`] }));
|
|
424
|
+
const aboutAvatar = media['about-avatar'];
|
|
425
|
+
return [
|
|
426
|
+
{
|
|
427
|
+
title: 'Home',
|
|
428
|
+
slug: 'home',
|
|
429
|
+
blocks: [
|
|
430
|
+
{
|
|
431
|
+
_key: 'demo-home-hero',
|
|
432
|
+
_type: 'hero',
|
|
433
|
+
_version: BLOCK_VERSION,
|
|
434
|
+
eyebrow: 'Featured',
|
|
435
|
+
title: featured.title,
|
|
436
|
+
subtitle: featured.excerpt,
|
|
437
|
+
...(media.hero === undefined ? {} : { media: media.hero }),
|
|
438
|
+
actions: [{ label: 'Read the story', target: { href: `/blog/${featured.slug}` } }],
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
_key: 'demo-home-latest',
|
|
442
|
+
_type: 'collectionList',
|
|
443
|
+
_version: BLOCK_VERSION,
|
|
444
|
+
title: 'Latest',
|
|
445
|
+
collection: 'post',
|
|
446
|
+
sort: { field: 'createdAt', direction: 'desc' },
|
|
447
|
+
limit: 6,
|
|
448
|
+
layout: 'grid',
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
_key: 'demo-home-topics',
|
|
452
|
+
_type: 'featureGrid',
|
|
453
|
+
_version: BLOCK_VERSION,
|
|
454
|
+
title: 'Topics',
|
|
455
|
+
items: [
|
|
456
|
+
{
|
|
457
|
+
_key: 'demo-topic-1',
|
|
458
|
+
icon: 'book',
|
|
459
|
+
title: 'Reading',
|
|
460
|
+
text: 'What I read, and why it stuck.',
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
_key: 'demo-topic-2',
|
|
464
|
+
icon: 'code',
|
|
465
|
+
title: 'Building',
|
|
466
|
+
text: 'Small tools, made to be used once.',
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
_key: 'demo-topic-3',
|
|
470
|
+
icon: 'pen',
|
|
471
|
+
title: 'Writing',
|
|
472
|
+
text: 'How a draft becomes a post.',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
_key: 'demo-topic-4',
|
|
476
|
+
icon: 'coffee',
|
|
477
|
+
title: 'Craft',
|
|
478
|
+
text: 'The unglamorous parts of making things.',
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
_key: 'demo-home-quote',
|
|
484
|
+
_type: 'quote',
|
|
485
|
+
_version: BLOCK_VERSION,
|
|
486
|
+
text: 'I started reading this blog for the writing advice and stayed for everything else.',
|
|
487
|
+
author: 'A. Reader',
|
|
488
|
+
role: 'Subscriber since issue one',
|
|
489
|
+
...(media['quote-avatar'] === undefined ? {} : { avatar: media['quote-avatar'] }),
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
_key: 'demo-home-archive',
|
|
493
|
+
_type: 'collectionList',
|
|
494
|
+
_version: BLOCK_VERSION,
|
|
495
|
+
title: 'From the archive',
|
|
496
|
+
collection: 'post',
|
|
497
|
+
sort: { field: 'createdAt', direction: 'asc' },
|
|
498
|
+
limit: 5,
|
|
499
|
+
layout: 'list',
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
_key: 'demo-home-newsletter',
|
|
503
|
+
_type: 'cta',
|
|
504
|
+
_version: BLOCK_VERSION,
|
|
505
|
+
title: 'Get the weekly letter',
|
|
506
|
+
text: 'One email, every Thursday, no more than five minutes to read.',
|
|
507
|
+
actions: [
|
|
508
|
+
{ label: 'Subscribe', target: { href: '/#newsletter' }, emphasis: 'primary' },
|
|
509
|
+
{ label: 'See a past issue', target: { href: '/archive' } },
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
// Omitted entirely, rather than sent with an empty `logos` array,
|
|
513
|
+
// when no logo mark was actually ingested (e.g. `seedDemoContent`
|
|
514
|
+
// called with `media: {}`) — contract B requires at least one item.
|
|
515
|
+
...(logoItems.length === 0
|
|
516
|
+
? []
|
|
517
|
+
: [
|
|
518
|
+
{
|
|
519
|
+
_key: 'demo-home-featured-in',
|
|
520
|
+
_type: 'logoStrip',
|
|
521
|
+
_version: BLOCK_VERSION,
|
|
522
|
+
caption: 'As featured in',
|
|
523
|
+
logos: logoItems,
|
|
524
|
+
},
|
|
525
|
+
]),
|
|
526
|
+
{
|
|
527
|
+
_key: 'demo-home-faq',
|
|
528
|
+
_type: 'faq',
|
|
529
|
+
_version: BLOCK_VERSION,
|
|
530
|
+
title: 'About this blog',
|
|
531
|
+
items: [
|
|
532
|
+
{
|
|
533
|
+
_key: 'demo-faq-1',
|
|
534
|
+
question: 'How often do you publish?',
|
|
535
|
+
answer: [
|
|
536
|
+
paragraph('Every other Thursday, with the occasional extra when something is urgent.'),
|
|
537
|
+
],
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
_key: 'demo-faq-2',
|
|
541
|
+
question: 'Do you accept guest posts?',
|
|
542
|
+
answer: [
|
|
543
|
+
paragraph('Not currently — everything here is written and edited by one person.'),
|
|
544
|
+
],
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
_key: 'demo-faq-3',
|
|
548
|
+
question: 'Is there an RSS feed?',
|
|
549
|
+
answer: [paragraph('Yes — the link is in the footer of every page.')],
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
_key: 'demo-faq-4',
|
|
553
|
+
question: 'Can I republish a post?',
|
|
554
|
+
answer: [paragraph('With attribution and a link back, always — just ask first.')],
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
title: 'About',
|
|
562
|
+
slug: 'about',
|
|
563
|
+
blocks: [
|
|
564
|
+
// L26 D2/D3: a small figure above the fold — the page read as bare
|
|
565
|
+
// with nothing but two short paragraphs and no visual at all.
|
|
566
|
+
// `align: 'start'` keeps it a modest author-photo size (half the
|
|
567
|
+
// reading measure, not a full-bleed hero) rather than stretching an
|
|
568
|
+
// abstract 600×600 mark across the whole page width.
|
|
569
|
+
...(aboutAvatar === undefined
|
|
570
|
+
? []
|
|
571
|
+
: [
|
|
572
|
+
{
|
|
573
|
+
_key: 'demo-about-avatar',
|
|
574
|
+
_type: 'mediaFigure',
|
|
575
|
+
_version: BLOCK_VERSION,
|
|
576
|
+
media: aboutAvatar,
|
|
577
|
+
ratio: '1:1',
|
|
578
|
+
align: 'start',
|
|
579
|
+
},
|
|
580
|
+
]),
|
|
581
|
+
{
|
|
582
|
+
_key: 'demo-about-prose',
|
|
583
|
+
_type: 'prose',
|
|
584
|
+
_version: BLOCK_VERSION,
|
|
585
|
+
body: [
|
|
586
|
+
paragraph('Ink & Paper started as a running list of one idea per book, kept so I would stop forgetting ' +
|
|
587
|
+
'what I had just read. It grew into a place for the smaller things too: the desk setups that ' +
|
|
588
|
+
'did not last, the tools that quietly disappeared, the habit that finally stuck after a dozen ' +
|
|
589
|
+
'that did not.'),
|
|
590
|
+
paragraph('Nothing here is written to sound like advice. Most of it is closer to a lab notebook — what I ' +
|
|
591
|
+
'tried, what broke, and the one detail that turned out to matter more than the rest. If a post ' +
|
|
592
|
+
'reads as useful to someone else, that is a bonus I do not plan around.'),
|
|
593
|
+
paragraph('This is a demo blog, scaffolded by create-cogenta from the "blog" blueprint. Its posts, ' +
|
|
594
|
+
'categories, tags and this very page were seeded by the installer so there is real content to ' +
|
|
595
|
+
'look at from the first run, not an empty admin screen.'),
|
|
596
|
+
paragraph('Everything here — the schema, the content, the skin — is a normal part of the site and is ' +
|
|
597
|
+
'meant to be edited, renamed or deleted the moment the defaults stop fitting.'),
|
|
598
|
+
],
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
},
|
|
602
|
+
];
|
|
603
|
+
}
|
|
273
604
|
/**
|
|
274
605
|
* Inserts the `blog` blueprint's demo content through the real `ContentStore`
|
|
275
|
-
* — never mocked (house rule)
|
|
276
|
-
* rows to look at, not a claim that it does.
|
|
606
|
+
* — never mocked (house rule).
|
|
277
607
|
*/
|
|
278
|
-
async function seedBlogDemoContent(
|
|
279
|
-
const
|
|
280
|
-
const
|
|
608
|
+
async function seedBlogDemoContent(ctx) {
|
|
609
|
+
const { db, defaultLocale, adminId, media } = ctx;
|
|
610
|
+
const categoryStore = createTaxonomyStore({ db, taxonomy: category });
|
|
611
|
+
const tagStore = createTaxonomyStore({ db, taxonomy: tag });
|
|
281
612
|
const postStore = createContentStore({ db, collection: post, defaultLocale });
|
|
282
613
|
const pageStore = createContentStore({ db, collection: page, defaultLocale });
|
|
283
614
|
const categoryIdBySlug = new Map();
|
|
284
615
|
for (const demo of BLOG_DEMO_CATEGORIES) {
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
values: { name: demo.name, slug: demo.slug },
|
|
616
|
+
const term = await categoryStore.create({
|
|
617
|
+
slug: demo.slug,
|
|
618
|
+
labels: { [defaultLocale]: demo.name },
|
|
289
619
|
});
|
|
290
|
-
categoryIdBySlug.set(demo.slug,
|
|
620
|
+
categoryIdBySlug.set(demo.slug, term.id);
|
|
291
621
|
}
|
|
292
622
|
const tagIdBySlug = new Map();
|
|
293
623
|
for (const demo of BLOG_DEMO_TAGS) {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
values: { name: demo.name, slug: demo.slug },
|
|
624
|
+
const term = await tagStore.create({
|
|
625
|
+
slug: demo.slug,
|
|
626
|
+
labels: { [defaultLocale]: demo.name },
|
|
298
627
|
});
|
|
299
|
-
tagIdBySlug.set(demo.slug,
|
|
628
|
+
tagIdBySlug.set(demo.slug, term.id);
|
|
300
629
|
}
|
|
301
630
|
for (const demo of BLOG_DEMO_POSTS) {
|
|
631
|
+
const cover = media[`post-${demo.slug}`];
|
|
302
632
|
await postStore.create({
|
|
303
633
|
status: 'published',
|
|
304
634
|
createdBy: adminId,
|
|
@@ -309,10 +639,11 @@ async function seedBlogDemoContent(db, defaultLocale, adminId) {
|
|
|
309
639
|
body: demo.body,
|
|
310
640
|
category: categoryIdBySlug.get(demo.categorySlug) ?? null,
|
|
311
641
|
tags: demo.tagSlugs.map((slug) => tagIdBySlug.get(slug)).filter((id) => id !== undefined),
|
|
642
|
+
...(cover === undefined ? {} : { coverImage: cover }),
|
|
312
643
|
},
|
|
313
644
|
});
|
|
314
645
|
}
|
|
315
|
-
for (const demo of
|
|
646
|
+
for (const demo of buildBlogDemoPages(media)) {
|
|
316
647
|
await pageStore.create({
|
|
317
648
|
status: 'published',
|
|
318
649
|
createdBy: adminId,
|
|
@@ -323,7 +654,12 @@ async function seedBlogDemoContent(db, defaultLocale, adminId) {
|
|
|
323
654
|
}
|
|
324
655
|
export const blogContentPack = {
|
|
325
656
|
collections: BLOG_COLLECTIONS,
|
|
657
|
+
taxonomies: BLOG_TAXONOMIES,
|
|
326
658
|
recommendedAgents: BLOG_RECOMMENDED_AGENTS,
|
|
327
659
|
seedDemoContent: seedBlogDemoContent,
|
|
660
|
+
defaultTheme: '@cogenta/theme-blog',
|
|
661
|
+
menus: BLOG_MENUS,
|
|
662
|
+
siteSettings: BLOG_SITE_SETTINGS,
|
|
663
|
+
mediaSpecs: BLOG_MEDIA_SPECS,
|
|
328
664
|
};
|
|
329
665
|
//# sourceMappingURL=blog.js.map
|