@flexireact/core 3.0.1 → 3.0.2
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/dist/cli/index.js +1514 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/client/index.js +373 -0
- package/dist/core/client/index.js.map +1 -0
- package/dist/core/index.js +6415 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/server/index.js +3094 -0
- package/dist/core/server/index.js.map +1 -0
- package/package.json +80 -80
- package/bin/flexireact.js +0 -23
- package/cli/generators.ts +0 -616
- package/cli/index.ts +0 -1182
- package/core/actions/index.ts +0 -364
- package/core/api.ts +0 -143
- package/core/build/index.ts +0 -425
- package/core/cli/logger.ts +0 -353
- package/core/client/Link.tsx +0 -345
- package/core/client/hydration.ts +0 -147
- package/core/client/index.ts +0 -12
- package/core/client/islands.ts +0 -143
- package/core/client/navigation.ts +0 -212
- package/core/client/runtime.ts +0 -52
- package/core/config.ts +0 -116
- package/core/context.ts +0 -83
- package/core/dev.ts +0 -47
- package/core/devtools/index.ts +0 -644
- package/core/edge/cache.ts +0 -344
- package/core/edge/fetch-polyfill.ts +0 -247
- package/core/edge/handler.ts +0 -248
- package/core/edge/index.ts +0 -81
- package/core/edge/ppr.ts +0 -264
- package/core/edge/runtime.ts +0 -161
- package/core/font/index.ts +0 -306
- package/core/helpers.ts +0 -494
- package/core/image/index.ts +0 -413
- package/core/index.ts +0 -218
- package/core/islands/index.ts +0 -293
- package/core/loader.ts +0 -111
- package/core/logger.ts +0 -242
- package/core/metadata/index.ts +0 -622
- package/core/middleware/index.ts +0 -416
- package/core/plugins/index.ts +0 -373
- package/core/render/index.ts +0 -1243
- package/core/render.ts +0 -136
- package/core/router/index.ts +0 -551
- package/core/router.ts +0 -141
- package/core/rsc/index.ts +0 -199
- package/core/server/index.ts +0 -779
- package/core/server.ts +0 -203
- package/core/ssg/index.ts +0 -346
- package/core/start-dev.ts +0 -6
- package/core/start-prod.ts +0 -6
- package/core/tsconfig.json +0 -30
- package/core/types.ts +0 -239
- package/core/utils.ts +0 -176
package/core/metadata/index.ts
DELETED
|
@@ -1,622 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FlexiReact Metadata API
|
|
3
|
-
*
|
|
4
|
-
* Complete metadata management like Next.js:
|
|
5
|
-
* - Static and dynamic metadata
|
|
6
|
-
* - Open Graph / Twitter Cards
|
|
7
|
-
* - Robots / Sitemap
|
|
8
|
-
* - JSON-LD structured data
|
|
9
|
-
* - Viewport configuration
|
|
10
|
-
* - Icons and manifest
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import React from 'react';
|
|
14
|
-
|
|
15
|
-
// Base metadata types
|
|
16
|
-
export interface Metadata {
|
|
17
|
-
// Basic
|
|
18
|
-
title?: string | { default: string; template?: string; absolute?: string };
|
|
19
|
-
description?: string;
|
|
20
|
-
keywords?: string | string[];
|
|
21
|
-
authors?: Author | Author[];
|
|
22
|
-
creator?: string;
|
|
23
|
-
publisher?: string;
|
|
24
|
-
|
|
25
|
-
// Robots
|
|
26
|
-
robots?: Robots | string;
|
|
27
|
-
|
|
28
|
-
// Icons
|
|
29
|
-
icons?: Icons;
|
|
30
|
-
|
|
31
|
-
// Manifest
|
|
32
|
-
manifest?: string;
|
|
33
|
-
|
|
34
|
-
// Open Graph
|
|
35
|
-
openGraph?: OpenGraph;
|
|
36
|
-
|
|
37
|
-
// Twitter
|
|
38
|
-
twitter?: Twitter;
|
|
39
|
-
|
|
40
|
-
// Verification
|
|
41
|
-
verification?: Verification;
|
|
42
|
-
|
|
43
|
-
// Alternates
|
|
44
|
-
alternates?: Alternates;
|
|
45
|
-
|
|
46
|
-
// App Links
|
|
47
|
-
appLinks?: AppLinks;
|
|
48
|
-
|
|
49
|
-
// Archives
|
|
50
|
-
archives?: string | string[];
|
|
51
|
-
|
|
52
|
-
// Assets
|
|
53
|
-
assets?: string | string[];
|
|
54
|
-
|
|
55
|
-
// Bookmarks
|
|
56
|
-
bookmarks?: string | string[];
|
|
57
|
-
|
|
58
|
-
// Category
|
|
59
|
-
category?: string;
|
|
60
|
-
|
|
61
|
-
// Classification
|
|
62
|
-
classification?: string;
|
|
63
|
-
|
|
64
|
-
// Other
|
|
65
|
-
other?: Record<string, string | string[]>;
|
|
66
|
-
|
|
67
|
-
// Viewport
|
|
68
|
-
viewport?: Viewport | string;
|
|
69
|
-
|
|
70
|
-
// Theme Color
|
|
71
|
-
themeColor?: ThemeColor | ThemeColor[];
|
|
72
|
-
|
|
73
|
-
// Color Scheme
|
|
74
|
-
colorScheme?: 'normal' | 'light' | 'dark' | 'light dark' | 'dark light';
|
|
75
|
-
|
|
76
|
-
// Format Detection
|
|
77
|
-
formatDetection?: FormatDetection;
|
|
78
|
-
|
|
79
|
-
// Base URL
|
|
80
|
-
metadataBase?: URL | string;
|
|
81
|
-
|
|
82
|
-
// Generator
|
|
83
|
-
generator?: string;
|
|
84
|
-
|
|
85
|
-
// Application Name
|
|
86
|
-
applicationName?: string;
|
|
87
|
-
|
|
88
|
-
// Referrer
|
|
89
|
-
referrer?: 'no-referrer' | 'origin' | 'no-referrer-when-downgrade' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface Author {
|
|
93
|
-
name?: string;
|
|
94
|
-
url?: string;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface Robots {
|
|
98
|
-
index?: boolean;
|
|
99
|
-
follow?: boolean;
|
|
100
|
-
noarchive?: boolean;
|
|
101
|
-
nosnippet?: boolean;
|
|
102
|
-
noimageindex?: boolean;
|
|
103
|
-
nocache?: boolean;
|
|
104
|
-
googleBot?: Robots | string;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface Icons {
|
|
108
|
-
icon?: IconDescriptor | IconDescriptor[];
|
|
109
|
-
shortcut?: IconDescriptor | IconDescriptor[];
|
|
110
|
-
apple?: IconDescriptor | IconDescriptor[];
|
|
111
|
-
other?: IconDescriptor[];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface IconDescriptor {
|
|
115
|
-
url: string;
|
|
116
|
-
type?: string;
|
|
117
|
-
sizes?: string;
|
|
118
|
-
color?: string;
|
|
119
|
-
rel?: string;
|
|
120
|
-
media?: string;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface OpenGraph {
|
|
124
|
-
type?: 'website' | 'article' | 'book' | 'profile' | 'music.song' | 'music.album' | 'music.playlist' | 'music.radio_station' | 'video.movie' | 'video.episode' | 'video.tv_show' | 'video.other';
|
|
125
|
-
url?: string;
|
|
126
|
-
title?: string;
|
|
127
|
-
description?: string;
|
|
128
|
-
siteName?: string;
|
|
129
|
-
locale?: string;
|
|
130
|
-
images?: OGImage | OGImage[];
|
|
131
|
-
videos?: OGVideo | OGVideo[];
|
|
132
|
-
audio?: OGAudio | OGAudio[];
|
|
133
|
-
determiner?: 'a' | 'an' | 'the' | 'auto' | '';
|
|
134
|
-
|
|
135
|
-
// Article specific
|
|
136
|
-
publishedTime?: string;
|
|
137
|
-
modifiedTime?: string;
|
|
138
|
-
expirationTime?: string;
|
|
139
|
-
authors?: string | string[];
|
|
140
|
-
section?: string;
|
|
141
|
-
tags?: string[];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export interface OGImage {
|
|
145
|
-
url: string;
|
|
146
|
-
secureUrl?: string;
|
|
147
|
-
type?: string;
|
|
148
|
-
width?: number;
|
|
149
|
-
height?: number;
|
|
150
|
-
alt?: string;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export interface OGVideo {
|
|
154
|
-
url: string;
|
|
155
|
-
secureUrl?: string;
|
|
156
|
-
type?: string;
|
|
157
|
-
width?: number;
|
|
158
|
-
height?: number;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export interface OGAudio {
|
|
162
|
-
url: string;
|
|
163
|
-
secureUrl?: string;
|
|
164
|
-
type?: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export interface Twitter {
|
|
168
|
-
card?: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
169
|
-
site?: string;
|
|
170
|
-
siteId?: string;
|
|
171
|
-
creator?: string;
|
|
172
|
-
creatorId?: string;
|
|
173
|
-
title?: string;
|
|
174
|
-
description?: string;
|
|
175
|
-
images?: string | TwitterImage | (string | TwitterImage)[];
|
|
176
|
-
app?: TwitterApp;
|
|
177
|
-
player?: TwitterPlayer;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export interface TwitterImage {
|
|
181
|
-
url: string;
|
|
182
|
-
alt?: string;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export interface TwitterApp {
|
|
186
|
-
id?: { iphone?: string; ipad?: string; googleplay?: string };
|
|
187
|
-
name?: string;
|
|
188
|
-
url?: { iphone?: string; ipad?: string; googleplay?: string };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export interface TwitterPlayer {
|
|
192
|
-
url: string;
|
|
193
|
-
width?: number;
|
|
194
|
-
height?: number;
|
|
195
|
-
stream?: string;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export interface Verification {
|
|
199
|
-
google?: string | string[];
|
|
200
|
-
yahoo?: string | string[];
|
|
201
|
-
yandex?: string | string[];
|
|
202
|
-
me?: string | string[];
|
|
203
|
-
other?: Record<string, string | string[]>;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export interface Alternates {
|
|
207
|
-
canonical?: string;
|
|
208
|
-
languages?: Record<string, string>;
|
|
209
|
-
media?: Record<string, string>;
|
|
210
|
-
types?: Record<string, string>;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export interface AppLinks {
|
|
214
|
-
ios?: AppLink | AppLink[];
|
|
215
|
-
iphone?: AppLink | AppLink[];
|
|
216
|
-
ipad?: AppLink | AppLink[];
|
|
217
|
-
android?: AppLink | AppLink[];
|
|
218
|
-
windows_phone?: AppLink | AppLink[];
|
|
219
|
-
windows?: AppLink | AppLink[];
|
|
220
|
-
windows_universal?: AppLink | AppLink[];
|
|
221
|
-
web?: AppLink | AppLink[];
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export interface AppLink {
|
|
225
|
-
url: string;
|
|
226
|
-
app_store_id?: string;
|
|
227
|
-
app_name?: string;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export interface Viewport {
|
|
231
|
-
width?: number | 'device-width';
|
|
232
|
-
height?: number | 'device-height';
|
|
233
|
-
initialScale?: number;
|
|
234
|
-
minimumScale?: number;
|
|
235
|
-
maximumScale?: number;
|
|
236
|
-
userScalable?: boolean;
|
|
237
|
-
viewportFit?: 'auto' | 'cover' | 'contain';
|
|
238
|
-
interactiveWidget?: 'resizes-visual' | 'resizes-content' | 'overlays-content';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export interface ThemeColor {
|
|
242
|
-
color: string;
|
|
243
|
-
media?: string;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export interface FormatDetection {
|
|
247
|
-
telephone?: boolean;
|
|
248
|
-
date?: boolean;
|
|
249
|
-
address?: boolean;
|
|
250
|
-
email?: boolean;
|
|
251
|
-
url?: boolean;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Generate HTML head tags from metadata
|
|
255
|
-
export function generateMetadataTags(metadata: Metadata, baseUrl?: string): string {
|
|
256
|
-
const tags: string[] = [];
|
|
257
|
-
const base = baseUrl || metadata.metadataBase?.toString() || '';
|
|
258
|
-
|
|
259
|
-
// Title
|
|
260
|
-
if (metadata.title) {
|
|
261
|
-
const title = typeof metadata.title === 'string'
|
|
262
|
-
? metadata.title
|
|
263
|
-
: metadata.title.absolute || (metadata.title.template
|
|
264
|
-
? metadata.title.template.replace('%s', metadata.title.default)
|
|
265
|
-
: metadata.title.default);
|
|
266
|
-
tags.push(`<title>${escapeHtml(title)}</title>`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Description
|
|
270
|
-
if (metadata.description) {
|
|
271
|
-
tags.push(`<meta name="description" content="${escapeHtml(metadata.description)}">`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Keywords
|
|
275
|
-
if (metadata.keywords) {
|
|
276
|
-
const keywords = Array.isArray(metadata.keywords) ? metadata.keywords.join(', ') : metadata.keywords;
|
|
277
|
-
tags.push(`<meta name="keywords" content="${escapeHtml(keywords)}">`);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Authors
|
|
281
|
-
if (metadata.authors) {
|
|
282
|
-
const authors = Array.isArray(metadata.authors) ? metadata.authors : [metadata.authors];
|
|
283
|
-
authors.forEach(author => {
|
|
284
|
-
if (author.name) tags.push(`<meta name="author" content="${escapeHtml(author.name)}">`);
|
|
285
|
-
if (author.url) tags.push(`<link rel="author" href="${author.url}">`);
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Generator
|
|
290
|
-
if (metadata.generator) {
|
|
291
|
-
tags.push(`<meta name="generator" content="${escapeHtml(metadata.generator)}">`);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Application Name
|
|
295
|
-
if (metadata.applicationName) {
|
|
296
|
-
tags.push(`<meta name="application-name" content="${escapeHtml(metadata.applicationName)}">`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Referrer
|
|
300
|
-
if (metadata.referrer) {
|
|
301
|
-
tags.push(`<meta name="referrer" content="${metadata.referrer}">`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Robots
|
|
305
|
-
if (metadata.robots) {
|
|
306
|
-
if (typeof metadata.robots === 'string') {
|
|
307
|
-
tags.push(`<meta name="robots" content="${metadata.robots}">`);
|
|
308
|
-
} else {
|
|
309
|
-
const robotsContent = generateRobotsContent(metadata.robots);
|
|
310
|
-
tags.push(`<meta name="robots" content="${robotsContent}">`);
|
|
311
|
-
if (metadata.robots.googleBot) {
|
|
312
|
-
const googleBotContent = typeof metadata.robots.googleBot === 'string'
|
|
313
|
-
? metadata.robots.googleBot
|
|
314
|
-
: generateRobotsContent(metadata.robots.googleBot);
|
|
315
|
-
tags.push(`<meta name="googlebot" content="${googleBotContent}">`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Viewport
|
|
321
|
-
if (metadata.viewport) {
|
|
322
|
-
const viewportContent = typeof metadata.viewport === 'string'
|
|
323
|
-
? metadata.viewport
|
|
324
|
-
: generateViewportContent(metadata.viewport);
|
|
325
|
-
tags.push(`<meta name="viewport" content="${viewportContent}">`);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Theme Color
|
|
329
|
-
if (metadata.themeColor) {
|
|
330
|
-
const themeColors = Array.isArray(metadata.themeColor) ? metadata.themeColor : [metadata.themeColor];
|
|
331
|
-
themeColors.forEach(tc => {
|
|
332
|
-
if (typeof tc === 'string') {
|
|
333
|
-
tags.push(`<meta name="theme-color" content="${tc}">`);
|
|
334
|
-
} else {
|
|
335
|
-
const mediaAttr = tc.media ? ` media="${tc.media}"` : '';
|
|
336
|
-
tags.push(`<meta name="theme-color" content="${tc.color}"${mediaAttr}>`);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Color Scheme
|
|
342
|
-
if (metadata.colorScheme) {
|
|
343
|
-
tags.push(`<meta name="color-scheme" content="${metadata.colorScheme}">`);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Format Detection
|
|
347
|
-
if (metadata.formatDetection) {
|
|
348
|
-
const fd = metadata.formatDetection;
|
|
349
|
-
const parts: string[] = [];
|
|
350
|
-
if (fd.telephone === false) parts.push('telephone=no');
|
|
351
|
-
if (fd.date === false) parts.push('date=no');
|
|
352
|
-
if (fd.address === false) parts.push('address=no');
|
|
353
|
-
if (fd.email === false) parts.push('email=no');
|
|
354
|
-
if (parts.length > 0) {
|
|
355
|
-
tags.push(`<meta name="format-detection" content="${parts.join(', ')}">`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Icons
|
|
360
|
-
if (metadata.icons) {
|
|
361
|
-
const addIcon = (icon: IconDescriptor, defaultRel: string) => {
|
|
362
|
-
const rel = icon.rel || defaultRel;
|
|
363
|
-
const type = icon.type ? ` type="${icon.type}"` : '';
|
|
364
|
-
const sizes = icon.sizes ? ` sizes="${icon.sizes}"` : '';
|
|
365
|
-
const color = icon.color ? ` color="${icon.color}"` : '';
|
|
366
|
-
const media = icon.media ? ` media="${icon.media}"` : '';
|
|
367
|
-
tags.push(`<link rel="${rel}" href="${resolveUrl(icon.url, base)}"${type}${sizes}${color}${media}>`);
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
if (metadata.icons.icon) {
|
|
371
|
-
const icons = Array.isArray(metadata.icons.icon) ? metadata.icons.icon : [metadata.icons.icon];
|
|
372
|
-
icons.forEach(icon => addIcon(icon, 'icon'));
|
|
373
|
-
}
|
|
374
|
-
if (metadata.icons.shortcut) {
|
|
375
|
-
const icons = Array.isArray(metadata.icons.shortcut) ? metadata.icons.shortcut : [metadata.icons.shortcut];
|
|
376
|
-
icons.forEach(icon => addIcon(icon, 'shortcut icon'));
|
|
377
|
-
}
|
|
378
|
-
if (metadata.icons.apple) {
|
|
379
|
-
const icons = Array.isArray(metadata.icons.apple) ? metadata.icons.apple : [metadata.icons.apple];
|
|
380
|
-
icons.forEach(icon => addIcon(icon, 'apple-touch-icon'));
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Manifest
|
|
385
|
-
if (metadata.manifest) {
|
|
386
|
-
tags.push(`<link rel="manifest" href="${resolveUrl(metadata.manifest, base)}">`);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Open Graph
|
|
390
|
-
if (metadata.openGraph) {
|
|
391
|
-
const og = metadata.openGraph;
|
|
392
|
-
if (og.type) tags.push(`<meta property="og:type" content="${og.type}">`);
|
|
393
|
-
if (og.title) tags.push(`<meta property="og:title" content="${escapeHtml(og.title)}">`);
|
|
394
|
-
if (og.description) tags.push(`<meta property="og:description" content="${escapeHtml(og.description)}">`);
|
|
395
|
-
if (og.url) tags.push(`<meta property="og:url" content="${resolveUrl(og.url, base)}">`);
|
|
396
|
-
if (og.siteName) tags.push(`<meta property="og:site_name" content="${escapeHtml(og.siteName)}">`);
|
|
397
|
-
if (og.locale) tags.push(`<meta property="og:locale" content="${og.locale}">`);
|
|
398
|
-
if (og.determiner) tags.push(`<meta property="og:determiner" content="${og.determiner}">`);
|
|
399
|
-
|
|
400
|
-
// Images
|
|
401
|
-
if (og.images) {
|
|
402
|
-
const images = Array.isArray(og.images) ? og.images : [og.images];
|
|
403
|
-
images.forEach(img => {
|
|
404
|
-
tags.push(`<meta property="og:image" content="${resolveUrl(img.url, base)}">`);
|
|
405
|
-
if (img.secureUrl) tags.push(`<meta property="og:image:secure_url" content="${img.secureUrl}">`);
|
|
406
|
-
if (img.type) tags.push(`<meta property="og:image:type" content="${img.type}">`);
|
|
407
|
-
if (img.width) tags.push(`<meta property="og:image:width" content="${img.width}">`);
|
|
408
|
-
if (img.height) tags.push(`<meta property="og:image:height" content="${img.height}">`);
|
|
409
|
-
if (img.alt) tags.push(`<meta property="og:image:alt" content="${escapeHtml(img.alt)}">`);
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Article specific
|
|
414
|
-
if (og.type === 'article') {
|
|
415
|
-
if (og.publishedTime) tags.push(`<meta property="article:published_time" content="${og.publishedTime}">`);
|
|
416
|
-
if (og.modifiedTime) tags.push(`<meta property="article:modified_time" content="${og.modifiedTime}">`);
|
|
417
|
-
if (og.expirationTime) tags.push(`<meta property="article:expiration_time" content="${og.expirationTime}">`);
|
|
418
|
-
if (og.section) tags.push(`<meta property="article:section" content="${escapeHtml(og.section)}">`);
|
|
419
|
-
if (og.tags) {
|
|
420
|
-
og.tags.forEach(tag => tags.push(`<meta property="article:tag" content="${escapeHtml(tag)}">`));
|
|
421
|
-
}
|
|
422
|
-
if (og.authors) {
|
|
423
|
-
const authors = Array.isArray(og.authors) ? og.authors : [og.authors];
|
|
424
|
-
authors.forEach(author => tags.push(`<meta property="article:author" content="${escapeHtml(author)}">`));
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Twitter
|
|
430
|
-
if (metadata.twitter) {
|
|
431
|
-
const tw = metadata.twitter;
|
|
432
|
-
if (tw.card) tags.push(`<meta name="twitter:card" content="${tw.card}">`);
|
|
433
|
-
if (tw.site) tags.push(`<meta name="twitter:site" content="${tw.site}">`);
|
|
434
|
-
if (tw.siteId) tags.push(`<meta name="twitter:site:id" content="${tw.siteId}">`);
|
|
435
|
-
if (tw.creator) tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
|
|
436
|
-
if (tw.creatorId) tags.push(`<meta name="twitter:creator:id" content="${tw.creatorId}">`);
|
|
437
|
-
if (tw.title) tags.push(`<meta name="twitter:title" content="${escapeHtml(tw.title)}">`);
|
|
438
|
-
if (tw.description) tags.push(`<meta name="twitter:description" content="${escapeHtml(tw.description)}">`);
|
|
439
|
-
|
|
440
|
-
if (tw.images) {
|
|
441
|
-
const images = Array.isArray(tw.images) ? tw.images : [tw.images];
|
|
442
|
-
images.forEach(img => {
|
|
443
|
-
const url = typeof img === 'string' ? img : img.url;
|
|
444
|
-
tags.push(`<meta name="twitter:image" content="${resolveUrl(url, base)}">`);
|
|
445
|
-
if (typeof img !== 'string' && img.alt) {
|
|
446
|
-
tags.push(`<meta name="twitter:image:alt" content="${escapeHtml(img.alt)}">`);
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Verification
|
|
453
|
-
if (metadata.verification) {
|
|
454
|
-
const v = metadata.verification;
|
|
455
|
-
if (v.google) {
|
|
456
|
-
const values = Array.isArray(v.google) ? v.google : [v.google];
|
|
457
|
-
values.forEach(val => tags.push(`<meta name="google-site-verification" content="${val}">`));
|
|
458
|
-
}
|
|
459
|
-
if (v.yandex) {
|
|
460
|
-
const values = Array.isArray(v.yandex) ? v.yandex : [v.yandex];
|
|
461
|
-
values.forEach(val => tags.push(`<meta name="yandex-verification" content="${val}">`));
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Alternates
|
|
466
|
-
if (metadata.alternates) {
|
|
467
|
-
const alt = metadata.alternates;
|
|
468
|
-
if (alt.canonical) {
|
|
469
|
-
tags.push(`<link rel="canonical" href="${resolveUrl(alt.canonical, base)}">`);
|
|
470
|
-
}
|
|
471
|
-
if (alt.languages) {
|
|
472
|
-
Object.entries(alt.languages).forEach(([lang, url]) => {
|
|
473
|
-
tags.push(`<link rel="alternate" hreflang="${lang}" href="${resolveUrl(url, base)}">`);
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return tags.join('\n ');
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Helper functions
|
|
482
|
-
function escapeHtml(str: string): string {
|
|
483
|
-
return str
|
|
484
|
-
.replace(/&/g, '&')
|
|
485
|
-
.replace(/</g, '<')
|
|
486
|
-
.replace(/>/g, '>')
|
|
487
|
-
.replace(/"/g, '"')
|
|
488
|
-
.replace(/'/g, ''');
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function resolveUrl(url: string, base: string): string {
|
|
492
|
-
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
|
|
493
|
-
return url;
|
|
494
|
-
}
|
|
495
|
-
return base ? `${base.replace(/\/$/, '')}${url.startsWith('/') ? '' : '/'}${url}` : url;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function generateRobotsContent(robots: Robots): string {
|
|
499
|
-
const parts: string[] = [];
|
|
500
|
-
if (robots.index !== undefined) parts.push(robots.index ? 'index' : 'noindex');
|
|
501
|
-
if (robots.follow !== undefined) parts.push(robots.follow ? 'follow' : 'nofollow');
|
|
502
|
-
if (robots.noarchive) parts.push('noarchive');
|
|
503
|
-
if (robots.nosnippet) parts.push('nosnippet');
|
|
504
|
-
if (robots.noimageindex) parts.push('noimageindex');
|
|
505
|
-
if (robots.nocache) parts.push('nocache');
|
|
506
|
-
return parts.join(', ') || 'index, follow';
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function generateViewportContent(viewport: Viewport): string {
|
|
510
|
-
const parts: string[] = [];
|
|
511
|
-
if (viewport.width) parts.push(`width=${viewport.width}`);
|
|
512
|
-
if (viewport.height) parts.push(`height=${viewport.height}`);
|
|
513
|
-
if (viewport.initialScale !== undefined) parts.push(`initial-scale=${viewport.initialScale}`);
|
|
514
|
-
if (viewport.minimumScale !== undefined) parts.push(`minimum-scale=${viewport.minimumScale}`);
|
|
515
|
-
if (viewport.maximumScale !== undefined) parts.push(`maximum-scale=${viewport.maximumScale}`);
|
|
516
|
-
if (viewport.userScalable !== undefined) parts.push(`user-scalable=${viewport.userScalable ? 'yes' : 'no'}`);
|
|
517
|
-
if (viewport.viewportFit) parts.push(`viewport-fit=${viewport.viewportFit}`);
|
|
518
|
-
return parts.join(', ') || 'width=device-width, initial-scale=1';
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Merge metadata (child overrides parent)
|
|
522
|
-
export function mergeMetadata(parent: Metadata, child: Metadata): Metadata {
|
|
523
|
-
return {
|
|
524
|
-
...parent,
|
|
525
|
-
...child,
|
|
526
|
-
// Deep merge for nested objects
|
|
527
|
-
openGraph: child.openGraph ? { ...parent.openGraph, ...child.openGraph } : parent.openGraph,
|
|
528
|
-
twitter: child.twitter ? { ...parent.twitter, ...child.twitter } : parent.twitter,
|
|
529
|
-
icons: child.icons ? { ...parent.icons, ...child.icons } : parent.icons,
|
|
530
|
-
verification: child.verification ? { ...parent.verification, ...child.verification } : parent.verification,
|
|
531
|
-
alternates: child.alternates ? { ...parent.alternates, ...child.alternates } : parent.alternates
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Generate JSON-LD structured data
|
|
536
|
-
export function generateJsonLd(data: Record<string, any>): string {
|
|
537
|
-
return `<script type="application/ld+json">${JSON.stringify(data)}</script>`;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Common JSON-LD schemas
|
|
541
|
-
export const jsonLd = {
|
|
542
|
-
website: (config: { name: string; url: string; description?: string }) => ({
|
|
543
|
-
'@context': 'https://schema.org',
|
|
544
|
-
'@type': 'WebSite',
|
|
545
|
-
name: config.name,
|
|
546
|
-
url: config.url,
|
|
547
|
-
description: config.description
|
|
548
|
-
}),
|
|
549
|
-
|
|
550
|
-
article: (config: {
|
|
551
|
-
headline: string;
|
|
552
|
-
description?: string;
|
|
553
|
-
image?: string | string[];
|
|
554
|
-
datePublished: string;
|
|
555
|
-
dateModified?: string;
|
|
556
|
-
author: { name: string; url?: string } | { name: string; url?: string }[];
|
|
557
|
-
}) => ({
|
|
558
|
-
'@context': 'https://schema.org',
|
|
559
|
-
'@type': 'Article',
|
|
560
|
-
headline: config.headline,
|
|
561
|
-
description: config.description,
|
|
562
|
-
image: config.image,
|
|
563
|
-
datePublished: config.datePublished,
|
|
564
|
-
dateModified: config.dateModified || config.datePublished,
|
|
565
|
-
author: Array.isArray(config.author)
|
|
566
|
-
? config.author.map(a => ({ '@type': 'Person', ...a }))
|
|
567
|
-
: { '@type': 'Person', ...config.author }
|
|
568
|
-
}),
|
|
569
|
-
|
|
570
|
-
organization: (config: {
|
|
571
|
-
name: string;
|
|
572
|
-
url: string;
|
|
573
|
-
logo?: string;
|
|
574
|
-
sameAs?: string[];
|
|
575
|
-
}) => ({
|
|
576
|
-
'@context': 'https://schema.org',
|
|
577
|
-
'@type': 'Organization',
|
|
578
|
-
name: config.name,
|
|
579
|
-
url: config.url,
|
|
580
|
-
logo: config.logo,
|
|
581
|
-
sameAs: config.sameAs
|
|
582
|
-
}),
|
|
583
|
-
|
|
584
|
-
product: (config: {
|
|
585
|
-
name: string;
|
|
586
|
-
description?: string;
|
|
587
|
-
image?: string | string[];
|
|
588
|
-
brand?: string;
|
|
589
|
-
offers?: { price: number; priceCurrency: string; availability?: string };
|
|
590
|
-
}) => ({
|
|
591
|
-
'@context': 'https://schema.org',
|
|
592
|
-
'@type': 'Product',
|
|
593
|
-
name: config.name,
|
|
594
|
-
description: config.description,
|
|
595
|
-
image: config.image,
|
|
596
|
-
brand: config.brand ? { '@type': 'Brand', name: config.brand } : undefined,
|
|
597
|
-
offers: config.offers ? {
|
|
598
|
-
'@type': 'Offer',
|
|
599
|
-
price: config.offers.price,
|
|
600
|
-
priceCurrency: config.offers.priceCurrency,
|
|
601
|
-
availability: config.offers.availability || 'https://schema.org/InStock'
|
|
602
|
-
} : undefined
|
|
603
|
-
}),
|
|
604
|
-
|
|
605
|
-
breadcrumb: (items: { name: string; url: string }[]) => ({
|
|
606
|
-
'@context': 'https://schema.org',
|
|
607
|
-
'@type': 'BreadcrumbList',
|
|
608
|
-
itemListElement: items.map((item, index) => ({
|
|
609
|
-
'@type': 'ListItem',
|
|
610
|
-
position: index + 1,
|
|
611
|
-
name: item.name,
|
|
612
|
-
item: item.url
|
|
613
|
-
}))
|
|
614
|
-
})
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
export default {
|
|
618
|
-
generateMetadataTags,
|
|
619
|
-
mergeMetadata,
|
|
620
|
-
generateJsonLd,
|
|
621
|
-
jsonLd
|
|
622
|
-
};
|