mechanica-shared 2.0.0-alpha.9 → 2.0.2
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 +7 -4
- package/dist/block-format.js +4 -0
- package/dist/index.js +39 -8
- package/dist/page-format.js +2 -0
- package/dist/types/compose.d.ts +10 -3
- package/dist/types/generate-page.d.ts +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/page-format.d.ts +5 -0
- package/dist/types/types.d.ts +45 -1
- package/package.json +4 -8
- package/src/block-format.ts +0 -100
- package/src/compose.ts +0 -120
- package/src/composer-manifest.ts +0 -45
- package/src/fields.ts +0 -155
- package/src/generate-page.ts +0 -212
- package/src/index.ts +0 -119
- package/src/locale.ts +0 -88
- package/src/migrate.ts +0 -44
- package/src/page-format.ts +0 -422
- package/src/query-engine.ts +0 -175
- package/src/schema.ts +0 -137
- package/src/seo.ts +0 -228
- package/src/translation.ts +0 -169
- package/src/types.ts +0 -334
- package/src/validate-links.ts +0 -56
package/src/types.ts
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core data types shared between the plugin runtime, the editor, and the
|
|
3
|
-
* (future) render service. This module is intentionally DOM- and
|
|
4
|
-
* framework-free so the render side can import it cleanly.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { LocalesConfig } from './locale'
|
|
8
|
-
|
|
9
|
-
/** Scope at which a {@link DataEntry} lives. */
|
|
10
|
-
export type DataScope = 'site' | 'folder' | 'page'
|
|
11
|
-
|
|
12
|
-
/** Metadata about the current page, exposed via `usePageData`. */
|
|
13
|
-
export interface PageMeta {
|
|
14
|
-
title?: string
|
|
15
|
-
path?: string
|
|
16
|
-
meta?: Record<string, unknown>
|
|
17
|
-
/**
|
|
18
|
-
* Set on paginated variants of a page (`/blog/2`, …): which chunk of its
|
|
19
|
-
* paginated query this URL shows. Page 1 is the base path and carries none.
|
|
20
|
-
*/
|
|
21
|
-
pagination?: { page: number; pageCount?: number }
|
|
22
|
-
/** The locale this page renders in (multi-language sites). Omitted when i18n is off. */
|
|
23
|
-
locale?: string
|
|
24
|
-
/** Which locales this logical page has a translation for (the default included). */
|
|
25
|
-
locales?: string[]
|
|
26
|
-
/**
|
|
27
|
-
* True when the requested locale has no translation file and the page is
|
|
28
|
-
* rendering the default-locale content as a fallback (dev only — the editor
|
|
29
|
-
* shows a "translate this page" banner). Never set at export.
|
|
30
|
-
*/
|
|
31
|
-
localeFallback?: boolean
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Editor-facing metadata describing a *block type* — its schema and palette
|
|
36
|
-
* presentation, not its placed content. Produced by the block compiler from a
|
|
37
|
-
* `defineBlock(...)` descriptor.
|
|
38
|
-
*/
|
|
39
|
-
export interface Block {
|
|
40
|
-
id: string
|
|
41
|
-
name: string
|
|
42
|
-
/** Palette grouping (was `group` in v1). */
|
|
43
|
-
category?: string
|
|
44
|
-
/** Icon key shown in the block palette. */
|
|
45
|
-
icon?: string
|
|
46
|
-
description?: string
|
|
47
|
-
/** Sort order within a category. */
|
|
48
|
-
order?: number
|
|
49
|
-
/** Hidden from the palette. */
|
|
50
|
-
hidden?: boolean
|
|
51
|
-
/** Available only in dev, stripped from production output. */
|
|
52
|
-
devOnly?: boolean
|
|
53
|
-
/**
|
|
54
|
-
* Restrict the block to pages under these folders (folder paths relative to
|
|
55
|
-
* `pages/`, e.g. `'docs'`; nested folders match by prefix). Omitted = offered
|
|
56
|
-
* everywhere. Placed blocks always keep rendering — this only filters what
|
|
57
|
-
* the palette offers.
|
|
58
|
-
*/
|
|
59
|
-
folders?: string[]
|
|
60
|
-
/**
|
|
61
|
-
* Schema version of this block (defaults to 1). Bump it together with a
|
|
62
|
-
* `migrate` function whenever a saved page's data needs reshaping (renamed
|
|
63
|
-
* prop, changed type); placed blocks record the version they were written
|
|
64
|
-
* with and migrate on load.
|
|
65
|
-
*/
|
|
66
|
-
version?: number
|
|
67
|
-
/**
|
|
68
|
-
* Upgrade a placed block's data from an older schema version. Receives the
|
|
69
|
-
* stored data and the version it was written with; mutate it in place or
|
|
70
|
-
* return the replacement. Must handle every `from < version`.
|
|
71
|
-
*/
|
|
72
|
-
migrate?: (data: Record<string, unknown>, from: number) => Record<string, unknown> | undefined | void
|
|
73
|
-
/**
|
|
74
|
-
* Example prop values used when the block renders outside a page — the
|
|
75
|
-
* palette hover preview and the `/@mechanica/preview` route (`mechanica shot`).
|
|
76
|
-
* Merged over schema defaults, so it only needs the props that matter visually.
|
|
77
|
-
* A `$slots` key fills the block's slots with child blocks (see
|
|
78
|
-
* `PreviewSlotEntry` in `mechanica`); unfilled slots preview as placeholders.
|
|
79
|
-
*/
|
|
80
|
-
previewData?: Record<string, unknown>
|
|
81
|
-
/** compact-json-schema describing the editable props. */
|
|
82
|
-
props?: Record<string, unknown>
|
|
83
|
-
/** Slot name → slot metadata (currently `true`). */
|
|
84
|
-
slots?: Record<string, unknown>
|
|
85
|
-
/**
|
|
86
|
-
* Marks a block produced by the Block Composer (a {@link ComposedBlockDefinition}),
|
|
87
|
-
* rather than a compiled SFC. The page editor uses it to offer Edit/Delete and
|
|
88
|
-
* to group these under "Site blocks". Set by the composed-block factory.
|
|
89
|
-
*/
|
|
90
|
-
composed?: boolean
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* One entry in the Block Composer's components manifest (`src/composer.ts`,
|
|
95
|
-
* declared with `defineComposerComponents`). It exposes a site's own design
|
|
96
|
-
* system — a plain Vue SFC plus the compact-json-schema for its editable props —
|
|
97
|
-
* as building material in the composer, without touching the block compiler.
|
|
98
|
-
*
|
|
99
|
-
* DOM-free here: `component` is typed `unknown`; the `mechanica` package narrows
|
|
100
|
-
* it to a Vue `Component`. A manifest value may also be a bare string, which
|
|
101
|
-
* re-exposes an existing compiled block (by id) in the composer palette.
|
|
102
|
-
*/
|
|
103
|
-
export interface ComposerComponentDefinition {
|
|
104
|
-
/** The Vue component rendered for this id (narrowed to `Component` in `mechanica`). */
|
|
105
|
-
component?: unknown
|
|
106
|
-
/** Display name in the composer's Components palette. */
|
|
107
|
-
name: string
|
|
108
|
-
/** VIcon name (or raw `<svg>`) for the palette card. */
|
|
109
|
-
icon?: string
|
|
110
|
-
/** compact-json-schema describing the editable props (same field registry as blocks). */
|
|
111
|
-
props?: Record<string, unknown>
|
|
112
|
-
/** Example prop values for the canvas / previews. */
|
|
113
|
-
previewData?: Record<string, unknown>
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** A components-manifest entry: a definition, or a string id re-exposing a compiled block. */
|
|
117
|
-
export type ComposerComponentEntry = ComposerComponentDefinition | string
|
|
118
|
-
|
|
119
|
-
/** The built-in composer element kinds a design-system class can attach to. */
|
|
120
|
-
export type ComposerElementKind = 'frame' | 'text' | 'image'
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* One design-system CSS class the site offers to the Block Composer (via
|
|
124
|
-
* `defineComposer`'s `classes`). The manifest key IS the CSS class name — the
|
|
125
|
-
* developer writes `.container` in their stylesheet, declares it here, and the
|
|
126
|
-
* composer offers it as a **Style** on matching elements. See COMPOSER-MANIFEST.md.
|
|
127
|
-
*/
|
|
128
|
-
export interface ComposerClassDefinition {
|
|
129
|
-
/** Label shown in the Style select; defaults to the class name. */
|
|
130
|
-
title?: string
|
|
131
|
-
/** Which element kinds may pick this class. */
|
|
132
|
-
on: ComposerElementKind | ComposerElementKind[]
|
|
133
|
-
/**
|
|
134
|
-
* Optional group. Classes sharing a group are mutually exclusive (one Style
|
|
135
|
-
* select per group, single-pick); classes in different groups stack on the
|
|
136
|
-
* element. Ungrouped classes share one implicit default group labelled "Style".
|
|
137
|
-
* The developer guarantees groups are orthogonal, so precedence stays clear.
|
|
138
|
-
*/
|
|
139
|
-
group?: string
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* A `classes` entry: the full definition, or a shorthand — a bare kind (`'text'`)
|
|
144
|
-
* or an array of kinds (`['frame', 'image']`), with the title defaulting to the
|
|
145
|
-
* class name.
|
|
146
|
-
*/
|
|
147
|
-
export type ComposerClassEntry = ComposerClassDefinition | ComposerElementKind | ComposerElementKind[]
|
|
148
|
-
|
|
149
|
-
/** A class entry normalized for the composer (shorthands unfolded). */
|
|
150
|
-
export interface ComposerClassDef {
|
|
151
|
-
/** The CSS class name (the manifest key). */
|
|
152
|
-
cls: string
|
|
153
|
-
/** Label for the Style select. */
|
|
154
|
-
title: string
|
|
155
|
-
/** Element kinds that may pick it. */
|
|
156
|
-
kinds: ComposerElementKind[]
|
|
157
|
-
/** Group key (undefined = the default "Style" group). Same group = mutually exclusive. */
|
|
158
|
-
group?: string
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* The site's element-system breakpoints (max-widths in px). Two fixed tiers;
|
|
163
|
-
* only the widths are configurable. Consumed by the generated element CSS and
|
|
164
|
-
* the composer's device switcher. Defaults: `{ md: 1024, sm: 640 }`.
|
|
165
|
-
*/
|
|
166
|
-
export interface ComposerBreakpoints {
|
|
167
|
-
md: number
|
|
168
|
-
sm: number
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* The Block Composer manifest (`src/composer.ts`, declared with `defineComposer`).
|
|
173
|
-
* A single home for the site's design system as seen by the composer: its
|
|
174
|
-
* components, its CSS classes, and its breakpoints. All sections are optional —
|
|
175
|
-
* a site that declares only `components` behaves exactly as before the split.
|
|
176
|
-
*/
|
|
177
|
-
export interface ComposerManifest {
|
|
178
|
-
/** The site's design-system components (was `defineComposerComponents`). */
|
|
179
|
-
components?: Record<string, ComposerComponentEntry>
|
|
180
|
-
/** Design-system CSS classes offered as element **Style** in the composer. */
|
|
181
|
-
classes?: Record<string, ComposerClassEntry>
|
|
182
|
-
/** Element-system breakpoints (max-widths, px). Numeric literals only. */
|
|
183
|
-
breakpoints?: Partial<ComposerBreakpoints>
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* A designer-assembled block ("composed block"): a named, parameterized
|
|
188
|
-
* {@link ContentBlock} subtree saved as data. It is not compiled — at render
|
|
189
|
-
* time it *expands* into its template through the normal block-render pipeline,
|
|
190
|
-
* so it behaves like any other block (palette, settings form, export, shots).
|
|
191
|
-
*
|
|
192
|
-
* Authored in the Block Composer and persisted as `.mech/blocks/<id>.block.yml`
|
|
193
|
-
* (codec in `mechanica-shared/block-format`); a developer can hand-write the
|
|
194
|
-
* same file. See PLAN.md.
|
|
195
|
-
*/
|
|
196
|
-
export interface ComposedBlockDefinition {
|
|
197
|
-
/** Stable, kebab-case id; unique across compiled blocks and composed blocks. */
|
|
198
|
-
id: string
|
|
199
|
-
/** Display name shown in the palette. */
|
|
200
|
-
name: string
|
|
201
|
-
/** VIcon name shown on the palette card. */
|
|
202
|
-
icon?: string
|
|
203
|
-
/** Palette grouping; defaults to a "Site blocks" group in the editor. */
|
|
204
|
-
category?: string
|
|
205
|
-
/**
|
|
206
|
-
* compact-json-schema for the props exposed out of the template (§ prop
|
|
207
|
-
* bindings). Placed instances get an auto-generated settings form from this,
|
|
208
|
-
* exactly like a compiled block's `props`.
|
|
209
|
-
*/
|
|
210
|
-
props?: Record<string, unknown>
|
|
211
|
-
/**
|
|
212
|
-
* Example prop values for previews (palette hover, `/@mechanica/preview`,
|
|
213
|
-
* `mechanica shot`). Captured from the canvas values at author time.
|
|
214
|
-
*/
|
|
215
|
-
previewData?: Record<string, unknown>
|
|
216
|
-
/**
|
|
217
|
-
* The block body: element/block nodes. A value inside a node's `data` may be
|
|
218
|
-
* a {@link PropBinding} (`{ $bind: 'propName' }`) that resolves from the
|
|
219
|
-
* instance's props at render time (see `resolveComposedTemplate`).
|
|
220
|
-
*/
|
|
221
|
-
template: ContentBlock[]
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* A placeholder inside a composed template's data, replaced at render time by
|
|
226
|
-
* the value of the named prop on the placed instance.
|
|
227
|
-
*/
|
|
228
|
-
export interface PropBinding {
|
|
229
|
-
$bind: string
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** A block placed in a page's content tree. */
|
|
233
|
-
export interface ContentBlock {
|
|
234
|
-
id: string
|
|
235
|
-
blockId: string
|
|
236
|
-
data: Record<string, unknown>
|
|
237
|
-
/** Schema version the data was written with (absent = 1); see `Block.version`. */
|
|
238
|
-
v?: number
|
|
239
|
-
/** Either a single default-slot list or a map of named-slot lists. */
|
|
240
|
-
children?: ContentBlock[] | Record<string, ContentBlock[]>
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** A shared data entry declared with `defineData`. */
|
|
244
|
-
export interface DataEntry {
|
|
245
|
-
id: string
|
|
246
|
-
title?: string
|
|
247
|
-
/** compact-json-schema describing the data shape. */
|
|
248
|
-
props?: Record<string, unknown>
|
|
249
|
-
/**
|
|
250
|
-
* Translate this entry per locale (multi-language sites). Its site/folder
|
|
251
|
-
* value is stored per locale (with fallback to the default locale) instead of
|
|
252
|
-
* once, so shared strings (nav labels, footer) can differ by language. Ignored
|
|
253
|
-
* when i18n is off. Page-scoped values are already per-locale (they live in
|
|
254
|
-
* the translation file), so this only affects site/folder scope.
|
|
255
|
-
*/
|
|
256
|
-
localized?: boolean
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* The runtime state serialized onto the page (`window.state`) and read back by
|
|
261
|
-
* the runtime to hydrate.
|
|
262
|
-
*/
|
|
263
|
-
export interface State {
|
|
264
|
-
content: ContentBlock[]
|
|
265
|
-
/** Effective data: site < folder < page merged, with schema defaults filled. */
|
|
266
|
-
data: Record<string, unknown>
|
|
267
|
-
query?: Record<string, unknown>
|
|
268
|
-
/**
|
|
269
|
-
* Scope buckets for the editor, so it can edit each level and tell which
|
|
270
|
-
* entries a page overrides. Only injected in dev (the runtime renders `data`).
|
|
271
|
-
*/
|
|
272
|
-
siteData?: Record<string, unknown>
|
|
273
|
-
folderData?: Record<string, unknown>
|
|
274
|
-
pageData?: Record<string, unknown>
|
|
275
|
-
/** The folder this page lives in (null at the root), so the editor can offer folder scope. */
|
|
276
|
-
folder?: string | null
|
|
277
|
-
/**
|
|
278
|
-
* The default-locale page's content (dev only, on a non-default locale). Lets
|
|
279
|
-
* the editor mark which fields a translation *overrides* vs inherits, and
|
|
280
|
-
* offer a one-click reset to the inherited value. Absent when i18n is off or
|
|
281
|
-
* on the default locale.
|
|
282
|
-
*/
|
|
283
|
-
baseContent?: ContentBlock[]
|
|
284
|
-
/** Base URL the page is served under (for routing/link resolution). */
|
|
285
|
-
baseUrl?: string
|
|
286
|
-
/** Current page metadata. */
|
|
287
|
-
page?: PageMeta
|
|
288
|
-
/**
|
|
289
|
-
* The site's locale configuration (multi-language sites). Present in the
|
|
290
|
-
* serialized state so the runtime can prefix internal links for the current
|
|
291
|
-
* locale and build language switchers. Absent when i18n is off.
|
|
292
|
-
*/
|
|
293
|
-
locales?: LocalesConfig
|
|
294
|
-
/**
|
|
295
|
-
* Dev only: this state belongs to a programmatically generated page
|
|
296
|
-
* ({@link VirtualPage}) that has no `.page.md` file. The editor treats it as
|
|
297
|
-
* read-only — it never queues a save against a nonexistent file.
|
|
298
|
-
*/
|
|
299
|
-
generated?: boolean
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* A page produced programmatically (the plugin's `generatePages` option) instead
|
|
304
|
-
* of from a `.page.md` file — plain, serializable data. The plugin runs its
|
|
305
|
-
* providers at build and bakes the output into the SSR bundle for the static
|
|
306
|
-
* export; the same shape is what a future render backend consumes for on-demand
|
|
307
|
-
* / webhook regeneration. One `VirtualPage` per (logical page × locale).
|
|
308
|
-
*/
|
|
309
|
-
export interface VirtualPage {
|
|
310
|
-
/** Logical (unprefixed) path, e.g. `/docs/api/math/mathf`. Localized per `locale` when served. */
|
|
311
|
-
path: string
|
|
312
|
-
/** The block tree to render — usually one template block parameterized by `data`. */
|
|
313
|
-
content: ContentBlock[]
|
|
314
|
-
/** Page-scoped data (head, layout, block props). Site data still merges under it. */
|
|
315
|
-
data?: Record<string, unknown>
|
|
316
|
-
/** Page meta: `title` (breadcrumb / SEO name), `noindex`, and `{{ page.meta.* }}` hints. */
|
|
317
|
-
meta?: Record<string, unknown>
|
|
318
|
-
/** The locale this page renders in. Omit for the default locale. */
|
|
319
|
-
locale?: string
|
|
320
|
-
/** Every locale this logical page exists in — for hreflang alternates + language switchers. */
|
|
321
|
-
locales?: string[]
|
|
322
|
-
/** Sitemap `<lastmod>` (ISO date). The provider supplies it; there is no file to stat. */
|
|
323
|
-
lastmod?: string
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/** A link target produced by the `smartLink` field type. */
|
|
327
|
-
export interface PageLink {
|
|
328
|
-
id: string
|
|
329
|
-
url?: string
|
|
330
|
-
query?: string
|
|
331
|
-
title: string
|
|
332
|
-
external?: boolean
|
|
333
|
-
openNewTab?: boolean
|
|
334
|
-
}
|
package/src/validate-links.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { Block } from './types'
|
|
2
|
-
import { walkTree, walkSchema } from './schema'
|
|
3
|
-
|
|
4
|
-
/** A link on a page pointing at an internal path no page is exported for. */
|
|
5
|
-
export interface LinkIssue {
|
|
6
|
-
/** The page the broken link lives on. */
|
|
7
|
-
page: string
|
|
8
|
-
/** The internal URL with no matching page. */
|
|
9
|
-
url: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** Normalize an internal URL for page-path comparison (drop query/hash/trailing slash). */
|
|
13
|
-
export function normalizeInternalUrl(url: string): string {
|
|
14
|
-
const bare = url.split(/[?#]/)[0]!
|
|
15
|
-
const trimmed = bare.replace(/\/+$/, '')
|
|
16
|
-
return trimmed === '' ? '/' : trimmed
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Collect the internal link targets on a page: every `smartLink`-formatted
|
|
21
|
-
* field whose URL is site-relative (starts with `/`) and not marked external.
|
|
22
|
-
*/
|
|
23
|
-
export function collectInternalLinks(content: unknown[], blocksMap: Map<string, Block>): string[] {
|
|
24
|
-
const found: string[] = []
|
|
25
|
-
walkTree(content as never, (block: any) => {
|
|
26
|
-
const meta = blocksMap.get(block.blockId)
|
|
27
|
-
if (!meta) return
|
|
28
|
-
walkSchema(block.data, meta.props, (value: any, schema: any) => {
|
|
29
|
-
if (schema?.format !== 'smartLink' || value == null) return
|
|
30
|
-
const url = typeof value === 'string' ? value : value.url
|
|
31
|
-
const external = typeof value === 'object' && value.external === true
|
|
32
|
-
if (typeof url === 'string' && url.startsWith('/') && !external) found.push(url)
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
return found
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Cross-check every page's internal `smartLink` targets against the set of
|
|
40
|
-
* page paths that actually exist. Returns the dead links (empty = all good).
|
|
41
|
-
* Meant for export/deploy time — a warning, not a hard failure, since a target
|
|
42
|
-
* may be intentionally served by something else (redirects, external hosting).
|
|
43
|
-
*/
|
|
44
|
-
export function validateLinks(
|
|
45
|
-
pages: Array<{ path: string; content?: unknown[] }>,
|
|
46
|
-
blocksMap: Map<string, Block>,
|
|
47
|
-
): LinkIssue[] {
|
|
48
|
-
const known = new Set(pages.map((page) => normalizeInternalUrl(page.path)))
|
|
49
|
-
const issues: LinkIssue[] = []
|
|
50
|
-
for (const page of pages) {
|
|
51
|
-
for (const url of collectInternalLinks(page.content ?? [], blocksMap)) {
|
|
52
|
-
if (!known.has(normalizeInternalUrl(url))) issues.push({ page: page.path, url })
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return issues
|
|
56
|
-
}
|