meno-core 1.0.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/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- package/vite.config.ts +43 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree path utilities for parsing and navigating tree structure paths
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ComponentNode, PageData, JSONPage } from './types';
|
|
6
|
+
import type { PropDefinition } from './types';
|
|
7
|
+
import type { Path } from './pathArrayUtils';
|
|
8
|
+
import { stringToPath } from './pathArrayUtils';
|
|
9
|
+
|
|
10
|
+
// Type for page data that can have component structure
|
|
11
|
+
type PageDataWithComponent = PageData & {
|
|
12
|
+
component?: {
|
|
13
|
+
structure?: ComponentNode;
|
|
14
|
+
interface?: Record<string, PropDefinition>;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export interface NodeLocation {
|
|
19
|
+
parent: ComponentNode | PageData | null;
|
|
20
|
+
index: number;
|
|
21
|
+
node: ComponentNode | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate that component data has a valid structure
|
|
26
|
+
* @param pageData - PageData to validate
|
|
27
|
+
* @returns true if component has valid structure, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
export function validateComponentStructure(pageData: PageData | null): boolean {
|
|
30
|
+
if (!pageData) return false;
|
|
31
|
+
|
|
32
|
+
// Check for page format (has root property)
|
|
33
|
+
if ('root' in pageData && pageData.root) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check for component format (has component.structure property)
|
|
38
|
+
if ('component' in pageData && pageData.component) {
|
|
39
|
+
if ('structure' in pageData.component && pageData.component.structure) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
// Component exists but structure is missing
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the root data from pageData (handles both page and component structures
|
|
51
|
+
* Logs warnings when structure is missing for better debugging
|
|
52
|
+
*/
|
|
53
|
+
export function getRootData(pageData: PageData | null): ComponentNode | null {
|
|
54
|
+
if (!pageData) return null;
|
|
55
|
+
|
|
56
|
+
// Check for page format (has root property)
|
|
57
|
+
if ('root' in pageData && pageData.root) {
|
|
58
|
+
return pageData.root;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for component format (has component.structure property)
|
|
62
|
+
if ('component' in pageData && pageData.component) {
|
|
63
|
+
if ('structure' in pageData.component) {
|
|
64
|
+
const structure = pageData.component.structure;
|
|
65
|
+
if (structure) {
|
|
66
|
+
return structure;
|
|
67
|
+
}
|
|
68
|
+
// Structure field exists but is null/undefined
|
|
69
|
+
} else {
|
|
70
|
+
// Component exists but structure field is missing
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse a tree path and return the parent, index, and node
|
|
79
|
+
* Handles paths like: root_0, root_0_children_0, root_0_children_0_children_1
|
|
80
|
+
* Also accepts array paths: [0], [0,0], [0,0,1]
|
|
81
|
+
*/
|
|
82
|
+
export function getParentAndIndexFromPath(
|
|
83
|
+
path: Path | string,
|
|
84
|
+
pageData: PageData
|
|
85
|
+
): NodeLocation | null {
|
|
86
|
+
const rootData = getRootData(pageData);
|
|
87
|
+
if (!rootData) return null;
|
|
88
|
+
|
|
89
|
+
// Convert string to array if needed
|
|
90
|
+
const pathArray = typeof path === 'string' ? stringToPath(path) : path;
|
|
91
|
+
|
|
92
|
+
// Handle root [0] specially
|
|
93
|
+
if (pathArray.length === 1 && pathArray[0] === 0) {
|
|
94
|
+
return {
|
|
95
|
+
parent: pageData,
|
|
96
|
+
index: 0,
|
|
97
|
+
node: rootData
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Navigate through path array
|
|
102
|
+
let current: ComponentNode = rootData;
|
|
103
|
+
let parent: ComponentNode | PageData | null = null;
|
|
104
|
+
let lastIndex = -1;
|
|
105
|
+
|
|
106
|
+
// Skip first element (root index 0), navigate through rest
|
|
107
|
+
for (let i = 1; i < pathArray.length; i++) {
|
|
108
|
+
const childIndex = pathArray[i];
|
|
109
|
+
|
|
110
|
+
if (!current || !('children' in current) || !Array.isArray(current.children)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isNaN(childIndex) || childIndex < 0 || childIndex >= current.children.length) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
parent = current;
|
|
119
|
+
const child = current.children[childIndex];
|
|
120
|
+
if (typeof child === 'string') {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
current = child;
|
|
124
|
+
lastIndex = childIndex;
|
|
125
|
+
|
|
126
|
+
// If this is the last navigation step, we found our node
|
|
127
|
+
if (i === pathArray.length - 1) {
|
|
128
|
+
return { parent, index: childIndex, node: current };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Return the last valid location if we got here
|
|
133
|
+
if (parent && lastIndex >= 0) {
|
|
134
|
+
return { parent, index: lastIndex, node: current };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Extract structure without styles/props for tree comparison
|
|
142
|
+
* This allows tree to only regenerate when structure changes, not styles
|
|
143
|
+
*/
|
|
144
|
+
export function extractStructureWithoutStyles(node: ComponentNode | string | null | undefined): ComponentNode | string | null | undefined {
|
|
145
|
+
if (!node || typeof node === 'string') {
|
|
146
|
+
return node;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof node !== 'object') {
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create a new object with only structure-relevant properties
|
|
154
|
+
const { type } = node;
|
|
155
|
+
const tag = 'tag' in node ? node.tag : undefined;
|
|
156
|
+
const children = 'children' in node ? node.children : undefined;
|
|
157
|
+
|
|
158
|
+
// Recursively process children
|
|
159
|
+
const processedChildren = children
|
|
160
|
+
? Array.isArray(children)
|
|
161
|
+
? children.map((child: ComponentNode | string) =>
|
|
162
|
+
typeof child === 'string' ? child : extractStructureWithoutStyles(child) as ComponentNode
|
|
163
|
+
)
|
|
164
|
+
: typeof children === 'string'
|
|
165
|
+
? children
|
|
166
|
+
: extractStructureWithoutStyles(children) as ComponentNode
|
|
167
|
+
: undefined;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
type,
|
|
171
|
+
...(tag !== undefined ? { tag } : {}),
|
|
172
|
+
...(processedChildren !== undefined ? { children: processedChildren } : {})
|
|
173
|
+
} as ComponentNode;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Calculate insertion index based on drop target information
|
|
178
|
+
*/
|
|
179
|
+
export function calculateInsertionIndex(
|
|
180
|
+
target: {
|
|
181
|
+
childIndex?: number;
|
|
182
|
+
childAfterItem?: { index: string };
|
|
183
|
+
childBeforeItem?: { index: string };
|
|
184
|
+
linearIndex?: number;
|
|
185
|
+
},
|
|
186
|
+
parentChildren: Array<ComponentNode | string>,
|
|
187
|
+
nodeDataMap: Map<string, ComponentNode | string>
|
|
188
|
+
): number {
|
|
189
|
+
if (target.childIndex !== undefined && target.childIndex >= 0) {
|
|
190
|
+
// Most reliable: use childIndex directly
|
|
191
|
+
return Math.min(Math.max(0, target.childIndex), parentChildren.length);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (target.childAfterItem?.index) {
|
|
195
|
+
const afterPath = target.childAfterItem.index;
|
|
196
|
+
const afterNode = nodeDataMap.get(afterPath);
|
|
197
|
+
if (afterNode) {
|
|
198
|
+
const afterIndex = parentChildren.findIndex((child: ComponentNode | string) => child === afterNode);
|
|
199
|
+
if (afterIndex >= 0) {
|
|
200
|
+
return afterIndex + 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Fallback: append
|
|
204
|
+
return parentChildren.length;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (target.childBeforeItem?.index) {
|
|
208
|
+
const beforePath = target.childBeforeItem.index;
|
|
209
|
+
const beforeNode = nodeDataMap.get(beforePath);
|
|
210
|
+
if (beforeNode) {
|
|
211
|
+
const beforeIndex = parentChildren.findIndex((child: ComponentNode | string) => child === beforeNode);
|
|
212
|
+
if (beforeIndex >= 0) {
|
|
213
|
+
return beforeIndex;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Fallback: prepend
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Last resort: use linearIndex or append
|
|
221
|
+
if (target.linearIndex !== undefined) {
|
|
222
|
+
return Math.min(Math.max(0, target.linearIndex), parentChildren.length);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return parentChildren.length;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Type guard to check if a value is a valid NodeLocation
|
|
230
|
+
*/
|
|
231
|
+
export function isValidNodeLocation(location: unknown): location is NodeLocation {
|
|
232
|
+
if (!location || typeof location !== 'object') {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Use proper type narrowing instead of assertion
|
|
237
|
+
const loc = location as Record<string, unknown>;
|
|
238
|
+
const hasParent = 'parent' in loc && (loc.parent === null || typeof loc.parent === 'object');
|
|
239
|
+
const hasIndex = 'index' in loc && typeof loc.index === 'number' && loc.index >= 0;
|
|
240
|
+
const hasNode = 'node' in loc && (loc.node === null || typeof loc.node === 'object');
|
|
241
|
+
|
|
242
|
+
return hasParent && hasIndex && hasNode;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Type guard to check if a value is a valid Path
|
|
247
|
+
*/
|
|
248
|
+
export function isValidPath(path: unknown): path is Path {
|
|
249
|
+
if (!Array.isArray(path)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Path is an array of numbers
|
|
254
|
+
return path.every((item): item is number => typeof item === 'number' && item >= 0);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Type guard to check if parent is PageData
|
|
259
|
+
*/
|
|
260
|
+
export function isPageDataParent(
|
|
261
|
+
parent: ComponentNode | PageData | null
|
|
262
|
+
): parent is PageData {
|
|
263
|
+
if (!parent) return false;
|
|
264
|
+
|
|
265
|
+
// Check for JSONPage format (has root property)
|
|
266
|
+
if ('root' in parent) return true;
|
|
267
|
+
|
|
268
|
+
// Check for component format - component must be an object, not a string
|
|
269
|
+
if ('component' in parent) {
|
|
270
|
+
return typeof parent.component === 'object' && parent.component !== null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Type guard to check if parent is ComponentNode
|
|
278
|
+
*/
|
|
279
|
+
export function isComponentNodeParent(
|
|
280
|
+
parent: ComponentNode | PageData | null
|
|
281
|
+
): parent is ComponentNode {
|
|
282
|
+
if (!parent) return false;
|
|
283
|
+
// Check for 'type' property (all ComponentNodes have it)
|
|
284
|
+
// Then check for specific node types that can have children:
|
|
285
|
+
// - HtmlNode: has 'tag' property
|
|
286
|
+
// - ComponentInstanceNode: has 'component' property
|
|
287
|
+
// - SlotMarker: type === 'slot'
|
|
288
|
+
// - ObjectLinkNode: type === 'object-link'
|
|
289
|
+
// - EmbedNode: type === 'embed'
|
|
290
|
+
return 'type' in parent && (
|
|
291
|
+
'tag' in parent ||
|
|
292
|
+
'component' in parent ||
|
|
293
|
+
parent.type === 'slot' ||
|
|
294
|
+
parent.type === 'object-link' ||
|
|
295
|
+
parent.type === 'embed'
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get children array from parent (handles both PageData and ComponentNode)
|
|
301
|
+
*/
|
|
302
|
+
export function getParentChildren(
|
|
303
|
+
parent: ComponentNode | PageData | null
|
|
304
|
+
): Array<ComponentNode | string> | null {
|
|
305
|
+
if (!parent) return null;
|
|
306
|
+
|
|
307
|
+
if (isPageDataParent(parent)) {
|
|
308
|
+
const rootData = getRootData(parent);
|
|
309
|
+
return rootData && 'children' in rootData ? rootData.children || null : null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (isComponentNodeParent(parent)) {
|
|
313
|
+
return 'children' in parent ? parent.children || null : null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Set children array on parent (handles both PageData and ComponentNode)
|
|
321
|
+
*/
|
|
322
|
+
export function setParentChildren(
|
|
323
|
+
parent: ComponentNode | PageData | null,
|
|
324
|
+
children: Array<ComponentNode | string>
|
|
325
|
+
): void {
|
|
326
|
+
if (!parent) return;
|
|
327
|
+
|
|
328
|
+
if (isPageDataParent(parent)) {
|
|
329
|
+
const rootData = getRootData(parent);
|
|
330
|
+
if (rootData && 'children' in rootData) {
|
|
331
|
+
rootData.children = children;
|
|
332
|
+
}
|
|
333
|
+
} else if (isComponentNodeParent(parent)) {
|
|
334
|
+
if ('children' in parent) {
|
|
335
|
+
parent.children = children;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { I18nValue } from './components';
|
|
6
|
+
import type { CMSFieldDefinition } from './cms';
|
|
7
|
+
|
|
8
|
+
export interface HMRMessage {
|
|
9
|
+
type: 'hmr:update' | 'hmr:colors-update';
|
|
10
|
+
path?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PageListResponse {
|
|
14
|
+
pages: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RouteHandler {
|
|
18
|
+
pattern: string | RegExp;
|
|
19
|
+
handler: (req: Request, url: URL) => Response | Promise<Response> | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Value that can be either a plain string or an i18n object */
|
|
23
|
+
type StringOrI18n = string | I18nValue;
|
|
24
|
+
|
|
25
|
+
/** CMS configuration embedded in page meta (includes full schema) */
|
|
26
|
+
export interface PageCmsConfig {
|
|
27
|
+
/** Unique identifier for the collection */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Display name for the collection */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Which field to use for URL slug */
|
|
32
|
+
slugField: string;
|
|
33
|
+
/** URL pattern, e.g., "/blog/{{slug}}" */
|
|
34
|
+
urlPattern: string;
|
|
35
|
+
/** Field definitions */
|
|
36
|
+
fields: Record<string, CMSFieldDefinition>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Page metadata structure
|
|
41
|
+
* Text fields can be either plain strings or i18n objects
|
|
42
|
+
*/
|
|
43
|
+
export interface PageMetaData {
|
|
44
|
+
title?: StringOrI18n;
|
|
45
|
+
description?: StringOrI18n;
|
|
46
|
+
keywords?: StringOrI18n;
|
|
47
|
+
ogTitle?: StringOrI18n;
|
|
48
|
+
ogDescription?: StringOrI18n;
|
|
49
|
+
ogImage?: string;
|
|
50
|
+
ogType?: string;
|
|
51
|
+
/** Translated slugs for each locale, e.g., { "en": "about", "pl": "o-nas" } */
|
|
52
|
+
slugs?: Record<string, string>;
|
|
53
|
+
/** Data source: 'static' (default) or 'cms' */
|
|
54
|
+
source?: 'static' | 'cms';
|
|
55
|
+
/** CMS configuration with embedded schema (required when source: 'cms') */
|
|
56
|
+
cms?: PageCmsConfig;
|
|
57
|
+
}
|
|
58
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CMS Types
|
|
3
|
+
* Defines schema structure and content items for CMS collections
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Field types supported by CMS */
|
|
7
|
+
export type CMSFieldType =
|
|
8
|
+
| 'string' // Single-line text
|
|
9
|
+
| 'text' // Multi-line text / rich text
|
|
10
|
+
| 'number' // Numeric value
|
|
11
|
+
| 'boolean' // True/false
|
|
12
|
+
| 'image' // Image URL/path
|
|
13
|
+
| 'date' // ISO date string
|
|
14
|
+
| 'select' // Single selection from options
|
|
15
|
+
| 'reference' // Reference to another collection item
|
|
16
|
+
| 'i18n' // Internationalized text (single-line)
|
|
17
|
+
| 'i18n-text'; // Internationalized text (multi-line)
|
|
18
|
+
|
|
19
|
+
/** Single field definition in a schema */
|
|
20
|
+
export interface CMSFieldDefinition {
|
|
21
|
+
type: CMSFieldType;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
default?: unknown;
|
|
24
|
+
label?: string; // Display label in editor
|
|
25
|
+
description?: string; // Help text
|
|
26
|
+
options?: string[]; // For 'select' type
|
|
27
|
+
collection?: string; // For 'reference' type
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** CMS Schema - defines collection structure (embedded in page meta.cms) */
|
|
31
|
+
export interface CMSSchema {
|
|
32
|
+
/** Unique identifier for the collection */
|
|
33
|
+
id: string;
|
|
34
|
+
/** Display name for the collection */
|
|
35
|
+
name: string;
|
|
36
|
+
/** Which field to use for URL slug */
|
|
37
|
+
slugField: string;
|
|
38
|
+
/** URL pattern, e.g., "/blog/{{slug}}" */
|
|
39
|
+
urlPattern: string;
|
|
40
|
+
/** Field definitions */
|
|
41
|
+
fields: Record<string, CMSFieldDefinition>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** CMS Item - actual content entry (stored as individual JSON file) */
|
|
45
|
+
export interface CMSItem {
|
|
46
|
+
_id: string;
|
|
47
|
+
_slug?: string; // Derived from filename (e.g., hello-world.json -> "hello-world")
|
|
48
|
+
_createdAt?: string; // ISO timestamp
|
|
49
|
+
_updatedAt?: string; // ISO timestamp
|
|
50
|
+
[field: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** CMS Collection - schema + items */
|
|
54
|
+
export interface CMSCollection {
|
|
55
|
+
schema: CMSSchema;
|
|
56
|
+
items: CMSItem[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Resolved CMS route match */
|
|
60
|
+
export interface CMSRouteMatch {
|
|
61
|
+
collection: string;
|
|
62
|
+
slug: string;
|
|
63
|
+
item: CMSItem;
|
|
64
|
+
pagePath: string; // Path to the template page file
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Filter operators for CMS queries */
|
|
68
|
+
export type CMSFilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'in';
|
|
69
|
+
|
|
70
|
+
/** Single filter condition */
|
|
71
|
+
export interface CMSFilterCondition {
|
|
72
|
+
field: string;
|
|
73
|
+
operator?: CMSFilterOperator; // defaults to 'eq'
|
|
74
|
+
value: unknown;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Sort configuration */
|
|
78
|
+
export interface CMSSortConfig {
|
|
79
|
+
field: string;
|
|
80
|
+
order?: 'asc' | 'desc'; // defaults to 'asc'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** CMSList query configuration */
|
|
84
|
+
export interface CMSListQuery {
|
|
85
|
+
collection: string;
|
|
86
|
+
filter?: CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown>;
|
|
87
|
+
sort?: CMSSortConfig | CMSSortConfig[];
|
|
88
|
+
limit?: number;
|
|
89
|
+
offset?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** CMSList component props (in JSON page) */
|
|
93
|
+
export interface CMSListProps extends CMSListQuery {
|
|
94
|
+
/** Children are repeated for each item with {{item.field}} context */
|
|
95
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Variables Types
|
|
3
|
+
* Handles color variable definitions and configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Color variables configuration
|
|
8
|
+
* Maps semantic color names to their hex/rgb values
|
|
9
|
+
*/
|
|
10
|
+
export interface ColorVariables {
|
|
11
|
+
colors: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Theme configuration with color set and metadata
|
|
16
|
+
*/
|
|
17
|
+
export interface Theme {
|
|
18
|
+
label: string;
|
|
19
|
+
colors: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Theme configuration file structure
|
|
24
|
+
* Supports multiple named themes with a default theme
|
|
25
|
+
*/
|
|
26
|
+
export interface ThemeConfig {
|
|
27
|
+
default: string;
|
|
28
|
+
themes: Record<string, Theme>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Color variable entry for editor display
|
|
33
|
+
*/
|
|
34
|
+
export interface ColorVariableEntry {
|
|
35
|
+
name: string;
|
|
36
|
+
value: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Theme entry for theme selector
|
|
41
|
+
*/
|
|
42
|
+
export interface ThemeEntry {
|
|
43
|
+
name: string;
|
|
44
|
+
label: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Definition Types
|
|
3
|
+
* Improved type safety with stricter types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentNode } from './nodes';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prop type definitions
|
|
10
|
+
*/
|
|
11
|
+
export type PropType = 'string' | 'select' | 'boolean' | 'number' | 'link' | 'file';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internationalization (i18n) value object
|
|
15
|
+
* Keys are locale codes (e.g., 'en', 'pl', 'de')
|
|
16
|
+
*/
|
|
17
|
+
export interface I18nValue {
|
|
18
|
+
_i18n: true;
|
|
19
|
+
[locale: string]: string | true; // true is for the _i18n marker itself
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Locale configuration with metadata
|
|
24
|
+
*/
|
|
25
|
+
export interface LocaleConfig {
|
|
26
|
+
code: string; // URL prefix & translation key (e.g., "en", "pl")
|
|
27
|
+
name: string; // English name for admin UI (e.g., "Polish")
|
|
28
|
+
nativeName: string; // Native name for public UI (e.g., "Polski")
|
|
29
|
+
langTag: string; // BCP 47 language tag for SEO (e.g., "pl-PL")
|
|
30
|
+
icon?: string; // Optional flag icon path (e.g., "/icons/flag-en.svg")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Internationalization configuration
|
|
35
|
+
*/
|
|
36
|
+
export interface I18nConfig {
|
|
37
|
+
defaultLocale: string;
|
|
38
|
+
locales: LocaleConfig[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Link prop value type
|
|
43
|
+
*/
|
|
44
|
+
export interface LinkPropValue {
|
|
45
|
+
href: string;
|
|
46
|
+
target?: '_blank';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Prop definition with improved type safety
|
|
51
|
+
*/
|
|
52
|
+
export interface PropDefinition {
|
|
53
|
+
type: PropType;
|
|
54
|
+
default?: string | number | boolean | I18nValue | LinkPropValue;
|
|
55
|
+
options?: readonly string[]; // Required for "select" type
|
|
56
|
+
accept?: string; // For "file" type: MIME pattern like "image/*", "video/*"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Structured component definition
|
|
61
|
+
*/
|
|
62
|
+
export interface StructuredComponentDefinition {
|
|
63
|
+
interface?: Record<string, PropDefinition>;
|
|
64
|
+
structure?: ComponentNode;
|
|
65
|
+
javascript?: string; // Vanilla JS code to be rendered at end of HTML
|
|
66
|
+
css?: string; // CSS code to be rendered in <style> tag in <head>
|
|
67
|
+
category?: string; // Component category for organization
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Component definition
|
|
72
|
+
* Supports both new format (just component) and legacy format (type/props/children/component)
|
|
73
|
+
*/
|
|
74
|
+
export interface ComponentDefinition {
|
|
75
|
+
type?: string; // Legacy format
|
|
76
|
+
props?: Record<string, unknown>; // Legacy format
|
|
77
|
+
children?: unknown[]; // Legacy format
|
|
78
|
+
component: StructuredComponentDefinition;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Line range for element line number tracking
|
|
83
|
+
*/
|
|
84
|
+
export interface LineRange {
|
|
85
|
+
startLine: number;
|
|
86
|
+
endLine: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* JSON page structure
|
|
91
|
+
*/
|
|
92
|
+
export interface JSONPage {
|
|
93
|
+
meta?: import('./api').PageMetaData;
|
|
94
|
+
components?: Record<string, ComponentDefinition>;
|
|
95
|
+
root?: ComponentNode;
|
|
96
|
+
_lineMap?: Record<string, LineRange>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Page data type that can be either a JSONPage or a component definition structure
|
|
101
|
+
*/
|
|
102
|
+
export type PageData = JSONPage | {
|
|
103
|
+
component: {
|
|
104
|
+
structure?: ComponentNode;
|
|
105
|
+
interface?: Record<string, PropDefinition>;
|
|
106
|
+
javascript?: string;
|
|
107
|
+
css?: string;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Page data with component structure (for type narrowing)
|
|
113
|
+
*/
|
|
114
|
+
export interface PageDataWithComponent {
|
|
115
|
+
component: {
|
|
116
|
+
structure?: ComponentNode;
|
|
117
|
+
interface?: Record<string, PropDefinition>;
|
|
118
|
+
javascript?: string;
|
|
119
|
+
css?: string;
|
|
120
|
+
};
|
|
121
|
+
}
|