@stati/core 1.8.0 → 1.10.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 +6 -6
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +39 -4
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +70 -2
- package/dist/core/isg/deps.js +21 -0
- package/dist/core/isg/hash.d.ts +14 -0
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +32 -1
- package/dist/core/isg/index.d.ts +1 -1
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +1 -1
- package/dist/core/isg/manifest.d.ts.map +1 -1
- package/dist/core/isg/manifest.js +7 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +43 -8
- package/dist/core/utils/callable-partials.d.ts +60 -0
- package/dist/core/utils/callable-partials.d.ts.map +1 -0
- package/dist/core/utils/callable-partials.js +108 -0
- package/dist/core/utils/index.d.ts +4 -0
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +6 -0
- package/dist/core/utils/navigation-helpers.d.ts +124 -0
- package/dist/core/utils/navigation-helpers.d.ts.map +1 -0
- package/dist/core/utils/navigation-helpers.js +219 -0
- package/dist/core/utils/partial-validation.d.ts +5 -2
- package/dist/core/utils/partial-validation.d.ts.map +1 -1
- package/dist/core/utils/partial-validation.js +35 -7
- package/dist/core/utils/server.d.ts +1 -1
- package/dist/core/utils/server.d.ts.map +1 -1
- package/dist/core/utils/server.js +14 -1
- package/dist/core/utils/tailwind-inventory.d.ts +91 -0
- package/dist/core/utils/tailwind-inventory.d.ts.map +1 -0
- package/dist/core/utils/tailwind-inventory.js +228 -0
- package/dist/core/utils/template-utils.d.ts +3 -0
- package/dist/core/utils/template-utils.d.ts.map +1 -1
- package/dist/core/utils/template-utils.js +27 -3
- package/dist/types/content.d.ts +24 -3
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/isg.d.ts +4 -1
- package/dist/types/isg.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a callable partial that can be used both as a value and as a function.
|
|
3
|
+
* This enables both syntaxes:
|
|
4
|
+
* - Direct usage: <%~ stati.partials.header %>
|
|
5
|
+
* - With props: <%~ stati.partials.hero({ title: 'Hello' }) %>
|
|
6
|
+
*
|
|
7
|
+
* @param eta - The Eta template engine instance
|
|
8
|
+
* @param partialPath - Absolute path to the partial template file
|
|
9
|
+
* @param baseContext - The base template context (without props)
|
|
10
|
+
* @param renderedContent - Pre-rendered content for the no-props case
|
|
11
|
+
* @returns A callable partial function
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const callable = makeCallablePartial(eta, '/path/to/partial.eta', baseContext, '<div>Header</div>');
|
|
16
|
+
*
|
|
17
|
+
* // Use without props (returns pre-rendered content)
|
|
18
|
+
* const html1 = callable.toString(); // '<div>Header</div>'
|
|
19
|
+
*
|
|
20
|
+
* // Use with props (re-renders with merged context)
|
|
21
|
+
* const html2 = callable({ title: 'Custom Title' }); // Renders with custom props
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function makeCallablePartial(eta, partialPath, baseContext, renderedContent) {
|
|
25
|
+
/**
|
|
26
|
+
* The main callable function.
|
|
27
|
+
* When called with props, re-renders the partial with merged context.
|
|
28
|
+
* When called without props, returns the pre-rendered content.
|
|
29
|
+
*/
|
|
30
|
+
const callable = (props) => {
|
|
31
|
+
if (!props || Object.keys(props).length === 0) {
|
|
32
|
+
// No props provided - return pre-rendered content
|
|
33
|
+
return renderedContent;
|
|
34
|
+
}
|
|
35
|
+
// Props provided - re-render with merged context
|
|
36
|
+
try {
|
|
37
|
+
const mergedContext = {
|
|
38
|
+
...baseContext,
|
|
39
|
+
props, // Make props available as stati.props
|
|
40
|
+
};
|
|
41
|
+
// Render the partial with the merged context using renderAsync
|
|
42
|
+
// This is a synchronous call despite the name when used with already-loaded templates
|
|
43
|
+
const result = eta.render(partialPath, mergedContext);
|
|
44
|
+
return result || '';
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(`Error rendering callable partial ${partialPath} with props:`, error);
|
|
48
|
+
return `<!-- Error rendering partial with props: ${error instanceof Error ? error.message : String(error)} -->`;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
// Create a Proxy to handle different usage patterns
|
|
52
|
+
const proxy = new Proxy(callable, {
|
|
53
|
+
/**
|
|
54
|
+
* Handle function calls: stati.partials.header({ props })
|
|
55
|
+
*/
|
|
56
|
+
apply(target, thisArg, args) {
|
|
57
|
+
return target.apply(thisArg, args);
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* Handle toString(): When used in template interpolation without parentheses
|
|
61
|
+
* Example: <%~ stati.partials.header %>
|
|
62
|
+
*/
|
|
63
|
+
get(target, prop) {
|
|
64
|
+
if (prop === 'toString' || prop === 'valueOf') {
|
|
65
|
+
return () => renderedContent;
|
|
66
|
+
}
|
|
67
|
+
// Allow other function properties to pass through
|
|
68
|
+
return Reflect.get(target, prop);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return proxy;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Wraps all partials in a record with callable partial wrappers.
|
|
75
|
+
* This allows partials to be used both as values and as functions.
|
|
76
|
+
*
|
|
77
|
+
* @param eta - The Eta template engine instance
|
|
78
|
+
* @param partials - Record mapping partial names to their rendered content
|
|
79
|
+
* @param partialPaths - Record mapping partial names to their absolute file paths
|
|
80
|
+
* @param baseContext - The base template context (without props)
|
|
81
|
+
* @returns Record of callable partials
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const callablePartials = wrapPartialsAsCallable(
|
|
86
|
+
* eta,
|
|
87
|
+
* { header: '<div>Header</div>', footer: '<div>Footer</div>' },
|
|
88
|
+
* { header: '/path/to/header.eta', footer: '/path/to/footer.eta' },
|
|
89
|
+
* baseContext
|
|
90
|
+
* );
|
|
91
|
+
*
|
|
92
|
+
* // Both syntaxes work
|
|
93
|
+
* callablePartials.header.toString(); // Direct usage
|
|
94
|
+
* callablePartials.header({ title: 'Custom' }); // With props
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function wrapPartialsAsCallable(eta, partials, partialPaths, baseContext) {
|
|
98
|
+
const callablePartials = {};
|
|
99
|
+
for (const [name, renderedContent] of Object.entries(partials)) {
|
|
100
|
+
const partialPath = partialPaths[name];
|
|
101
|
+
if (!partialPath) {
|
|
102
|
+
console.warn(`No path found for partial "${name}", skipping callable wrapper`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
callablePartials[name] = makeCallablePartial(eta, partialPath, baseContext, renderedContent);
|
|
106
|
+
}
|
|
107
|
+
return callablePartials;
|
|
108
|
+
}
|
|
@@ -6,8 +6,12 @@ export { readFile, writeFile, pathExists, ensureDir, remove, copyFile, readdir,
|
|
|
6
6
|
export { resolveSrcDir, resolveOutDir, resolveStaticDir, resolveCacheDir, resolveDevPaths, normalizeTemplatePath, resolveSrcPath, resolveOutPath, resolveStaticPath, } from './paths.js';
|
|
7
7
|
export { discoverLayout, isCollectionIndexPage, getCollectionPathForPage, } from './template-discovery.js';
|
|
8
8
|
export { propValue } from './template-utils.js';
|
|
9
|
+
export { trackTailwindClass, enableInventoryTracking, disableInventoryTracking, clearInventory, getInventory, getInventorySize, isTrackingEnabled, writeTailwindClassInventory, isTailwindUsed, resetTailwindDetection, loadPreviousInventory, } from './tailwind-inventory.js';
|
|
9
10
|
export { createValidatingPartialsProxy } from './partial-validation.js';
|
|
11
|
+
export { makeCallablePartial, wrapPartialsAsCallable } from './callable-partials.js';
|
|
12
|
+
export type { CallablePartial } from './callable-partials.js';
|
|
10
13
|
export { TemplateError, parseEtaError, createTemplateError } from './template-errors.js';
|
|
14
|
+
export { createNavigationHelpers } from './navigation-helpers.js';
|
|
11
15
|
export { resolvePrettyUrl } from './server.js';
|
|
12
16
|
export type { PrettyUrlResult } from './server.js';
|
|
13
17
|
export { createErrorOverlay, parseErrorDetails } from './error-overlay.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,GACL,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAGxE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,GACL,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,2BAA2B,EAC3B,cAAc,EACd,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAGxE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrF,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/core/utils/index.js
CHANGED
|
@@ -10,10 +10,16 @@ export { resolveSrcDir, resolveOutDir, resolveStaticDir, resolveCacheDir, resolv
|
|
|
10
10
|
export { discoverLayout, isCollectionIndexPage, getCollectionPathForPage, } from './template-discovery.js';
|
|
11
11
|
// Template utilities
|
|
12
12
|
export { propValue } from './template-utils.js';
|
|
13
|
+
// Tailwind inventory utilities
|
|
14
|
+
export { trackTailwindClass, enableInventoryTracking, disableInventoryTracking, clearInventory, getInventory, getInventorySize, isTrackingEnabled, writeTailwindClassInventory, isTailwindUsed, resetTailwindDetection, loadPreviousInventory, } from './tailwind-inventory.js';
|
|
13
15
|
// Partial validation utilities
|
|
14
16
|
export { createValidatingPartialsProxy } from './partial-validation.js';
|
|
17
|
+
// Callable partial utilities
|
|
18
|
+
export { makeCallablePartial, wrapPartialsAsCallable } from './callable-partials.js';
|
|
15
19
|
// Template error utilities
|
|
16
20
|
export { TemplateError, parseEtaError, createTemplateError } from './template-errors.js';
|
|
21
|
+
// Navigation helper utilities
|
|
22
|
+
export { createNavigationHelpers } from './navigation-helpers.js';
|
|
17
23
|
// Server utilities
|
|
18
24
|
export { resolvePrettyUrl } from './server.js';
|
|
19
25
|
// Error overlay utilities
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { NavNode, PageModel } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Navigation helper utilities for template context.
|
|
4
|
+
* Provides methods for querying and traversing the navigation tree.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Finds a navigation node by its path or URL.
|
|
8
|
+
*
|
|
9
|
+
* @param tree - Navigation tree to search
|
|
10
|
+
* @param path - Path or URL to find
|
|
11
|
+
* @returns The found node or undefined
|
|
12
|
+
*/
|
|
13
|
+
export declare function findNode(tree: NavNode[], path: string): NavNode | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Gets the children of a specific navigation node by path.
|
|
16
|
+
*
|
|
17
|
+
* @param tree - Navigation tree to search
|
|
18
|
+
* @param path - Path of the parent node
|
|
19
|
+
* @returns Array of child nodes or empty array if not found or no children
|
|
20
|
+
*/
|
|
21
|
+
export declare function getChildren(tree: NavNode[], path: string): NavNode[];
|
|
22
|
+
/**
|
|
23
|
+
* Gets the parent node of a specific path.
|
|
24
|
+
*
|
|
25
|
+
* @param tree - Navigation tree to search
|
|
26
|
+
* @param path - Path to find parent for
|
|
27
|
+
* @returns The parent node or undefined
|
|
28
|
+
*/
|
|
29
|
+
export declare function getParent(tree: NavNode[], path: string): NavNode | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Gets the siblings of a specific path (nodes at the same level).
|
|
32
|
+
*
|
|
33
|
+
* @param tree - Navigation tree to search
|
|
34
|
+
* @param path - Path to find siblings for
|
|
35
|
+
* @param includeSelf - Whether to include the node itself in the results
|
|
36
|
+
* @returns Array of sibling nodes or empty array
|
|
37
|
+
*/
|
|
38
|
+
export declare function getSiblings(tree: NavNode[], path: string, includeSelf?: boolean): NavNode[];
|
|
39
|
+
/**
|
|
40
|
+
* Gets a subtree starting from a specific path.
|
|
41
|
+
*
|
|
42
|
+
* @param tree - Navigation tree to search
|
|
43
|
+
* @param path - Root path for the subtree
|
|
44
|
+
* @returns The subtree as an array (single node with its children) or empty array
|
|
45
|
+
*/
|
|
46
|
+
export declare function getSubtree(tree: NavNode[], path: string): NavNode[];
|
|
47
|
+
/**
|
|
48
|
+
* Gets the breadcrumb trail from root to a specific path.
|
|
49
|
+
*
|
|
50
|
+
* @param tree - Navigation tree to search
|
|
51
|
+
* @param path - Path to get breadcrumbs for
|
|
52
|
+
* @returns Array of nodes from root to the target path
|
|
53
|
+
*/
|
|
54
|
+
export declare function getBreadcrumbs(tree: NavNode[], path: string): NavNode[];
|
|
55
|
+
/**
|
|
56
|
+
* Finds the current page's navigation node.
|
|
57
|
+
*
|
|
58
|
+
* @param tree - Navigation tree to search
|
|
59
|
+
* @param currentPage - Current page model
|
|
60
|
+
* @returns The navigation node for the current page or undefined
|
|
61
|
+
*/
|
|
62
|
+
export declare function getCurrentNode(tree: NavNode[], currentPage: PageModel): NavNode | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Creates a navigation helpers object for template context.
|
|
65
|
+
*
|
|
66
|
+
* @param tree - The full navigation tree
|
|
67
|
+
* @param currentPage - The current page being rendered
|
|
68
|
+
* @returns Object with navigation helper methods
|
|
69
|
+
*/
|
|
70
|
+
export declare function createNavigationHelpers(tree: NavNode[], currentPage: PageModel): {
|
|
71
|
+
/**
|
|
72
|
+
* The full navigation tree.
|
|
73
|
+
* Use stati.nav.tree to access the global navigation.
|
|
74
|
+
*/
|
|
75
|
+
tree: NavNode[];
|
|
76
|
+
/**
|
|
77
|
+
* Gets the full navigation tree.
|
|
78
|
+
* @returns The complete navigation tree
|
|
79
|
+
*/
|
|
80
|
+
getTree: () => NavNode[];
|
|
81
|
+
/**
|
|
82
|
+
* Finds a navigation node by path or URL.
|
|
83
|
+
* @param path - The path or URL to find
|
|
84
|
+
* @returns The found node or undefined
|
|
85
|
+
*/
|
|
86
|
+
findNode: (path: string) => NavNode | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Gets the children of a navigation node.
|
|
89
|
+
* @param path - The path of the parent node
|
|
90
|
+
* @returns Array of child navigation nodes
|
|
91
|
+
*/
|
|
92
|
+
getChildren: (path: string) => NavNode[];
|
|
93
|
+
/**
|
|
94
|
+
* Gets the parent of a navigation node.
|
|
95
|
+
* @param path - The path to find the parent for (defaults to current page)
|
|
96
|
+
* @returns The parent node or undefined
|
|
97
|
+
*/
|
|
98
|
+
getParent: (path?: string) => NavNode | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Gets the siblings of a navigation node.
|
|
101
|
+
* @param path - The path to find siblings for (defaults to current page)
|
|
102
|
+
* @param includeSelf - Whether to include the node itself
|
|
103
|
+
* @returns Array of sibling nodes
|
|
104
|
+
*/
|
|
105
|
+
getSiblings: (path?: string, includeSelf?: boolean) => NavNode[];
|
|
106
|
+
/**
|
|
107
|
+
* Gets a subtree starting from a specific path.
|
|
108
|
+
* @param path - The root path for the subtree
|
|
109
|
+
* @returns Array containing the subtree
|
|
110
|
+
*/
|
|
111
|
+
getSubtree: (path: string) => NavNode[];
|
|
112
|
+
/**
|
|
113
|
+
* Gets the breadcrumb trail for a path.
|
|
114
|
+
* @param path - The path to get breadcrumbs for (defaults to current page)
|
|
115
|
+
* @returns Array of nodes from root to the target
|
|
116
|
+
*/
|
|
117
|
+
getBreadcrumbs: (path?: string) => NavNode[];
|
|
118
|
+
/**
|
|
119
|
+
* Gets the current page's navigation node.
|
|
120
|
+
* @returns The navigation node for the current page or undefined
|
|
121
|
+
*/
|
|
122
|
+
getCurrentNode: () => NavNode | undefined;
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=navigation-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-helpers.d.ts","sourceRoot":"","sources":["../../../src/core/utils/navigation-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAE/D;;;GAGG;AAEH;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAiB3E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAGpE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAE5E;AAiCD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,EAAE,CAiBzF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAGnE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAIvE;AAkCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAE3F;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS;IAE3E;;;OAGG;;IAGH;;;OAGG;;IAGH;;;;OAIG;qBACc,MAAM;IAEvB;;;;OAIG;wBACiB,MAAM;IAE1B;;;;OAIG;uBACgB,MAAM;IAEzB;;;;;OAKG;yBACkB,MAAM;IAG3B;;;;OAIG;uBACgB,MAAM;IAEzB;;;;OAIG;4BACqB,MAAM;IAE9B;;;OAGG;;EAGN"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation helper utilities for template context.
|
|
3
|
+
* Provides methods for querying and traversing the navigation tree.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Finds a navigation node by its path or URL.
|
|
7
|
+
*
|
|
8
|
+
* @param tree - Navigation tree to search
|
|
9
|
+
* @param path - Path or URL to find
|
|
10
|
+
* @returns The found node or undefined
|
|
11
|
+
*/
|
|
12
|
+
export function findNode(tree, path) {
|
|
13
|
+
for (const node of tree) {
|
|
14
|
+
// Check current node
|
|
15
|
+
if (node.path === path || node.url === path) {
|
|
16
|
+
return node;
|
|
17
|
+
}
|
|
18
|
+
// Recursively search children
|
|
19
|
+
if (node.children && node.children.length > 0) {
|
|
20
|
+
const found = findNode(node.children, path);
|
|
21
|
+
if (found) {
|
|
22
|
+
return found;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Gets the children of a specific navigation node by path.
|
|
30
|
+
*
|
|
31
|
+
* @param tree - Navigation tree to search
|
|
32
|
+
* @param path - Path of the parent node
|
|
33
|
+
* @returns Array of child nodes or empty array if not found or no children
|
|
34
|
+
*/
|
|
35
|
+
export function getChildren(tree, path) {
|
|
36
|
+
const node = findNode(tree, path);
|
|
37
|
+
return node?.children || [];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gets the parent node of a specific path.
|
|
41
|
+
*
|
|
42
|
+
* @param tree - Navigation tree to search
|
|
43
|
+
* @param path - Path to find parent for
|
|
44
|
+
* @returns The parent node or undefined
|
|
45
|
+
*/
|
|
46
|
+
export function getParent(tree, path) {
|
|
47
|
+
return findParentNode(tree, path, null);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Helper function to find parent node recursively.
|
|
51
|
+
*
|
|
52
|
+
* @param nodes - Current nodes to search
|
|
53
|
+
* @param targetPath - Path we're looking for
|
|
54
|
+
* @param parent - Current parent node
|
|
55
|
+
* @returns The parent node or undefined
|
|
56
|
+
*/
|
|
57
|
+
function findParentNode(nodes, targetPath, parent) {
|
|
58
|
+
for (const node of nodes) {
|
|
59
|
+
// Check if this node is the target
|
|
60
|
+
if (node.path === targetPath || node.url === targetPath) {
|
|
61
|
+
return parent || undefined;
|
|
62
|
+
}
|
|
63
|
+
// Recursively search children with this node as parent
|
|
64
|
+
if (node.children && node.children.length > 0) {
|
|
65
|
+
const found = findParentNode(node.children, targetPath, node);
|
|
66
|
+
if (found !== undefined) {
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Gets the siblings of a specific path (nodes at the same level).
|
|
75
|
+
*
|
|
76
|
+
* @param tree - Navigation tree to search
|
|
77
|
+
* @param path - Path to find siblings for
|
|
78
|
+
* @param includeSelf - Whether to include the node itself in the results
|
|
79
|
+
* @returns Array of sibling nodes or empty array
|
|
80
|
+
*/
|
|
81
|
+
export function getSiblings(tree, path, includeSelf = false) {
|
|
82
|
+
const parent = getParent(tree, path);
|
|
83
|
+
// If no parent, check if the node is at root level
|
|
84
|
+
if (!parent) {
|
|
85
|
+
// Node might be at root level
|
|
86
|
+
const isAtRoot = tree.some((node) => node.path === path || node.url === path);
|
|
87
|
+
if (isAtRoot) {
|
|
88
|
+
return includeSelf ? tree : tree.filter((node) => node.path !== path && node.url !== path);
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const siblings = parent.children || [];
|
|
93
|
+
return includeSelf
|
|
94
|
+
? siblings
|
|
95
|
+
: siblings.filter((node) => node.path !== path && node.url !== path);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Gets a subtree starting from a specific path.
|
|
99
|
+
*
|
|
100
|
+
* @param tree - Navigation tree to search
|
|
101
|
+
* @param path - Root path for the subtree
|
|
102
|
+
* @returns The subtree as an array (single node with its children) or empty array
|
|
103
|
+
*/
|
|
104
|
+
export function getSubtree(tree, path) {
|
|
105
|
+
const node = findNode(tree, path);
|
|
106
|
+
return node ? [node] : [];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Gets the breadcrumb trail from root to a specific path.
|
|
110
|
+
*
|
|
111
|
+
* @param tree - Navigation tree to search
|
|
112
|
+
* @param path - Path to get breadcrumbs for
|
|
113
|
+
* @returns Array of nodes from root to the target path
|
|
114
|
+
*/
|
|
115
|
+
export function getBreadcrumbs(tree, path) {
|
|
116
|
+
const trail = [];
|
|
117
|
+
findBreadcrumbTrail(tree, path, trail);
|
|
118
|
+
return trail;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Helper function to build breadcrumb trail recursively.
|
|
122
|
+
*
|
|
123
|
+
* @param nodes - Current nodes to search
|
|
124
|
+
* @param targetPath - Path we're looking for
|
|
125
|
+
* @param trail - Current breadcrumb trail
|
|
126
|
+
* @returns True if the target was found in this branch
|
|
127
|
+
*/
|
|
128
|
+
function findBreadcrumbTrail(nodes, targetPath, trail) {
|
|
129
|
+
for (const node of nodes) {
|
|
130
|
+
// Add current node to trail
|
|
131
|
+
trail.push(node);
|
|
132
|
+
// Check if this is the target
|
|
133
|
+
if (node.path === targetPath || node.url === targetPath) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
// Check children
|
|
137
|
+
if (node.children && node.children.length > 0) {
|
|
138
|
+
if (findBreadcrumbTrail(node.children, targetPath, trail)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// This branch didn't contain the target, remove this node from trail
|
|
143
|
+
trail.pop();
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Finds the current page's navigation node.
|
|
149
|
+
*
|
|
150
|
+
* @param tree - Navigation tree to search
|
|
151
|
+
* @param currentPage - Current page model
|
|
152
|
+
* @returns The navigation node for the current page or undefined
|
|
153
|
+
*/
|
|
154
|
+
export function getCurrentNode(tree, currentPage) {
|
|
155
|
+
return findNode(tree, currentPage.url);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Creates a navigation helpers object for template context.
|
|
159
|
+
*
|
|
160
|
+
* @param tree - The full navigation tree
|
|
161
|
+
* @param currentPage - The current page being rendered
|
|
162
|
+
* @returns Object with navigation helper methods
|
|
163
|
+
*/
|
|
164
|
+
export function createNavigationHelpers(tree, currentPage) {
|
|
165
|
+
return {
|
|
166
|
+
/**
|
|
167
|
+
* The full navigation tree.
|
|
168
|
+
* Use stati.nav.tree to access the global navigation.
|
|
169
|
+
*/
|
|
170
|
+
tree,
|
|
171
|
+
/**
|
|
172
|
+
* Gets the full navigation tree.
|
|
173
|
+
* @returns The complete navigation tree
|
|
174
|
+
*/
|
|
175
|
+
getTree: () => tree,
|
|
176
|
+
/**
|
|
177
|
+
* Finds a navigation node by path or URL.
|
|
178
|
+
* @param path - The path or URL to find
|
|
179
|
+
* @returns The found node or undefined
|
|
180
|
+
*/
|
|
181
|
+
findNode: (path) => findNode(tree, path),
|
|
182
|
+
/**
|
|
183
|
+
* Gets the children of a navigation node.
|
|
184
|
+
* @param path - The path of the parent node
|
|
185
|
+
* @returns Array of child navigation nodes
|
|
186
|
+
*/
|
|
187
|
+
getChildren: (path) => getChildren(tree, path),
|
|
188
|
+
/**
|
|
189
|
+
* Gets the parent of a navigation node.
|
|
190
|
+
* @param path - The path to find the parent for (defaults to current page)
|
|
191
|
+
* @returns The parent node or undefined
|
|
192
|
+
*/
|
|
193
|
+
getParent: (path) => getParent(tree, path || currentPage.url),
|
|
194
|
+
/**
|
|
195
|
+
* Gets the siblings of a navigation node.
|
|
196
|
+
* @param path - The path to find siblings for (defaults to current page)
|
|
197
|
+
* @param includeSelf - Whether to include the node itself
|
|
198
|
+
* @returns Array of sibling nodes
|
|
199
|
+
*/
|
|
200
|
+
getSiblings: (path, includeSelf = false) => getSiblings(tree, path || currentPage.url, includeSelf),
|
|
201
|
+
/**
|
|
202
|
+
* Gets a subtree starting from a specific path.
|
|
203
|
+
* @param path - The root path for the subtree
|
|
204
|
+
* @returns Array containing the subtree
|
|
205
|
+
*/
|
|
206
|
+
getSubtree: (path) => getSubtree(tree, path),
|
|
207
|
+
/**
|
|
208
|
+
* Gets the breadcrumb trail for a path.
|
|
209
|
+
* @param path - The path to get breadcrumbs for (defaults to current page)
|
|
210
|
+
* @returns Array of nodes from root to the target
|
|
211
|
+
*/
|
|
212
|
+
getBreadcrumbs: (path) => getBreadcrumbs(tree, path || currentPage.url),
|
|
213
|
+
/**
|
|
214
|
+
* Gets the current page's navigation node.
|
|
215
|
+
* @returns The navigation node for the current page or undefined
|
|
216
|
+
*/
|
|
217
|
+
getCurrentNode: () => getCurrentNode(tree, currentPage),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { type CallablePartial } from './callable-partials.js';
|
|
1
2
|
/**
|
|
2
3
|
* Creates a development-mode Proxy for the partials object that throws errors
|
|
3
|
-
* when accessing non-existent partials instead of returning undefined
|
|
4
|
+
* when accessing non-existent partials instead of returning undefined.
|
|
5
|
+
*
|
|
6
|
+
* Supports both string partials and CallablePartial.
|
|
4
7
|
*/
|
|
5
|
-
export declare function createValidatingPartialsProxy(partials: Record<string,
|
|
8
|
+
export declare function createValidatingPartialsProxy<T extends string | CallablePartial>(partials: Record<string, T>): Record<string, T>;
|
|
6
9
|
//# sourceMappingURL=partial-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"partial-validation.d.ts","sourceRoot":"","sources":["../../../src/core/utils/partial-validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"partial-validation.d.ts","sourceRoot":"","sources":["../../../src/core/utils/partial-validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA8F9D;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,SAAS,MAAM,GAAG,eAAe,EAC9E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAC1B,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CA+EnB"}
|
|
@@ -84,7 +84,9 @@ function findSimilarPartialNames(targetName, availableNames) {
|
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* Creates a development-mode Proxy for the partials object that throws errors
|
|
87
|
-
* when accessing non-existent partials instead of returning undefined
|
|
87
|
+
* when accessing non-existent partials instead of returning undefined.
|
|
88
|
+
*
|
|
89
|
+
* Supports both string partials and CallablePartial.
|
|
88
90
|
*/
|
|
89
91
|
export function createValidatingPartialsProxy(partials) {
|
|
90
92
|
// In production, return partials as-is
|
|
@@ -92,6 +94,11 @@ export function createValidatingPartialsProxy(partials) {
|
|
|
92
94
|
if (getEnv() === 'production') {
|
|
93
95
|
return partials;
|
|
94
96
|
}
|
|
97
|
+
// If there are no partials, return the empty object as-is
|
|
98
|
+
// This avoids proxy-related issues during test serialization
|
|
99
|
+
if (Object.keys(partials).length === 0) {
|
|
100
|
+
return partials;
|
|
101
|
+
}
|
|
95
102
|
return new Proxy(partials, {
|
|
96
103
|
get(target, prop, receiver) {
|
|
97
104
|
// Allow normal object operations
|
|
@@ -104,18 +111,39 @@ export function createValidatingPartialsProxy(partials) {
|
|
|
104
111
|
return target[propName];
|
|
105
112
|
}
|
|
106
113
|
// Special case: allow accessing length, toString, etc.
|
|
107
|
-
|
|
114
|
+
// Also handle test framework inspection properties
|
|
115
|
+
if (propName in Object.prototype ||
|
|
116
|
+
propName === 'length' ||
|
|
117
|
+
propName === 'constructor' ||
|
|
118
|
+
propName === 'then' || // Promise detection
|
|
119
|
+
propName === '$$typeof' || // React inspection
|
|
120
|
+
propName === 'nodeType' || // DOM node detection
|
|
121
|
+
propName === 'asymmetricMatch' || // Jest/Vitest matcher
|
|
122
|
+
propName === 'toJSON' // JSON serialization
|
|
123
|
+
) {
|
|
108
124
|
return Reflect.get(target, prop, receiver);
|
|
109
125
|
}
|
|
110
126
|
// Property doesn't exist - return error overlay HTML instead of throwing
|
|
111
127
|
const availablePartials = Object.keys(target);
|
|
112
128
|
const suggestions = findSimilarPartialNames(propName, availablePartials);
|
|
113
|
-
// Special case: throw error if no partials are available at all
|
|
114
|
-
if (availablePartials.length === 0) {
|
|
115
|
-
throw new Error('No partials are available');
|
|
116
|
-
}
|
|
117
129
|
// In development, render an inline error overlay
|
|
118
|
-
|
|
130
|
+
const errorHtml = createInlineErrorOverlay(propName, suggestions);
|
|
131
|
+
// Check if we're dealing with CallablePartials by testing a known partial
|
|
132
|
+
const samplePartial = Object.values(target)[0];
|
|
133
|
+
const isCallable = typeof samplePartial === 'function';
|
|
134
|
+
if (isCallable) {
|
|
135
|
+
// For CallablePartial, return a function that returns the error HTML
|
|
136
|
+
// This prevents "string is not a function" errors when templates call missing partials
|
|
137
|
+
// Accept any arguments to handle props being passed
|
|
138
|
+
const errorFunction = (..._args) => errorHtml;
|
|
139
|
+
errorFunction.toString = () => errorHtml;
|
|
140
|
+
errorFunction.valueOf = () => errorHtml;
|
|
141
|
+
return errorFunction;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// For string partials, return the error HTML directly
|
|
145
|
+
return errorHtml;
|
|
146
|
+
}
|
|
119
147
|
},
|
|
120
148
|
has(target, prop) {
|
|
121
149
|
return prop in target;
|
|
@@ -12,7 +12,7 @@ export interface PrettyUrlResult {
|
|
|
12
12
|
* This handles common patterns like:
|
|
13
13
|
* - /path/ -> /path/index.html
|
|
14
14
|
* - /path/ -> /path.html (if no index.html exists)
|
|
15
|
-
* - /path -> /path.html (when original path is not found)
|
|
15
|
+
* - /path -> /path.html (when original path is not found and has no extension)
|
|
16
16
|
*
|
|
17
17
|
* @param outDir The output directory to serve files from
|
|
18
18
|
* @param requestPath The requested URL path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,CAAC,CA2D1B"}
|
|
@@ -5,7 +5,7 @@ import { stat } from 'fs/promises';
|
|
|
5
5
|
* This handles common patterns like:
|
|
6
6
|
* - /path/ -> /path/index.html
|
|
7
7
|
* - /path/ -> /path.html (if no index.html exists)
|
|
8
|
-
* - /path -> /path.html (when original path is not found)
|
|
8
|
+
* - /path -> /path.html (when original path is not found and has no extension)
|
|
9
9
|
*
|
|
10
10
|
* @param outDir The output directory to serve files from
|
|
11
11
|
* @param requestPath The requested URL path
|
|
@@ -55,6 +55,19 @@ export async function resolvePrettyUrl(outDir, requestPath, originalFilePath) {
|
|
|
55
55
|
// Continue to not found
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
else if (!requestPath.includes('.')) {
|
|
59
|
+
// For requests without trailing slash and without extension, try .html
|
|
60
|
+
const htmlPath = join(outDir, `${requestPath}.html`);
|
|
61
|
+
try {
|
|
62
|
+
const stats = await stat(htmlPath);
|
|
63
|
+
if (stats.isFile()) {
|
|
64
|
+
return { filePath: htmlPath, found: true };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Continue to not found
|
|
69
|
+
}
|
|
70
|
+
}
|
|
58
71
|
// No fallback worked
|
|
59
72
|
return { filePath: null, found: false };
|
|
60
73
|
}
|