nodality 1.2.9 → 1.2.11
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/bin/mcp-server.mjs +86 -0
- package/bin/nodality.js +12 -0
- package/bin/schema-cli.mjs +72 -0
- package/dist/animator.cjs.js.LICENSE.txt +1 -1
- package/dist/animator.esm.js.LICENSE.txt +1 -1
- package/dist/audionew.cjs.js.LICENSE.txt +1 -1
- package/dist/audionew.esm.js.LICENSE.txt +1 -1
- package/dist/base.cjs.js.LICENSE.txt +1 -1
- package/dist/base.esm.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
- package/dist/bundle.umd.js +1 -1
- package/dist/bundle.umd.js.LICENSE.txt +1 -28
- package/dist/button.cjs.js.LICENSE.txt +1 -1
- package/dist/button.esm.js.LICENSE.txt +1 -1
- package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
- package/dist/card-getter.esm.js.LICENSE.txt +1 -1
- package/dist/center.cjs.js.LICENSE.txt +1 -1
- package/dist/center.esm.js.LICENSE.txt +1 -1
- package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
- package/dist/checkbox.esm.js.LICENSE.txt +1 -1
- package/dist/code.cjs.js.LICENSE.txt +1 -1
- package/dist/code.esm.js.LICENSE.txt +1 -1
- package/dist/container.cjs.js.LICENSE.txt +1 -1
- package/dist/container.esm.js.LICENSE.txt +1 -1
- package/dist/data-list.cjs.js.LICENSE.txt +1 -1
- package/dist/data-list.esm.js.LICENSE.txt +1 -1
- package/dist/designer.cjs.js +1 -1
- package/dist/designer.cjs.js.LICENSE.txt +1 -28
- package/dist/designer.esm.js +1 -1
- package/dist/designer.esm.js.LICENSE.txt +1 -28
- package/dist/element-mapper.cjs.js +1 -1
- package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
- package/dist/element-mapper.esm.js +1 -1
- package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
- package/dist/finalresult.esm.js +1 -1
- package/dist/finalresult.esm.js.LICENSE.txt +1 -28
- package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-card.esm.js.LICENSE.txt +1 -1
- package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
- package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-row.esm.js.LICENSE.txt +1 -1
- package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
- package/dist/floating-input.esm.js.LICENSE.txt +1 -1
- package/dist/free.cjs.js.LICENSE.txt +1 -1
- package/dist/free.esm.js.LICENSE.txt +1 -1
- package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
- package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
- package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
- package/dist/image-picker.esm.js.LICENSE.txt +1 -1
- package/dist/image.cjs.js.LICENSE.txt +1 -1
- package/dist/image.esm.js.LICENSE.txt +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.LICENSE.txt +1 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.LICENSE.txt +1 -28
- package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
- package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
- package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
- package/dist/link-getter.esm.js.LICENSE.txt +1 -1
- package/dist/link.cjs.js.LICENSE.txt +1 -1
- package/dist/link.esm.js.LICENSE.txt +1 -1
- package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
- package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
- package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/picker.cjs.js.LICENSE.txt +1 -1
- package/dist/picker.esm.js.LICENSE.txt +1 -1
- package/dist/progress.cjs.js.LICENSE.txt +1 -1
- package/dist/progress.esm.js.LICENSE.txt +1 -1
- package/dist/radio.cjs.js.LICENSE.txt +1 -1
- package/dist/radio.esm.js.LICENSE.txt +1 -1
- package/dist/range.cjs.js.LICENSE.txt +1 -1
- package/dist/range.esm.js.LICENSE.txt +1 -1
- package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
- package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
- package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-bar.esm.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
- package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
- package/dist/spacer.cjs.js.LICENSE.txt +1 -1
- package/dist/spacer.esm.js.LICENSE.txt +1 -1
- package/dist/stack.cjs.js.LICENSE.txt +1 -1
- package/dist/stack.esm.js.LICENSE.txt +1 -1
- package/dist/stacker.cjs.js.LICENSE.txt +1 -1
- package/dist/stacker.esm.js.LICENSE.txt +1 -1
- package/dist/table.cjs.js.LICENSE.txt +1 -1
- package/dist/table.esm.js.LICENSE.txt +1 -1
- package/dist/text-field.cjs.js.LICENSE.txt +1 -1
- package/dist/text-field.esm.js.LICENSE.txt +1 -1
- package/dist/text.cjs.js.LICENSE.txt +1 -1
- package/dist/text.esm.js.LICENSE.txt +1 -1
- package/dist/theme.cjs.js.LICENSE.txt +1 -1
- package/dist/theme.esm.js.LICENSE.txt +1 -1
- package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
- package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
- package/dist/ulist.cjs.js.LICENSE.txt +1 -1
- package/dist/ulist.esm.js.LICENSE.txt +1 -1
- package/dist/video.cjs.js.LICENSE.txt +1 -1
- package/dist/video.esm.js.LICENSE.txt +1 -1
- package/dist/wrap.cjs.js.LICENSE.txt +1 -1
- package/dist/wrap.esm.js.LICENSE.txt +1 -1
- package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
- package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
- package/layout/animator.js +1 -1
- package/layout/audio.js +1 -1
- package/layout/audionew.js +1 -1
- package/layout/base.js +1 -1
- package/layout/beta-desktop-bar.js +1 -1
- package/layout/beta-mobile-bar.js +1 -1
- package/layout/button.js +1 -1
- package/layout/center.js +1 -1
- package/layout/checkbox.js +1 -1
- package/layout/circle.js +1 -1
- package/layout/code.js +1 -1
- package/layout/container.js +1 -1
- package/layout/dropdown-2025.js +1 -1
- package/layout/flex-card.js +1 -1
- package/layout/flex-grid.js +1 -1
- package/layout/flex-row.js +1 -1
- package/layout/form-components/custom.js +1 -1
- package/layout/form-components/data-list.js +1 -1
- package/layout/form-components/floating-input.js +1 -1
- package/layout/form-components/form-all.js +1 -1
- package/layout/form-components/form.js +1 -1
- package/layout/form-components/image-picker.js +1 -1
- package/layout/form-components/picker.js +1 -1
- package/layout/form-components/radio.js +1 -1
- package/layout/form-components/radiogroup.js +1 -1
- package/layout/form-components/range.js +1 -1
- package/layout/free.js +1 -1
- package/layout/grid-switcher.js +1 -1
- package/layout/grid.js +1 -1
- package/layout/horizontal-scroller.js +1 -1
- package/layout/image.js +1 -1
- package/layout/index.js +1 -1
- package/layout/link.js +1 -1
- package/layout/list.js +1 -1
- package/layout/meta-adder.js +1 -1
- package/layout/modal-2025.js +1 -1
- package/layout/morph.js +1 -1
- package/layout/multiswitcher.js +1 -1
- package/layout/nav-bar.js +1 -1
- package/layout/nav-factor/custom-div.js +1 -1
- package/layout/new-nav-bar.js +1 -1
- package/layout/polygon.js +1 -1
- package/layout/prerender-site.js +1 -1
- package/layout/prerender.js +1 -1
- package/layout/progress.js +1 -1
- package/layout/row.js +1 -1
- package/layout/scroll-video.js +1 -1
- package/layout/side-bar.js +1 -1
- package/layout/side-nav-bar.js +1 -1
- package/layout/simple-bar.js +1 -1
- package/layout/slider-2025.js +1 -1
- package/layout/spacer.js +1 -1
- package/layout/stack.js +1 -1
- package/layout/svg.js +1 -1
- package/layout/switcher.js +1 -1
- package/layout/table.js +1 -1
- package/layout/text-field.js +1 -1
- package/layout/text.js +1 -1
- package/layout/ulist.js +1 -1
- package/layout/video.js +1 -1
- package/layout/wrap.js +1 -1
- package/layout/zoom-card.js +1 -1
- package/lib/agent-surface.js +1 -1
- package/lib/card-getter.js +1 -1
- package/lib/codegen.js +1 -1
- package/lib/data.js +1 -1
- package/lib/designer.js +21 -1
- package/lib/element-mapper.js +173 -16
- package/lib/element-params.generated.js +11 -0
- package/lib/keyframe-animation.js +1 -1
- package/lib/link-getter.js +1 -1
- package/lib/morph-node.js +1 -1
- package/lib/parse-html.js +151 -0
- package/lib/raster-inspect.js +1 -1
- package/lib/raster-ops.js +1 -1
- package/lib/raster-presets.js +1 -1
- package/lib/scroll-video.js +1 -1
- package/lib/seo.js +1 -1
- package/lib/stacker.js +1 -1
- package/lib/suggest.js +1 -1
- package/lib/theme.js +1 -1
- package/lib/transform-anim.js +1 -1
- package/lib/transition.js +1 -1
- package/lib/validate-nodes.js +112 -2
- package/lib/webmcp-adapter.js +1 -1
- package/package.json +5 -2
- package/schema.json +7953 -0
- package/scripts/generate-schema.mjs +251 -0
- package/skills/nodality/SKILL.md +71 -1
package/lib/element-mapper.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* nodality v1.2.
|
|
2
|
+
* nodality v1.2.11
|
|
3
3
|
* (c) 2026 Filip Vabrousek
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
@@ -83,6 +83,16 @@ const ELEMENT_TYPES = [
|
|
|
83
83
|
"simple", "copy", "wrap", "circle", "polygon", "code", "table", "ulist",
|
|
84
84
|
];
|
|
85
85
|
|
|
86
|
+
// A nav entry is {title, link}. A bare string is accepted as the title: that
|
|
87
|
+
// is the shape callers were already passing while `items` was ignored, and
|
|
88
|
+
// ignoring it was the defect Stage 4 fixes - throwing on it would be a worse
|
|
89
|
+
// one. Missing fields are filled rather than left undefined, because the
|
|
90
|
+
// mapper derives an id from the title and a missing title threw.
|
|
91
|
+
const navEntry = (i) =>
|
|
92
|
+
(typeof i === "string" || typeof i === "number")
|
|
93
|
+
? { title: String(i), link: "#e" }
|
|
94
|
+
: { ...i, title: String(i?.title ?? ""), link: i?.link ?? "#e" };
|
|
95
|
+
|
|
86
96
|
class ElementMapper { // 22:09:58 04/11/2024
|
|
87
97
|
static mapType(obj) {
|
|
88
98
|
// console.log("LOBJ");
|
|
@@ -172,12 +182,19 @@ class ElementMapper { // 22:09:58 04/11/2024
|
|
|
172
182
|
|
|
173
183
|
let el = obj.el;
|
|
174
184
|
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
// Phase S4. A table's rows are DATA, not element specs, so `items` takes
|
|
186
|
+
// row objects and the columns follow from their keys. Without items the
|
|
187
|
+
// three placeholder rows are emitted exactly as before — the same
|
|
188
|
+
// non-breaking rule as cards and row.
|
|
189
|
+
const rows = Array.isArray(el.items) && el.items.length
|
|
190
|
+
? el.items
|
|
191
|
+
: [
|
|
177
192
|
{ abbr: "AUIUI/AK9PT", name: "Pokročilé mobilní technologie", grade: "A", date: "09.01.2024" },
|
|
178
193
|
{ abbr: "AUIUI/AK9PT", name: "Pokročilé mobilní technologie", grade: "A", date: "09.01.2024" },
|
|
179
194
|
{ abbr: "AUIUI/AK9PT", name: "Pokročilé mobilní technologie", grade: "A", date: "09.01.2024" }
|
|
180
|
-
]
|
|
195
|
+
];
|
|
196
|
+
return new Table()
|
|
197
|
+
.add(rows)
|
|
181
198
|
.set({
|
|
182
199
|
cellPadding: "0.3em",
|
|
183
200
|
cellAlign: "center",
|
|
@@ -231,6 +248,13 @@ class ElementMapper { // 22:09:58 04/11/2024
|
|
|
231
248
|
raster: this.filteroRaster(el.id, obj.customOptions),
|
|
232
249
|
}).items([
|
|
233
250
|
|
|
251
|
+
// Phase S4. `children` come from the element, mapped like any other
|
|
252
|
+
// subtree — the fix mapWrap got in P3 and mapGrid in S1. Absent, the
|
|
253
|
+
// three placeholders below are emitted exactly as before, so every
|
|
254
|
+
// existing page is unaffected.
|
|
255
|
+
...(Array.isArray(el.children) && el.children.length
|
|
256
|
+
? el.children.map((child) => this.mapType({ ...obj, el: child }))
|
|
257
|
+
: [
|
|
234
258
|
new Text("This").set({
|
|
235
259
|
size: "S3"
|
|
236
260
|
}),
|
|
@@ -244,7 +268,7 @@ new Text("row.").set({
|
|
|
244
268
|
size: "S3",
|
|
245
269
|
text: "Hello"
|
|
246
270
|
})
|
|
247
|
-
|
|
271
|
+
])
|
|
248
272
|
])
|
|
249
273
|
|
|
250
274
|
|
|
@@ -675,12 +699,29 @@ alert("PP")
|
|
|
675
699
|
// console.log(ft);
|
|
676
700
|
|
|
677
701
|
|
|
678
|
-
|
|
702
|
+
// Phase S4. Nav entries are DATA — {title, link} — so `items`
|
|
703
|
+
// carries them. Absent, the three placeholders are used exactly as
|
|
704
|
+
// before, which is what every existing page renders today.
|
|
705
|
+
let items = Array.isArray(obj.el?.items) && obj.el.items.length
|
|
706
|
+
? obj.el.items.map(navEntry)
|
|
707
|
+
: [
|
|
679
708
|
{title: "Fire", link: "@e"},
|
|
680
709
|
{title: "and", link: "@e"},
|
|
681
710
|
{title: "smoke", link: "@e"}
|
|
682
711
|
];
|
|
683
712
|
|
|
713
|
+
// The `links` built below feed the UINavBar block that is commented
|
|
714
|
+
// out further down, so they are not what a page renders. The live nav
|
|
715
|
+
// is the Switcher: two views, each carrying its own hardcoded link
|
|
716
|
+
// list. `navItems` is what drives those. It is null when the caller
|
|
717
|
+
// supplied nothing, which leaves the literals - and therefore every
|
|
718
|
+
// existing page - exactly as they were.
|
|
719
|
+
const navItems = Array.isArray(obj.el?.items) && obj.el.items.length
|
|
720
|
+
? obj.el.items.map(navEntry)
|
|
721
|
+
: null;
|
|
722
|
+
const navLinks = () => navItems.map((i) =>
|
|
723
|
+
new Link().set({ text: i.title, url: i.link }));
|
|
724
|
+
|
|
684
725
|
let links = items.map((i, o) => new Link(`"${i.title}"`)
|
|
685
726
|
.set({
|
|
686
727
|
fluidc: "S6",
|
|
@@ -844,7 +885,9 @@ if (obj.el.dropdown){
|
|
|
844
885
|
radius: "1rem",
|
|
845
886
|
}).add([
|
|
846
887
|
|
|
847
|
-
|
|
888
|
+
...(navItems
|
|
889
|
+
? navLinks()
|
|
890
|
+
: [new Link().set({text: "About", url: "#a"})]),//.render()
|
|
848
891
|
|
|
849
892
|
|
|
850
893
|
|
|
@@ -901,8 +944,13 @@ if (obj.el.dropdown){
|
|
|
901
944
|
|
|
902
945
|
|
|
903
946
|
|
|
947
|
+
// Supplied entries are emitted above, ahead of the dropdown, so
|
|
948
|
+
// these two trailing placeholders drop out rather than appending
|
|
949
|
+
// themselves to the end of the caller's nav.
|
|
950
|
+
...(navItems ? [] : [
|
|
904
951
|
new Link("B").set({text: "About", url: "#b"}),//.render()
|
|
905
952
|
new Link("C").set({text: "Contact", url: "https://www.abcnews.com"})//.render()
|
|
953
|
+
])
|
|
906
954
|
])//.addNavItem(new Link("C").set({}).render())
|
|
907
955
|
},
|
|
908
956
|
|
|
@@ -1013,9 +1061,11 @@ new Wrapper().set({
|
|
|
1013
1061
|
|
|
1014
1062
|
|
|
1015
1063
|
|
|
1064
|
+
...(navItems ? navLinks() : [
|
|
1016
1065
|
new Link().set({ text: "About", url: "#a"}),//.render()
|
|
1017
1066
|
new Link().set({ text: "Services", url: "#a"}),//.render()
|
|
1018
1067
|
new Link().set({ text: "Contact", url: "#a"})//.render()
|
|
1068
|
+
]),
|
|
1019
1069
|
// new Link("C").set({})//.render()
|
|
1020
1070
|
])
|
|
1021
1071
|
/*.addNavItem(
|
|
@@ -1062,7 +1112,11 @@ new Wrapper().set({
|
|
|
1062
1112
|
|
|
1063
1113
|
|
|
1064
1114
|
|
|
1065
|
-
|
|
1115
|
+
// Phase S4, as protoNav above: `items` carries {title, link}; absent,
|
|
1116
|
+
// the placeholders are used exactly as before.
|
|
1117
|
+
let items = Array.isArray(obj.el?.items) && obj.el.items.length
|
|
1118
|
+
? obj.el.items.map(navEntry)
|
|
1119
|
+
: [
|
|
1066
1120
|
{title: "Home", link: "@e"},
|
|
1067
1121
|
{title: "Projects", link: "@e"},
|
|
1068
1122
|
{title: "Services", link: "@e"}
|
|
@@ -1246,10 +1300,17 @@ if (obj.el.dropdown){
|
|
|
1246
1300
|
// pad: {sides: ["all"], value: "1rem"}
|
|
1247
1301
|
}),
|
|
1248
1302
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
(
|
|
1303
|
+
// Phase S4. These were three fixed slots, which assumed exactly
|
|
1304
|
+
// three nav entries. Once `items` could supply any number,
|
|
1305
|
+
// links[1] and links[2] were `undefined` and Wrapper.add called
|
|
1306
|
+
// toCode() on them — a throw, not a silent no-op. One slot per
|
|
1307
|
+
// item instead; with the default three the emitted list is
|
|
1308
|
+
// unchanged, and the dropdown slot after it still indexes the
|
|
1309
|
+
// entry the pushes above appended.
|
|
1310
|
+
...(obj.el.animation ? animatedLinks : links).slice(0, items.length).filter(Boolean),
|
|
1311
|
+
(obj.el.animation && obj.el.dropdown)
|
|
1312
|
+
? animatedLinks[items.length]
|
|
1313
|
+
: new Text("").set({}).excludeFromCode(),
|
|
1253
1314
|
|
|
1254
1315
|
new Text("Company, 2025")
|
|
1255
1316
|
.set({ // no ID, no animation
|
|
@@ -1690,15 +1751,111 @@ const blast = this.filtero("blast", el.id, obj.customOptions);
|
|
|
1690
1751
|
}
|
|
1691
1752
|
})
|
|
1692
1753
|
.items(
|
|
1693
|
-
|
|
1754
|
+
${this.gridItemsSource(obj, cardTemplate)}
|
|
1755
|
+
)
|
|
1756
|
+
`;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* The `.items(...)` argument for a `cards` grid.
|
|
1761
|
+
*
|
|
1762
|
+
* Phase S1. Until now this was a hardcoded array of three space cards
|
|
1763
|
+
* inside a template literal, so `{type:"cards"}` rendered the same
|
|
1764
|
+
* Starship/Saturn V/Shuttle grid whatever the caller declared — an
|
|
1765
|
+
* element type with no way to carry content. `items` supplies it.
|
|
1766
|
+
*
|
|
1767
|
+
* Three shapes, chosen per entry so a mixed array works:
|
|
1768
|
+
*
|
|
1769
|
+
* absent the three placeholder cards, EXACTLY as before.
|
|
1770
|
+
* This path emits a byte-identical string to the
|
|
1771
|
+
* pre-S1 mapper and is covered by a regression test;
|
|
1772
|
+
* the change ships behind no flag because nothing
|
|
1773
|
+
* that works today can observe it.
|
|
1774
|
+
*
|
|
1775
|
+
* {img,title,link} shorthand. The same card template as before, over
|
|
1776
|
+
* the caller's data instead of the placeholders.
|
|
1777
|
+
* Kept because it is what the published benchmark
|
|
1778
|
+
* and Table 3 of the PeerJ submission already write.
|
|
1779
|
+
*
|
|
1780
|
+
* [ {type:…}, … ] nested specs: a card whose children are ordinary
|
|
1781
|
+
* element descriptors, mapped through `mapType` like
|
|
1782
|
+
* any other subtree and emitted with `toCode()`.
|
|
1783
|
+
* This is the shape that makes the format TOTAL —
|
|
1784
|
+
* any element can appear inside a card without a new
|
|
1785
|
+
* schema per composite.
|
|
1786
|
+
*
|
|
1787
|
+
* Data in, code out: every branch still emits `new Card().set({...})`
|
|
1788
|
+
* source the developer owns. The template became parameterised; it did
|
|
1789
|
+
* not become an interpreter.
|
|
1790
|
+
*/
|
|
1791
|
+
static gridItemsSource(obj, cardTemplate) {
|
|
1792
|
+
const items = obj.el.items;
|
|
1793
|
+
|
|
1794
|
+
// No items: the pre-S1 string, character for character.
|
|
1795
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
1796
|
+
return `[
|
|
1694
1797
|
{ img: "https://upload.wikimedia.org/wikipedia/commons/3/3a/Starship_S20.jpg", title: "Starship", link: "#ship" },
|
|
1695
1798
|
{ img: "https://upload.wikimedia.org/wikipedia/commons/1/16/Apollo_11_Launch_-_GPN-2000-000630.jpg", title: "Saturn V", link: "#saturn" },
|
|
1696
1799
|
{ img: "https://upload.wikimedia.org/wikipedia/commons/d/d6/STS120LaunchHiRes-edit1.jpg", title: "Shuttle", link: "#shuttle" }
|
|
1697
1800
|
].map(item =>
|
|
1698
1801
|
${cardTemplate}
|
|
1699
|
-
)
|
|
1700
|
-
|
|
1701
|
-
|
|
1802
|
+
)`;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
const isSpecList = (it) => Array.isArray(it);
|
|
1806
|
+
const shorthand = items.filter((it) => !isSpecList(it));
|
|
1807
|
+
const specLists = items.filter(isSpecList);
|
|
1808
|
+
|
|
1809
|
+
// Every entry is shorthand: keep the `.map()` form, so the emitted
|
|
1810
|
+
// code stays as compact as the hand-written equivalent and the
|
|
1811
|
+
// template is still paid once.
|
|
1812
|
+
if (specLists.length === 0) {
|
|
1813
|
+
return `${JSON.stringify(shorthand, null, 2).split("\n").map((l, i) => i === 0 ? l : " " + l).join("\n")}.map(item =>
|
|
1814
|
+
${cardTemplate}
|
|
1815
|
+
)`;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// At least one entry is a spec list, so the cards differ and a single
|
|
1819
|
+
// template cannot serve them. Emit each card explicitly.
|
|
1820
|
+
const cards = items.map((it) => isSpecList(it)
|
|
1821
|
+
? this.cardFromSpecs(obj, it)
|
|
1822
|
+
: this.cardFromShorthand(it));
|
|
1823
|
+
return `[
|
|
1824
|
+
${cards.join(",\n ")}
|
|
1825
|
+
]`;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/** A card whose children are element specs, mapped like any other subtree. */
|
|
1829
|
+
static cardFromSpecs(obj, specs) {
|
|
1830
|
+
const kids = specs.map((child) => {
|
|
1831
|
+
const mapped = this.mapType({ ...obj, el: child });
|
|
1832
|
+
// mapType returns an instance for most types and SOURCE for the
|
|
1833
|
+
// few that emit source themselves (grid, copy, wrap-as-source).
|
|
1834
|
+
// A string is already code; an instance knows how to become code.
|
|
1835
|
+
if (typeof mapped === "string") return mapped.trim();
|
|
1836
|
+
const code = mapped && typeof mapped.toCode === "function"
|
|
1837
|
+
? [].concat(mapped.toCode()).join("")
|
|
1838
|
+
: "";
|
|
1839
|
+
return code.trim();
|
|
1840
|
+
}).filter(Boolean);
|
|
1841
|
+
|
|
1842
|
+
return `new Card()
|
|
1843
|
+
.set({ width: "300px", height: "700px", radius: "0.7rem", mar: { sides: ["all"], value: "0.8rem" } })
|
|
1844
|
+
.items([
|
|
1845
|
+
${kids.join(",\n ")}
|
|
1846
|
+
])`;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
/** A card built from {img, title, link} shorthand, matching the template. */
|
|
1850
|
+
static cardFromShorthand(item) {
|
|
1851
|
+
const s = (v) => JSON.stringify(v ?? "");
|
|
1852
|
+
return `new Card()
|
|
1853
|
+
.set({ width: "300px", height: "700px", radius: "0.7rem", mar: { sides: ["all"], value: "0.8rem" } })
|
|
1854
|
+
.items([
|
|
1855
|
+
new Image(${s(item.img)}).set({isFull: true, url: ${s(item.img)}}),
|
|
1856
|
+
new Text(${s(item.title)}).set({ size: "S5", color: "#f97316" }),
|
|
1857
|
+
new Link("Link").set({ text: ${s(item.title)}, url: ${s(item.link)}, background: "#3498db", pad: [{ lr: "0.5rem", tb: "1rem" }], radius: "0.4rem", color: "white" })
|
|
1858
|
+
])`;
|
|
1702
1859
|
}
|
|
1703
1860
|
|
|
1704
1861
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* nodality v1.2.11
|
|
3
|
+
* (c) 2026 Filip Vabrousek
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// GENERATED by scripts/generate-schema.mjs — do not edit.
|
|
8
|
+
// Every parameter name any element type reads, recovered from source.
|
|
9
|
+
// Used for near-miss typo detection in validate-nodes.js. A drift test
|
|
10
|
+
// regenerates this and fails if it differs, so it cannot rot silently.
|
|
11
|
+
export const ELEMENT_PARAM_NAMES = ["absolute","accept","action","align","alignIts","animate","animation","area","arrayMargin","arrayPadding","at","auto","backTo","background","backgroundCard","backgroundColor","backgroundOp","behaviour","blast","blastData","block","bold","border","borderObj","borderObja","borderRadius","brand","breakWord","breakpoints","cellAlign","cellPadding","center","centerColumn","centered","checkedBackgroundColor","children","clampc","class","clipPath","code","color","column","contentWidth","cursor","customAlign","customJustify","customStyle","data","diameter","disp","dropdown","em","exact","filter","filtera","fixMobileTap","fixed","flex","flexCenter","flexDir","fluid","fluidc","font","frame","ga","gap","gpos","gradient","hamColour","hamburgerColour","headStyle","height","hideField","hover","icon","id","img","index","initLetter","inline","inpad","inputType","isFull","isHidden","isNavA","italic","items","keySet","kind","label","layout","left","length","link","map","mar","margin","marginOp","maxHeight","maxWidth","mboth","method","mobile","multiple","name","new","nowrap","offcanvas","okayColor","onScroll","onTap","opacity","overflow","pad","padding","placeholder","position","positions","preffersId","radius","ranges","raster","react","removeDecoration","required","resmar","respad","responsive","resprop","rounded","scale","sequence","shadow","sides","simpleBorder","simpleCenter","size","span","sticky","stretch","stroke","style","svg","tags","templateCols","text","theme","tint","title","top","transform","transition","type","url","useBrightness","value","vtn","weight","width","x","y","zIndex"];
|
package/lib/link-getter.js
CHANGED
package/lib/morph-node.js
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* nodality v1.2.11
|
|
3
|
+
* (c) 2026 Filip Vabrousek
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stage 5 of AGENTIC-FIRST-PLAN.md — the round-trip.
|
|
9
|
+
*
|
|
10
|
+
* parse(html) -> spec such that render(parse(html)) === html
|
|
11
|
+
*
|
|
12
|
+
* The plan expected this to be partial at first, and measurement decided
|
|
13
|
+
* exactly where the partial line falls.
|
|
14
|
+
*
|
|
15
|
+
* WHAT THE MEASUREMENT SAID. Rendering all 35 declared types in isolation and
|
|
16
|
+
* comparing their DOM signatures: 13 of them — `cards`, `nav`, `sideNav`,
|
|
17
|
+
* `row`, `dropdown`, `radio`, `labelInput`, `filePicker`, `checkbox`, `wrap`,
|
|
18
|
+
* `circle`, `polygon`, `table` — produce a bare `<div>` with no class and no
|
|
19
|
+
* attributes. Nothing in the output says which one produced it. Looking two
|
|
20
|
+
* levels deeper separates 24 of 30, but only for the placeholder renders it
|
|
21
|
+
* was calibrated on: a `row` holding an `h2` and a `wrap` holding an `h2` are
|
|
22
|
+
* the same shape, so a signature table built from empty composites does not
|
|
23
|
+
* match a page with content in it. Structural recognition of a composite is
|
|
24
|
+
* therefore guesswork, and a parser that guesses wrong hands an agent a spec
|
|
25
|
+
* that renders a different page.
|
|
26
|
+
*
|
|
27
|
+
* SO THERE ARE TWO TIERS, and they are not presented as one.
|
|
28
|
+
*
|
|
29
|
+
* exact The page carries its own spec: `set({annotate: true})` writes
|
|
30
|
+
* each element's descriptor onto the node it produced. parse
|
|
31
|
+
* reads it back. Round-trip is exact for every type, including
|
|
32
|
+
* every style option, because nothing is being inferred.
|
|
33
|
+
*
|
|
34
|
+
* structural No annotation: recover what the tag alone determines —
|
|
35
|
+
* headings, paragraphs, links, images, lists. These are the
|
|
36
|
+
* leaf types, which is the case the paper measures, and their
|
|
37
|
+
* tag is not shared with anything ambiguous.
|
|
38
|
+
*
|
|
39
|
+
* Annotation is OPT-IN and off by default. It writes attributes into the
|
|
40
|
+
* output, and the whole project rests on existing pages rendering byte for
|
|
41
|
+
* byte as they always did.
|
|
42
|
+
*
|
|
43
|
+
* The 1:1 correspondence between descriptors and mounted children that
|
|
44
|
+
* `annotate` relies on is not a new assumption: morph nodes already resolve
|
|
45
|
+
* `from`/`to` against the rendered children by position, for the same reason
|
|
46
|
+
* (components do not all carry their id into the DOM). It was re-measured for
|
|
47
|
+
* this stage across mixed leaf-and-composite specs.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import { validateNodes } from "./validate-nodes.js";
|
|
51
|
+
|
|
52
|
+
const HEADINGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
53
|
+
|
|
54
|
+
/** The attribute a page uses to carry its own descriptors. */
|
|
55
|
+
export const SPEC_ATTR = "data-nod";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Write each descriptor onto the child it produced. Returns how many were
|
|
59
|
+
* written, which is what the caller needs to know if the two ever diverge.
|
|
60
|
+
*/
|
|
61
|
+
export function annotateRoundTrip(host, elements) {
|
|
62
|
+
if (!host || !Array.isArray(elements)) return 0;
|
|
63
|
+
const kids = Array.from(host.children || []);
|
|
64
|
+
let written = 0;
|
|
65
|
+
for (let i = 0; i < kids.length && i < elements.length; i++) {
|
|
66
|
+
try {
|
|
67
|
+
kids[i].setAttribute(SPEC_ATTR, JSON.stringify(elements[i]));
|
|
68
|
+
written++;
|
|
69
|
+
} catch (e) { /* a node that refuses attributes is skipped, not fatal */ }
|
|
70
|
+
}
|
|
71
|
+
return written;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Recover what the tag alone determines. Deliberately narrow: returning a
|
|
76
|
+
* confident guess for a composite would be worse than returning nothing,
|
|
77
|
+
* because the caller re-renders whatever comes back.
|
|
78
|
+
*/
|
|
79
|
+
export function inferNode(node) {
|
|
80
|
+
const tag = (node.tagName || "").toLowerCase();
|
|
81
|
+
if (HEADINGS.has(tag)) return { type: tag, text: node.textContent };
|
|
82
|
+
if (tag === "p") return { type: "p", text: node.textContent };
|
|
83
|
+
if (tag === "a") {
|
|
84
|
+
return { type: "a", text: node.textContent, url: node.getAttribute("href") || "" };
|
|
85
|
+
}
|
|
86
|
+
if (tag === "img") return { type: "img", url: node.getAttribute("src") || "" };
|
|
87
|
+
if (tag === "ul") {
|
|
88
|
+
return { type: "ulist", items: Array.from(node.children).map((c) => c.textContent) };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** One node to one descriptor: the carried spec if there is one, else inference. */
|
|
94
|
+
export function parseNode(node) {
|
|
95
|
+
if (!node || node.nodeType !== 1) return null;
|
|
96
|
+
const carried = node.getAttribute && node.getAttribute(SPEC_ATTR);
|
|
97
|
+
if (carried) {
|
|
98
|
+
try { return JSON.parse(carried); }
|
|
99
|
+
catch (e) { /* a corrupted attribute falls through to inference */ }
|
|
100
|
+
}
|
|
101
|
+
return inferNode(node);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* parse(html) -> spec. `doc` is only needed off-browser, where the caller
|
|
106
|
+
* supplies a document; the library's own globals are shadowed by the bridge,
|
|
107
|
+
* so this builds its container through createElement rather than a
|
|
108
|
+
* constructor.
|
|
109
|
+
*/
|
|
110
|
+
export function parseHTML(html, doc) {
|
|
111
|
+
const d = doc || (typeof document !== "undefined" ? document : null);
|
|
112
|
+
if (!d) throw new Error("[nodality] parseHTML needs a document");
|
|
113
|
+
const box = d.createElement("div");
|
|
114
|
+
box.innerHTML = String(html == null ? "" : html);
|
|
115
|
+
return Array.from(box.children).map(parseNode).filter(Boolean);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* What parse could NOT recover, so a caller can tell a partial read from a
|
|
120
|
+
* complete one instead of discovering it at render time.
|
|
121
|
+
*/
|
|
122
|
+
export function parseReport(html, doc) {
|
|
123
|
+
const d = doc || (typeof document !== "undefined" ? document : null);
|
|
124
|
+
if (!d) throw new Error("[nodality] parseReport needs a document");
|
|
125
|
+
const box = d.createElement("div");
|
|
126
|
+
box.innerHTML = String(html == null ? "" : html);
|
|
127
|
+
const kids = Array.from(box.children);
|
|
128
|
+
const spec = [], unrecovered = [];
|
|
129
|
+
kids.forEach((node, i) => {
|
|
130
|
+
const one = parseNode(node);
|
|
131
|
+
if (one) spec.push(one);
|
|
132
|
+
else unrecovered.push({ index: i, tag: (node.tagName || "").toLowerCase() });
|
|
133
|
+
});
|
|
134
|
+
// What parse returns is UNTRUSTED. A `data-nod` attribute is just text in
|
|
135
|
+
// a document: hand-edited, served by someone else, or written by a version
|
|
136
|
+
// of the library that is not this one. The caller's next move is to render
|
|
137
|
+
// it, so the recovered spec is checked here rather than after it has
|
|
138
|
+
// already become a page. This is also the first place Stage 3 and Stage 5
|
|
139
|
+
// compose — the validator that makes a spec repairable is the one that
|
|
140
|
+
// makes a parsed spec safe.
|
|
141
|
+
const check = validateNodes([], spec);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
ok: unrecovered.length === 0 && check.ok,
|
|
145
|
+
exact: kids.every((n) => n.getAttribute && n.getAttribute(SPEC_ATTR)),
|
|
146
|
+
total: kids.length,
|
|
147
|
+
spec,
|
|
148
|
+
unrecovered,
|
|
149
|
+
errors: check.errors,
|
|
150
|
+
};
|
|
151
|
+
}
|
package/lib/raster-inspect.js
CHANGED
package/lib/raster-ops.js
CHANGED
package/lib/raster-presets.js
CHANGED
package/lib/scroll-video.js
CHANGED
package/lib/seo.js
CHANGED
package/lib/stacker.js
CHANGED
package/lib/suggest.js
CHANGED
package/lib/theme.js
CHANGED
package/lib/transform-anim.js
CHANGED
package/lib/transition.js
CHANGED