@withl5e/l5e 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/action.js +10 -0
- package/dist/action.js.map +1 -0
- package/dist/client-D67hK4Yy.js +9 -0
- package/dist/client-D67hK4Yy.js.map +1 -0
- package/dist/entry-server-Ckh6zfgm.js +258 -0
- package/dist/entry-server-Ckh6zfgm.js.map +1 -0
- package/dist/entry-server.js +12 -0
- package/dist/entry-server.js.map +1 -0
- package/dist/generateMetadata-C5QsMS-H.js +144 -0
- package/dist/generateMetadata-C5QsMS-H.js.map +1 -0
- package/dist/index-BIt7MJT9.js +163 -0
- package/dist/index-BIt7MJT9.js.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/island/client.js +5 -0
- package/dist/island/client.js.map +1 -0
- package/dist/island/runtime.js +98 -0
- package/dist/island/runtime.js.map +1 -0
- package/dist/island.js +39 -0
- package/dist/island.js.map +1 -0
- package/dist/jsx-runtime-C2Vw67N2.js +256 -0
- package/dist/jsx-runtime-C2Vw67N2.js.map +1 -0
- package/dist/jsx-runtime.js +26 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/middleware.js +9 -0
- package/dist/middleware.js.map +1 -0
- package/dist/seo.js +7 -0
- package/dist/seo.js.map +1 -0
- package/dist/server.js +489 -0
- package/dist/server.js.map +1 -0
- package/dist/swap/server.js +15 -0
- package/dist/swap/server.js.map +1 -0
- package/dist/swap.js +121 -0
- package/dist/swap.js.map +1 -0
- package/dist/tooltip.js +129 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/vite-plugin.js +381 -0
- package/dist/vite-plugin.js.map +1 -0
- package/index.ts +1 -0
- package/package.json +129 -0
- package/src/action/define-action.ts +8 -0
- package/src/action/index.ts +2 -0
- package/src/action/types.ts +21 -0
- package/src/core/bundler.ts +275 -0
- package/src/core/const.ts +2 -0
- package/src/core/entry-server.d.ts +1 -0
- package/src/core/entry-server.ts +381 -0
- package/src/core/exceptions.ts +80 -0
- package/src/core/head-priority.ts +15 -0
- package/src/core/index.ts +40 -0
- package/src/core/jsx-runtime.ts +325 -0
- package/src/core/jsx-types.d.ts +548 -0
- package/src/core/render.ts +181 -0
- package/src/core/request.ts +31 -0
- package/src/core/server.ts +740 -0
- package/src/core/vite-plugin.ts +779 -0
- package/src/island/ClientIsland.ts +71 -0
- package/src/island/client.ts +3 -0
- package/src/island/index.ts +3 -0
- package/src/island/runtime.ts +149 -0
- package/src/island/strategy-registry.ts +10 -0
- package/src/island/types.ts +28 -0
- package/src/middleware/defineMiddleware.ts +5 -0
- package/src/middleware/index.ts +133 -0
- package/src/middleware/sequence.ts +105 -0
- package/src/middleware/types.ts +28 -0
- package/src/seo/generateMetadata.tsx +559 -0
- package/src/seo/index.ts +10 -0
- package/src/seo/mergeMetadata.ts +200 -0
- package/src/seo/types.ts +316 -0
- package/src/swap/SwapResponse.tsx +16 -0
- package/src/swap/create-swap.ts +121 -0
- package/src/swap/index.ts +8 -0
- package/src/swap/parse.ts +12 -0
- package/src/swap/server.ts +1 -0
- package/src/swap/swap.ts +57 -0
- package/src/swap/types.ts +47 -0
- package/src/swap/utils.ts +7 -0
- package/src/tooltip/index.ts +2 -0
- package/src/tooltip/tooltip-loader.ts +108 -0
- package/src/tooltip/tooltip-runtime.ts +173 -0
- package/types.d.ts +14 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { Metadata } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merges parent and child metadata objects
|
|
5
|
+
* Follows Next.js shallow merge pattern with deep merge for nested objects
|
|
6
|
+
*
|
|
7
|
+
* @param parent - Parent metadata (from layout/global loader)
|
|
8
|
+
* @param child - Child metadata (from page/view loader)
|
|
9
|
+
* @returns Merged metadata object
|
|
10
|
+
*/
|
|
11
|
+
export function mergeMetadata(
|
|
12
|
+
parent: Metadata | null | undefined,
|
|
13
|
+
child: Metadata | null | undefined,
|
|
14
|
+
): Metadata {
|
|
15
|
+
// If no parent, return child (or empty object)
|
|
16
|
+
if (!parent) {
|
|
17
|
+
return child || {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// If no child, return parent
|
|
21
|
+
if (!child) {
|
|
22
|
+
return parent;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Shallow merge base properties (child overrides parent)
|
|
26
|
+
const merged: Metadata = {
|
|
27
|
+
...parent,
|
|
28
|
+
...child,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Deep merge for nested objects
|
|
32
|
+
// OpenGraph: merge nested properties
|
|
33
|
+
if (child.openGraph || parent.openGraph) {
|
|
34
|
+
if (child.openGraph && parent.openGraph) {
|
|
35
|
+
merged.openGraph = {
|
|
36
|
+
...parent.openGraph,
|
|
37
|
+
...child.openGraph,
|
|
38
|
+
// Deep merge for nested arrays/objects in openGraph
|
|
39
|
+
images: child.openGraph.images ?? parent.openGraph.images,
|
|
40
|
+
videos: child.openGraph.videos ?? parent.openGraph.videos,
|
|
41
|
+
audio: child.openGraph.audio ?? parent.openGraph.audio,
|
|
42
|
+
alternateLocale: child.openGraph.alternateLocale ?? parent.openGraph.alternateLocale,
|
|
43
|
+
authors: child.openGraph.authors ?? parent.openGraph.authors,
|
|
44
|
+
tags: child.openGraph.tags ?? parent.openGraph.tags,
|
|
45
|
+
};
|
|
46
|
+
} else {
|
|
47
|
+
merged.openGraph = child.openGraph || parent.openGraph;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Twitter: merge nested properties
|
|
52
|
+
if (child.twitter || parent.twitter) {
|
|
53
|
+
if (child.twitter && parent.twitter) {
|
|
54
|
+
merged.twitter = {
|
|
55
|
+
...parent.twitter,
|
|
56
|
+
...child.twitter,
|
|
57
|
+
// Deep merge for nested objects
|
|
58
|
+
images: child.twitter.images ?? parent.twitter.images,
|
|
59
|
+
app: child.twitter.app
|
|
60
|
+
? {
|
|
61
|
+
...parent.twitter.app,
|
|
62
|
+
...child.twitter.app,
|
|
63
|
+
id: {
|
|
64
|
+
...parent.twitter.app?.id,
|
|
65
|
+
...child.twitter.app.id,
|
|
66
|
+
},
|
|
67
|
+
url: {
|
|
68
|
+
...parent.twitter.app?.url,
|
|
69
|
+
...child.twitter.app.url,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
: parent.twitter.app,
|
|
73
|
+
};
|
|
74
|
+
} else {
|
|
75
|
+
merged.twitter = child.twitter || parent.twitter;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Icons: merge nested properties
|
|
80
|
+
if (child.icons || parent.icons) {
|
|
81
|
+
if (child.icons && parent.icons) {
|
|
82
|
+
merged.icons = {
|
|
83
|
+
icon: child.icons.icon ?? parent.icons.icon,
|
|
84
|
+
shortcut: child.icons.shortcut ?? parent.icons.shortcut,
|
|
85
|
+
apple: child.icons.apple ?? parent.icons.apple,
|
|
86
|
+
other: child.icons.other ?? parent.icons.other,
|
|
87
|
+
};
|
|
88
|
+
} else {
|
|
89
|
+
merged.icons = child.icons || parent.icons;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Verification: merge nested properties
|
|
94
|
+
if (child.verification || parent.verification) {
|
|
95
|
+
if (child.verification && parent.verification) {
|
|
96
|
+
merged.verification = {
|
|
97
|
+
...parent.verification,
|
|
98
|
+
...child.verification,
|
|
99
|
+
// Deep merge for other verification tags
|
|
100
|
+
other: child.verification.other
|
|
101
|
+
? {
|
|
102
|
+
...parent.verification.other,
|
|
103
|
+
...child.verification.other,
|
|
104
|
+
}
|
|
105
|
+
: parent.verification.other,
|
|
106
|
+
me: child.verification.me ?? parent.verification.me,
|
|
107
|
+
};
|
|
108
|
+
} else {
|
|
109
|
+
merged.verification = child.verification || parent.verification;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// AppLinks: merge nested properties
|
|
114
|
+
if (child.appLinks || parent.appLinks) {
|
|
115
|
+
if (child.appLinks && parent.appLinks) {
|
|
116
|
+
merged.appLinks = {
|
|
117
|
+
ios: child.appLinks.ios
|
|
118
|
+
? {
|
|
119
|
+
...parent.appLinks.ios,
|
|
120
|
+
...child.appLinks.ios,
|
|
121
|
+
}
|
|
122
|
+
: parent.appLinks.ios,
|
|
123
|
+
android: child.appLinks.android
|
|
124
|
+
? {
|
|
125
|
+
...parent.appLinks.android,
|
|
126
|
+
...child.appLinks.android,
|
|
127
|
+
}
|
|
128
|
+
: parent.appLinks.android,
|
|
129
|
+
web: child.appLinks.web
|
|
130
|
+
? {
|
|
131
|
+
...parent.appLinks.web,
|
|
132
|
+
...child.appLinks.web,
|
|
133
|
+
}
|
|
134
|
+
: parent.appLinks.web,
|
|
135
|
+
};
|
|
136
|
+
} else {
|
|
137
|
+
merged.appLinks = child.appLinks || parent.appLinks;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// FormatDetection: merge nested properties
|
|
142
|
+
if (child.formatDetection || parent.formatDetection) {
|
|
143
|
+
if (child.formatDetection && parent.formatDetection) {
|
|
144
|
+
merged.formatDetection = {
|
|
145
|
+
...parent.formatDetection,
|
|
146
|
+
...child.formatDetection,
|
|
147
|
+
};
|
|
148
|
+
} else {
|
|
149
|
+
merged.formatDetection = child.formatDetection || parent.formatDetection;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Viewport: merge if both are objects
|
|
154
|
+
if (child.viewport && parent.viewport) {
|
|
155
|
+
if (typeof child.viewport === 'object' && typeof parent.viewport === 'object') {
|
|
156
|
+
merged.viewport = {
|
|
157
|
+
...parent.viewport,
|
|
158
|
+
...child.viewport,
|
|
159
|
+
} as Metadata['viewport'];
|
|
160
|
+
} else {
|
|
161
|
+
// If either is string, child overrides
|
|
162
|
+
merged.viewport = child.viewport;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Robots: merge if both are objects
|
|
167
|
+
if (child.robots && parent.robots) {
|
|
168
|
+
if (typeof child.robots === 'object' && typeof parent.robots === 'object') {
|
|
169
|
+
merged.robots = {
|
|
170
|
+
...parent.robots,
|
|
171
|
+
...child.robots,
|
|
172
|
+
} as Metadata['robots'];
|
|
173
|
+
} else {
|
|
174
|
+
// If either is string, child overrides
|
|
175
|
+
merged.robots = child.robots;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Array fields: child overrides parent (shallow merge behavior)
|
|
180
|
+
// These are already handled by spread operator above
|
|
181
|
+
// But we explicitly handle them for clarity:
|
|
182
|
+
merged.keywords = child.keywords ?? parent.keywords;
|
|
183
|
+
merged.themeColor = child.themeColor ?? parent.themeColor;
|
|
184
|
+
merged.archives = child.archives ?? parent.archives;
|
|
185
|
+
merged.assets = child.assets ?? parent.assets;
|
|
186
|
+
|
|
187
|
+
// Other: merge objects
|
|
188
|
+
if (child.other || parent.other) {
|
|
189
|
+
if (child.other && parent.other) {
|
|
190
|
+
merged.other = {
|
|
191
|
+
...parent.other,
|
|
192
|
+
...child.other,
|
|
193
|
+
};
|
|
194
|
+
} else {
|
|
195
|
+
merged.other = child.other || parent.other;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return merged;
|
|
200
|
+
}
|
package/src/seo/types.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// Basic metadata types
|
|
2
|
+
export type Author = { name: string; url?: string };
|
|
3
|
+
|
|
4
|
+
export interface Metadata {
|
|
5
|
+
// Basic meta tags
|
|
6
|
+
charset?: string;
|
|
7
|
+
title?: string | TemplateString;
|
|
8
|
+
description?: string;
|
|
9
|
+
keywords?: string | string[];
|
|
10
|
+
author?: string | Author | Author[];
|
|
11
|
+
|
|
12
|
+
// URL and canonical
|
|
13
|
+
canonical?: string;
|
|
14
|
+
|
|
15
|
+
// Robots and indexing
|
|
16
|
+
robots?:
|
|
17
|
+
| string
|
|
18
|
+
| {
|
|
19
|
+
index?: boolean;
|
|
20
|
+
follow?: boolean;
|
|
21
|
+
noarchive?: boolean;
|
|
22
|
+
nosnippet?: boolean;
|
|
23
|
+
noimageindex?: boolean;
|
|
24
|
+
nocache?: boolean;
|
|
25
|
+
allowQueryNames?: string | string[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Viewport
|
|
29
|
+
viewport?:
|
|
30
|
+
| string
|
|
31
|
+
| {
|
|
32
|
+
width?: string | number;
|
|
33
|
+
height?: string | number;
|
|
34
|
+
initialScale?: number;
|
|
35
|
+
maximumScale?: number;
|
|
36
|
+
minimumScale?: number;
|
|
37
|
+
userScalable?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Theme and colors
|
|
41
|
+
themeColor?: string | string[];
|
|
42
|
+
colorScheme?: 'light' | 'dark' | 'light dark' | 'dark light';
|
|
43
|
+
|
|
44
|
+
// Icons
|
|
45
|
+
icons?: {
|
|
46
|
+
icon?: string | string[];
|
|
47
|
+
shortcut?: string;
|
|
48
|
+
apple?: string | string[];
|
|
49
|
+
other?: Array<{
|
|
50
|
+
rel?: string;
|
|
51
|
+
url: string;
|
|
52
|
+
sizes?: string;
|
|
53
|
+
type?: string;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Application specific
|
|
58
|
+
generator?: string;
|
|
59
|
+
creator?: string;
|
|
60
|
+
publisher?: string;
|
|
61
|
+
referrer?:
|
|
62
|
+
| 'no-referrer'
|
|
63
|
+
| 'origin'
|
|
64
|
+
| 'no-referrer-when-downgrade'
|
|
65
|
+
| 'origin-when-cross-origin'
|
|
66
|
+
| 'same-origin'
|
|
67
|
+
| 'strict-origin'
|
|
68
|
+
| 'strict-origin-when-cross-origin'
|
|
69
|
+
| 'unsafe-url';
|
|
70
|
+
|
|
71
|
+
// Pagination links
|
|
72
|
+
prev?: string;
|
|
73
|
+
next?: string;
|
|
74
|
+
|
|
75
|
+
// Format detection
|
|
76
|
+
formatDetection?: {
|
|
77
|
+
telephone?: boolean;
|
|
78
|
+
date?: boolean;
|
|
79
|
+
address?: boolean;
|
|
80
|
+
email?: boolean;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Open Graph
|
|
84
|
+
openGraph?: {
|
|
85
|
+
type?:
|
|
86
|
+
| 'website'
|
|
87
|
+
| 'article'
|
|
88
|
+
| 'book'
|
|
89
|
+
| 'profile'
|
|
90
|
+
| 'music.song'
|
|
91
|
+
| 'music.album'
|
|
92
|
+
| 'music.playlist'
|
|
93
|
+
| 'music.radio_station'
|
|
94
|
+
| 'video.movie'
|
|
95
|
+
| 'video.episode'
|
|
96
|
+
| 'video.tv_show'
|
|
97
|
+
| 'video.other';
|
|
98
|
+
title?: string;
|
|
99
|
+
description?: string;
|
|
100
|
+
siteName?: string;
|
|
101
|
+
url?: string;
|
|
102
|
+
locale?: string;
|
|
103
|
+
alternateLocale?: string | string[];
|
|
104
|
+
images?: string | OpenGraphImage | Array<string | OpenGraphImage>;
|
|
105
|
+
videos?: string | OpenGraphVideo | Array<string | OpenGraphVideo>;
|
|
106
|
+
audio?: string | OpenGraphAudio | Array<string | OpenGraphAudio>;
|
|
107
|
+
determiner?: 'a' | 'an' | 'the' | 'auto' | '';
|
|
108
|
+
|
|
109
|
+
// Article specific
|
|
110
|
+
publishedTime?: string;
|
|
111
|
+
modifiedTime?: string;
|
|
112
|
+
expirationTime?: string;
|
|
113
|
+
authors?: string | string[];
|
|
114
|
+
section?: string;
|
|
115
|
+
tags?: string | string[];
|
|
116
|
+
|
|
117
|
+
// Book specific
|
|
118
|
+
isbn?: string;
|
|
119
|
+
releaseDate?: string;
|
|
120
|
+
|
|
121
|
+
// Profile specific
|
|
122
|
+
firstName?: string;
|
|
123
|
+
lastName?: string;
|
|
124
|
+
username?: string;
|
|
125
|
+
gender?: string;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Twitter Card
|
|
129
|
+
twitter?: {
|
|
130
|
+
card?: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
131
|
+
site?: string;
|
|
132
|
+
siteId?: string;
|
|
133
|
+
creator?: string;
|
|
134
|
+
creatorId?: string;
|
|
135
|
+
title?: string;
|
|
136
|
+
description?: string;
|
|
137
|
+
images?: string | TwitterImage | Array<string | TwitterImage>;
|
|
138
|
+
app?: {
|
|
139
|
+
id: {
|
|
140
|
+
iphone?: string;
|
|
141
|
+
ipad?: string;
|
|
142
|
+
googleplay?: string;
|
|
143
|
+
};
|
|
144
|
+
url?: {
|
|
145
|
+
iphone?: string;
|
|
146
|
+
ipad?: string;
|
|
147
|
+
googleplay?: string;
|
|
148
|
+
};
|
|
149
|
+
name?: string;
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Verification
|
|
154
|
+
verification?: {
|
|
155
|
+
google?: string;
|
|
156
|
+
yahoo?: string;
|
|
157
|
+
yandex?: string;
|
|
158
|
+
me?: string | string[];
|
|
159
|
+
other?: Record<string, string | string[]>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// App Links
|
|
163
|
+
appLinks?: {
|
|
164
|
+
ios?: {
|
|
165
|
+
url?: string;
|
|
166
|
+
app_store_id?: string;
|
|
167
|
+
app_name?: string;
|
|
168
|
+
};
|
|
169
|
+
android?: {
|
|
170
|
+
package?: string;
|
|
171
|
+
app_name?: string;
|
|
172
|
+
url?: string;
|
|
173
|
+
};
|
|
174
|
+
web?: {
|
|
175
|
+
url?: string;
|
|
176
|
+
should_fallback?: boolean;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Additional meta tags
|
|
181
|
+
other?: Record<string, string | number | string[]>;
|
|
182
|
+
|
|
183
|
+
// Archives (for blog posts, etc.)
|
|
184
|
+
archives?: string | string[];
|
|
185
|
+
|
|
186
|
+
// Assets and manifests
|
|
187
|
+
assets?: string | string[];
|
|
188
|
+
manifest?: string;
|
|
189
|
+
|
|
190
|
+
feed?: string;
|
|
191
|
+
|
|
192
|
+
// Category
|
|
193
|
+
category?: string;
|
|
194
|
+
|
|
195
|
+
// Classification
|
|
196
|
+
classification?: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface OpenGraphImage {
|
|
200
|
+
url: string;
|
|
201
|
+
secureUrl?: string;
|
|
202
|
+
alt?: string;
|
|
203
|
+
type?: string;
|
|
204
|
+
width?: string | number;
|
|
205
|
+
height?: string | number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface OpenGraphVideo {
|
|
209
|
+
url: string;
|
|
210
|
+
secureUrl?: string;
|
|
211
|
+
type?: string;
|
|
212
|
+
width?: string | number;
|
|
213
|
+
height?: string | number;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface OpenGraphAudio {
|
|
217
|
+
url: string;
|
|
218
|
+
secureUrl?: string;
|
|
219
|
+
type?: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface TwitterImage {
|
|
223
|
+
url: string;
|
|
224
|
+
alt?: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface TemplateString {
|
|
228
|
+
default: string;
|
|
229
|
+
template?: string;
|
|
230
|
+
absolute?: boolean;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// For dynamic metadata generation
|
|
234
|
+
export interface ResolvingMetadata extends Promise<Metadata> {}
|
|
235
|
+
|
|
236
|
+
// Page props type for generateMetadata
|
|
237
|
+
export interface MetadataProps<
|
|
238
|
+
Params = Record<string, string | string[]>,
|
|
239
|
+
SearchParams = Record<string, string | string[] | undefined>,
|
|
240
|
+
> {
|
|
241
|
+
params?: Params;
|
|
242
|
+
searchParams?: SearchParams;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Schema.org types for structured data
|
|
246
|
+
export interface OrganizationSchema {
|
|
247
|
+
'@context': 'https://schema.org';
|
|
248
|
+
'@type': 'Organization';
|
|
249
|
+
name: string;
|
|
250
|
+
url: string;
|
|
251
|
+
logo?: string;
|
|
252
|
+
description?: string;
|
|
253
|
+
sameAs?: string[]; // Social media profiles
|
|
254
|
+
contactPoint?: {
|
|
255
|
+
'@type': 'ContactPoint';
|
|
256
|
+
email?: string;
|
|
257
|
+
contactType?: string;
|
|
258
|
+
availableLanguage?: string[];
|
|
259
|
+
};
|
|
260
|
+
address?: {
|
|
261
|
+
'@type': 'PostalAddress';
|
|
262
|
+
addressCountry?: string;
|
|
263
|
+
addressLocality?: string;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface WebSiteSchema {
|
|
268
|
+
'@context': 'https://schema.org';
|
|
269
|
+
'@type': 'WebSite';
|
|
270
|
+
name: string;
|
|
271
|
+
url: string;
|
|
272
|
+
description?: string;
|
|
273
|
+
inLanguage?: string;
|
|
274
|
+
potentialAction?: {
|
|
275
|
+
'@type': 'SearchAction';
|
|
276
|
+
target: {
|
|
277
|
+
'@type': 'EntryPoint';
|
|
278
|
+
urlTemplate: string;
|
|
279
|
+
};
|
|
280
|
+
'query-input': string;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface BreadcrumbSchema {
|
|
285
|
+
'@context': 'https://schema.org';
|
|
286
|
+
'@type': 'BreadcrumbList';
|
|
287
|
+
itemListElement: Array<{
|
|
288
|
+
'@type': 'ListItem';
|
|
289
|
+
position: number;
|
|
290
|
+
name: string;
|
|
291
|
+
item?: string;
|
|
292
|
+
}>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface WebPageSchema {
|
|
296
|
+
'@context': 'https://schema.org';
|
|
297
|
+
'@type': 'WebPage';
|
|
298
|
+
name: string;
|
|
299
|
+
url: string;
|
|
300
|
+
description?: string;
|
|
301
|
+
inLanguage?: string;
|
|
302
|
+
isPartOf?: {
|
|
303
|
+
'@type': 'WebSite';
|
|
304
|
+
'@id': string;
|
|
305
|
+
};
|
|
306
|
+
about?: {
|
|
307
|
+
'@type': 'Thing';
|
|
308
|
+
name: string;
|
|
309
|
+
};
|
|
310
|
+
primaryImageOfPage?: {
|
|
311
|
+
'@type': 'ImageObject';
|
|
312
|
+
url: string;
|
|
313
|
+
width?: number;
|
|
314
|
+
height?: number;
|
|
315
|
+
};
|
|
316
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface SwapResponseProps {
|
|
2
|
+
children: JSX.Element | JSX.Element[];
|
|
3
|
+
hasMore?: boolean;
|
|
4
|
+
meta?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function SwapResponse({ children, hasMore, meta }: SwapResponseProps) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-has-more={hasMore !== undefined ? String(hasMore) : undefined}
|
|
11
|
+
{...(meta && Object.fromEntries(Object.entries(meta).map(([k, v]) => [`data-${k}`, v])))}
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { parseHTML } from './parse';
|
|
2
|
+
import { swap } from './swap';
|
|
3
|
+
import type { SwapOptions, SwapInstance, SwapMeta, SwapContext } from './types';
|
|
4
|
+
|
|
5
|
+
const REQUEST_CLASS = 'l5e-request';
|
|
6
|
+
|
|
7
|
+
function getNaturalEvent(el: Element): string {
|
|
8
|
+
const tag = el.tagName.toLowerCase();
|
|
9
|
+
if (tag === 'form') return 'submit';
|
|
10
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') return 'change';
|
|
11
|
+
return 'click';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createSwap(opts: SwapOptions): SwapInstance {
|
|
15
|
+
let destroyed = false;
|
|
16
|
+
const swapMode = opts.swap ?? 'innerHTML';
|
|
17
|
+
|
|
18
|
+
const triggerEls = opts.trigger
|
|
19
|
+
? Array.from(document.querySelectorAll<HTMLElement>(opts.trigger))
|
|
20
|
+
: [];
|
|
21
|
+
|
|
22
|
+
const targetEl = document.querySelector<HTMLElement>(opts.target);
|
|
23
|
+
|
|
24
|
+
const firstTrigger = triggerEls[0] ?? null;
|
|
25
|
+
const eventName = opts.event ?? (firstTrigger ? getNaturalEvent(firstTrigger) : 'click');
|
|
26
|
+
const shouldPreventDefault = eventName === 'submit';
|
|
27
|
+
|
|
28
|
+
const loadingCfg = opts.loading;
|
|
29
|
+
const loadingClass = loadingCfg?.class ?? REQUEST_CLASS;
|
|
30
|
+
|
|
31
|
+
function applyLoading(currentTrigger: HTMLElement | null): () => void {
|
|
32
|
+
if (!loadingCfg) return () => {};
|
|
33
|
+
const el = loadingCfg.target
|
|
34
|
+
? document.querySelector<HTMLElement>(loadingCfg.target)
|
|
35
|
+
: currentTrigger;
|
|
36
|
+
if (!el) return () => {};
|
|
37
|
+
|
|
38
|
+
const shouldDisable = loadingCfg.disabled ?? el === currentTrigger;
|
|
39
|
+
const originalHtml = el.innerHTML;
|
|
40
|
+
const originalDisabled = (el as HTMLButtonElement).disabled ?? false;
|
|
41
|
+
|
|
42
|
+
el.classList.add(loadingClass);
|
|
43
|
+
if (loadingCfg.html) el.innerHTML = loadingCfg.html;
|
|
44
|
+
if (shouldDisable && 'disabled' in el) (el as any).disabled = true;
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
el.classList.remove(loadingClass);
|
|
48
|
+
if (loadingCfg.html && el !== targetEl) el.innerHTML = originalHtml;
|
|
49
|
+
if (shouldDisable && 'disabled' in el) (el as any).disabled = originalDisabled;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function exec(input?: HTMLElement | string): Promise<void> {
|
|
54
|
+
if (destroyed) return;
|
|
55
|
+
|
|
56
|
+
if (!targetEl) return;
|
|
57
|
+
|
|
58
|
+
const ctxEl = input instanceof HTMLElement ? input : firstTrigger;
|
|
59
|
+
const ctx: SwapContext = {
|
|
60
|
+
triggerElement: ctxEl,
|
|
61
|
+
triggerDataset: ctxEl?.dataset ?? ({} as DOMStringMap),
|
|
62
|
+
};
|
|
63
|
+
const htmlInput = typeof input === 'string' ? input : undefined;
|
|
64
|
+
|
|
65
|
+
if (opts.onBefore?.(ctx) === false) return;
|
|
66
|
+
|
|
67
|
+
const restoreLoading = applyLoading(ctxEl);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
let html: string;
|
|
71
|
+
let res: Response | undefined;
|
|
72
|
+
if (opts.action) {
|
|
73
|
+
res = await opts.action(ctx);
|
|
74
|
+
html = await res.text();
|
|
75
|
+
} else if (htmlInput !== undefined) {
|
|
76
|
+
html = htmlInput;
|
|
77
|
+
} else {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let nodes: Node[];
|
|
82
|
+
if (opts.select) {
|
|
83
|
+
nodes = parseHTML(html, opts.select) as Node[];
|
|
84
|
+
} else {
|
|
85
|
+
nodes = Array.from(parseHTML(html).childNodes);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const root = parseHTML(html).firstElementChild as HTMLElement | null;
|
|
89
|
+
const meta: SwapMeta = { root, response: res };
|
|
90
|
+
|
|
91
|
+
if (opts.onNodes) {
|
|
92
|
+
const t = opts.onNodes(nodes, meta);
|
|
93
|
+
if (Array.isArray(t)) nodes = t;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const inserted = swap(targetEl, nodes, swapMode);
|
|
97
|
+
|
|
98
|
+
opts.onSwap?.(inserted, meta);
|
|
99
|
+
opts.onAfter?.(inserted, meta, ctx);
|
|
100
|
+
} catch (err: any) {
|
|
101
|
+
opts.onError?.(err, err.status);
|
|
102
|
+
} finally {
|
|
103
|
+
restoreLoading();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handleTrigger(e: Event) {
|
|
108
|
+
if (shouldPreventDefault) e.preventDefault();
|
|
109
|
+
exec((e.currentTarget as HTMLElement) ?? undefined);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
triggerEls.forEach((el) => el.addEventListener(eventName, handleTrigger));
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
exec,
|
|
116
|
+
destroy() {
|
|
117
|
+
destroyed = true;
|
|
118
|
+
triggerEls.forEach((el) => el.removeEventListener(eventName, handleTrigger));
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Layer 1
|
|
2
|
+
export { parseHTML } from './parse';
|
|
3
|
+
export { swap } from './swap';
|
|
4
|
+
export { debounce } from './utils';
|
|
5
|
+
|
|
6
|
+
// Layer 2
|
|
7
|
+
export { createSwap } from './create-swap';
|
|
8
|
+
export type { SwapOptions, SwapInstance, SwapMeta, SwapMode, SwapContext } from './types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function parseHTML(html: string): DocumentFragment;
|
|
2
|
+
export function parseHTML(html: string, selector: string): Element[];
|
|
3
|
+
export function parseHTML(html: string, selector?: string): DocumentFragment | Element[] {
|
|
4
|
+
const tpl = document.createElement('template');
|
|
5
|
+
tpl.innerHTML = html;
|
|
6
|
+
const fragment = tpl.content;
|
|
7
|
+
|
|
8
|
+
if (selector) {
|
|
9
|
+
return Array.from(fragment.querySelectorAll(selector));
|
|
10
|
+
}
|
|
11
|
+
return fragment;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SwapResponse } from './SwapResponse';
|