@digital-gravy/etch-connector 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +8 -9
- package/dist/guidance.js +65 -1
- package/dist/protocol.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ return groups.length;
|
|
|
132
132
|
`etch-connector tabs` reports, per tab, which Etch runtime it is and which
|
|
133
133
|
surfaces that runtime backs (read off `etch.environment`), then prints the
|
|
134
134
|
authoring rules that follow from it: the dynamic-data model (data sources vs
|
|
135
|
-
loops), the media lookup, the namespaces that throw `NOT_AVAILABLE`, and the
|
|
135
|
+
loops), the route tree, the content model, the media lookup, the namespaces that throw `NOT_AVAILABLE`, and the
|
|
136
136
|
block types the runtime can construct. Not every runtime backs every surface, so
|
|
137
137
|
read that before writing scripts — and branch on a capability, never on the
|
|
138
138
|
product name. `eval` names the runtime it targets on stderr as a reminder, and
|
package/dist/cli.js
CHANGED
|
@@ -522,7 +522,7 @@ hand-written eval.
|
|
|
522
522
|
|
|
523
523
|
AGENT: \`etch-connector tabs\` prints, per connected tab, which runtime it is and
|
|
524
524
|
which surfaces that runtime backs (from \`etch.environment\`), followed by the
|
|
525
|
-
authoring rules for it — the dynamic-data model, the media lookup, the
|
|
525
|
+
authoring rules for it — the dynamic-data model, the route tree, the content model, the media lookup, the
|
|
526
526
|
namespaces that throw, and the block types it can construct. Read that before
|
|
527
527
|
writing scripts: not every runtime backs every surface, so the same script does
|
|
528
528
|
not work everywhere. Branch on a capability, never on the product name.
|
|
@@ -602,12 +602,10 @@ render what you author):
|
|
|
602
602
|
(specialized "image" is the URL-based picker — NOT for mediaId.) Instances
|
|
603
603
|
then pass the numeric media ID, e.g. image: "10" — not the file URL.
|
|
604
604
|
|
|
605
|
-
Classes.
|
|
606
|
-
|
|
607
|
-
\`styles\` is
|
|
608
|
-
|
|
609
|
-
saveComponentEditModeAsync() → exitComponentEditMode(). An existing class
|
|
610
|
-
name re-links to its style record automatically (styling is preserved).
|
|
605
|
+
Classes. Set attributes.class in authored JSON (blocks.create(), replace(),
|
|
606
|
+
components.updateAsync()): each class links its style record, and a missing
|
|
607
|
+
one is created empty. A block's \`styles\` array is read-only; ids passed in
|
|
608
|
+
it are ignored. etch.blocks.addClass() works on existing blocks.
|
|
611
609
|
|
|
612
610
|
Sections. When you create a top-level section, always tag it
|
|
613
611
|
data-etch-element="section" and nest ONE container inside it as its inner —
|
|
@@ -630,8 +628,9 @@ render what you author):
|
|
|
630
628
|
• Component instance props are attributes: set them at create()/replace()
|
|
631
629
|
time, or update an existing instance in place with
|
|
632
630
|
etch.blocks.update(instanceId, { attributes: { image: "10" } })
|
|
633
|
-
• updateAsync({ blocks })
|
|
634
|
-
|
|
631
|
+
• components.updateAsync({ blocks }) takes the same block JSON as
|
|
632
|
+
blocks.create(). Older Etch builds reject it without \`styles\` on every
|
|
633
|
+
element block; there, pass the class style ids from etch.styles.list().
|
|
635
634
|
• No etch.media API. Where the runtime backs the WordPress media library
|
|
636
635
|
(capability \`wpMedia\` — see \`tabs\`), resolve a URL → media ID via the WP
|
|
637
636
|
REST API:
|
package/dist/guidance.js
CHANGED
|
@@ -16,7 +16,10 @@ import { hasCapability } from './protocol.js';
|
|
|
16
16
|
/** Namespaces that are declared on every runtime but backed only on some. */
|
|
17
17
|
const GATED_NAMESPACES = [
|
|
18
18
|
{ capability: 'loops', namespace: 'etch.loops.*' },
|
|
19
|
-
{
|
|
19
|
+
{
|
|
20
|
+
capability: 'fields',
|
|
21
|
+
namespace: 'etch.fields.* (content fields are on etch.content where backed)'
|
|
22
|
+
},
|
|
20
23
|
{ capability: 'templates', namespace: "etch.navigation.goTo('templates')" }
|
|
21
24
|
];
|
|
22
25
|
const UNKNOWN_RUNTIME = [
|
|
@@ -59,6 +62,65 @@ function dynamicDataGuidance(environment) {
|
|
|
59
62
|
' sources — do not author etch/loop blocks against it.'
|
|
60
63
|
];
|
|
61
64
|
}
|
|
65
|
+
function routesGuidance(environment) {
|
|
66
|
+
if (!hasCapability(environment, 'routes')) {
|
|
67
|
+
return [
|
|
68
|
+
'Routes: etch.routes is absent here — the property does not exist, so',
|
|
69
|
+
' feature-detect it rather than calling it and catching.'
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
return [
|
|
73
|
+
'Routes — the site URLs are a route tree, managed through etch.routes.*:',
|
|
74
|
+
' listAsync(), createAsync({ segment, parentId?, source?, templateId? }),',
|
|
75
|
+
' updateAsync(id, patch) and deleteAsync(id) persist immediately. A',
|
|
76
|
+
" segment is static ('about'), a group ('(marketing)') that adds nothing",
|
|
77
|
+
" to the URL, or dynamic ('{this.title.toSlug()}'), served once per",
|
|
78
|
+
' record of its source: record ids in postIds, content type keys in',
|
|
79
|
+
' postTypes. Templates are records listed by',
|
|
80
|
+
' etch.navigation.listTemplatesAsync(); a route renders through its',
|
|
81
|
+
" templateId, or its nearest ancestor's (templateId: null in a patch",
|
|
82
|
+
' inherits again). Read getPathsAsync() after a change — only',
|
|
83
|
+
' `published` paths are built.',
|
|
84
|
+
' Do not build links from .slug (this.slug, item.slug in a loop, or',
|
|
85
|
+
' PostSummary.slug): only a record a static route serves has one. A',
|
|
86
|
+
' record served by a dynamic route has none, and listPostsAsync() reports',
|
|
87
|
+
" '' for it, which reads like the homepage. Spell the link from the",
|
|
88
|
+
" route's own segment instead — /locations/{loc.title.toSlug()}/ under",
|
|
89
|
+
" a 'locations' route with segment '{this.title.toSlug()}' — and check it",
|
|
90
|
+
' against getPathsAsync().',
|
|
91
|
+
' Vocabulary: what users call a content type is a postType in the API',
|
|
92
|
+
' (its key), and a record or page is a post (postId, listPostsAsync()).'
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
function contentGuidance(environment) {
|
|
96
|
+
if (!hasCapability(environment, 'content')) {
|
|
97
|
+
return [
|
|
98
|
+
'Content: etch.content is absent here — the property does not exist, so',
|
|
99
|
+
' feature-detect it rather than calling it and catching.'
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
return [
|
|
103
|
+
'Content — content types, their fields and items, through etch.content.*',
|
|
104
|
+
' (writes persist immediately): listTypesAsync(), getTypeAsync(key),',
|
|
105
|
+
' createTypeAsync({ name, key?, isTemplate?, fields? }), updateTypeAsync(key,',
|
|
106
|
+
' { name?, key? }), deleteTypeAsync(key); addFieldAsync(typeKey, field),',
|
|
107
|
+
' updateFieldAsync(typeKey, fieldKey, patch) — a new key renames and keeps',
|
|
108
|
+
' values — removeFieldAsync(typeKey, fieldKey); listItemsAsync(postType?),',
|
|
109
|
+
' getItemAsync(id), createItemAsync({ postType, title, fields? }),',
|
|
110
|
+
' updateItemAsync(id, { title?, fields? }) — fields merge per key, null',
|
|
111
|
+
' clears — deleteItemAsync(id). Relationship values are item ids.',
|
|
112
|
+
' Templates are items of a type created with isTemplate: true.',
|
|
113
|
+
' Items carry title and field values only: build their blocks by opening',
|
|
114
|
+
' them (etch.navigation.openPostAsync(id) / openTemplateAsync(id)) and',
|
|
115
|
+
' using etch.blocks.',
|
|
116
|
+
" Refused: fields on a template type, changing isTemplate or an item's",
|
|
117
|
+
' postType, deleting the page type, a template type as relation target,',
|
|
118
|
+
' any write to a relationshipBacklink (READONLY), unknown field keys,',
|
|
119
|
+
' and values their field rejects.',
|
|
120
|
+
' Vocabulary: a content type is a postType (its key), an item is a post',
|
|
121
|
+
' (its id is the postId).'
|
|
122
|
+
];
|
|
123
|
+
}
|
|
62
124
|
function mediaGuidance(environment) {
|
|
63
125
|
const binding = ' Bind an etch/dynamic-image by a numeric `mediaId` attribute, never a URL.';
|
|
64
126
|
if (hasCapability(environment, 'wpMedia')) {
|
|
@@ -128,6 +190,8 @@ export function environmentGuidance(environment) {
|
|
|
128
190
|
`Runtime: ${environment.product} — backs ${backed.length > 0 ? backed.join(', ') : 'no optional surface'}.`
|
|
129
191
|
],
|
|
130
192
|
dynamicDataGuidance(environment),
|
|
193
|
+
routesGuidance(environment),
|
|
194
|
+
contentGuidance(environment),
|
|
131
195
|
mediaGuidance(environment),
|
|
132
196
|
unavailableGuidance(environment),
|
|
133
197
|
blockTypeGuidance(environment)
|
package/dist/protocol.js
CHANGED
|
@@ -16,7 +16,9 @@ const CAPABILITY_NAMES = [
|
|
|
16
16
|
'dataSources',
|
|
17
17
|
'fields',
|
|
18
18
|
'templates',
|
|
19
|
-
'wpMedia'
|
|
19
|
+
'wpMedia',
|
|
20
|
+
'routes',
|
|
21
|
+
'content'
|
|
20
22
|
];
|
|
21
23
|
/** Whether a runtime backs a capability. Absent or unknown counts as no. */
|
|
22
24
|
export function hasCapability(environment, capability) {
|