@flightdev/seo 0.2.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.
@@ -0,0 +1,1293 @@
1
+ import { reactive, provide, inject, computed, watch, onMounted, onUnmounted } from 'vue';
2
+
3
+ // src/vue/index.ts
4
+
5
+ // src/head.ts
6
+ var VOID_TAGS = /* @__PURE__ */ new Set(["meta", "link", "base"]);
7
+ var BOOLEAN_ATTRS = /* @__PURE__ */ new Set(["async", "defer", "nomodule", "crossorigin"]);
8
+ function escapeHtml(str) {
9
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
10
+ }
11
+ function escapeAttr(str) {
12
+ return str.replace(/"/g, "&quot;");
13
+ }
14
+ function renderTag(tag) {
15
+ const parts = [`<${tag.tag}`];
16
+ if (tag.attrs) {
17
+ for (const [key, value] of Object.entries(tag.attrs)) {
18
+ if (value === void 0 || value === false) {
19
+ continue;
20
+ }
21
+ if (value === true) {
22
+ parts.push(` ${key}`);
23
+ } else if (BOOLEAN_ATTRS.has(key) && value === "") {
24
+ parts.push(` ${key}`);
25
+ } else {
26
+ parts.push(` ${key}="${escapeAttr(String(value))}"`);
27
+ }
28
+ }
29
+ }
30
+ if (VOID_TAGS.has(tag.tag)) {
31
+ parts.push(" />");
32
+ } else if (tag.content !== void 0) {
33
+ const isJsonLd = tag.tag === "script" && tag.attrs?.type === "application/ld+json";
34
+ const content = isJsonLd ? tag.content : escapeHtml(tag.content);
35
+ parts.push(`>${content}</${tag.tag}>`);
36
+ } else {
37
+ parts.push(`></${tag.tag}>`);
38
+ }
39
+ return parts.join("");
40
+ }
41
+ function renderTags(tags) {
42
+ return tags.map((tag) => renderTag(tag)).join("\n");
43
+ }
44
+
45
+ // src/meta.ts
46
+ function generateTitleTag(title, template, defaultTitle) {
47
+ let resolvedTitle = title || defaultTitle;
48
+ if (!resolvedTitle) {
49
+ return null;
50
+ }
51
+ if (template && title) {
52
+ resolvedTitle = template.replace("%s", title);
53
+ }
54
+ return {
55
+ tag: "title",
56
+ content: resolvedTitle,
57
+ key: "title"
58
+ };
59
+ }
60
+ function generateCharsetTag(charset = "utf-8") {
61
+ return {
62
+ tag: "meta",
63
+ attrs: { charset },
64
+ key: "meta:charset"
65
+ };
66
+ }
67
+ function generateViewportTag(viewport) {
68
+ if (!viewport) {
69
+ return null;
70
+ }
71
+ let content;
72
+ if (typeof viewport === "string") {
73
+ content = viewport;
74
+ } else {
75
+ const parts = [];
76
+ if (viewport.width !== void 0) {
77
+ parts.push(`width=${viewport.width}`);
78
+ }
79
+ if (viewport.height !== void 0) {
80
+ parts.push(`height=${viewport.height}`);
81
+ }
82
+ if (viewport.initialScale !== void 0) {
83
+ parts.push(`initial-scale=${viewport.initialScale}`);
84
+ }
85
+ if (viewport.minimumScale !== void 0) {
86
+ parts.push(`minimum-scale=${viewport.minimumScale}`);
87
+ }
88
+ if (viewport.maximumScale !== void 0) {
89
+ parts.push(`maximum-scale=${viewport.maximumScale}`);
90
+ }
91
+ if (viewport.userScalable !== void 0) {
92
+ parts.push(`user-scalable=${viewport.userScalable ? "yes" : "no"}`);
93
+ }
94
+ if (viewport.viewportFit !== void 0) {
95
+ parts.push(`viewport-fit=${viewport.viewportFit}`);
96
+ }
97
+ content = parts.join(", ");
98
+ }
99
+ if (!content) {
100
+ return null;
101
+ }
102
+ return {
103
+ tag: "meta",
104
+ attrs: { name: "viewport", content },
105
+ key: "meta:name:viewport"
106
+ };
107
+ }
108
+ function generateDescriptionTag(description) {
109
+ if (!description) {
110
+ return null;
111
+ }
112
+ return {
113
+ tag: "meta",
114
+ attrs: { name: "description", content: description },
115
+ key: "meta:name:description"
116
+ };
117
+ }
118
+ function generateKeywordsTag(keywords) {
119
+ if (!keywords) {
120
+ return null;
121
+ }
122
+ const content = Array.isArray(keywords) ? keywords.join(", ") : keywords;
123
+ return {
124
+ tag: "meta",
125
+ attrs: { name: "keywords", content },
126
+ key: "meta:name:keywords"
127
+ };
128
+ }
129
+ function generateAuthorTag(author) {
130
+ if (!author) {
131
+ return null;
132
+ }
133
+ return {
134
+ tag: "meta",
135
+ attrs: { name: "author", content: author },
136
+ key: "meta:name:author"
137
+ };
138
+ }
139
+ function generateGeneratorTag(generator) {
140
+ if (!generator) {
141
+ return null;
142
+ }
143
+ return {
144
+ tag: "meta",
145
+ attrs: { name: "generator", content: generator },
146
+ key: "meta:name:generator"
147
+ };
148
+ }
149
+ function generateApplicationNameTag(name) {
150
+ if (!name) {
151
+ return null;
152
+ }
153
+ return {
154
+ tag: "meta",
155
+ attrs: { name: "application-name", content: name },
156
+ key: "meta:name:application-name"
157
+ };
158
+ }
159
+ function generateReferrerTag(referrer) {
160
+ if (!referrer) {
161
+ return null;
162
+ }
163
+ return {
164
+ tag: "meta",
165
+ attrs: { name: "referrer", content: referrer },
166
+ key: "meta:name:referrer"
167
+ };
168
+ }
169
+ function generateThemeColorTags(themeColor) {
170
+ if (!themeColor) {
171
+ return [];
172
+ }
173
+ if (typeof themeColor === "string") {
174
+ return [
175
+ {
176
+ tag: "meta",
177
+ attrs: { name: "theme-color", content: themeColor },
178
+ key: "meta:name:theme-color"
179
+ }
180
+ ];
181
+ }
182
+ return themeColor.map((tc, index) => ({
183
+ tag: "meta",
184
+ attrs: {
185
+ name: "theme-color",
186
+ content: tc.color,
187
+ media: tc.media
188
+ },
189
+ key: `meta:name:theme-color:${index}`
190
+ }));
191
+ }
192
+ function generateColorSchemeTag(colorScheme) {
193
+ if (!colorScheme) {
194
+ return null;
195
+ }
196
+ return {
197
+ tag: "meta",
198
+ attrs: { name: "color-scheme", content: colorScheme },
199
+ key: "meta:name:color-scheme"
200
+ };
201
+ }
202
+ function robotsMetaToString(robots) {
203
+ const directives = [];
204
+ if (robots.index === false) {
205
+ directives.push("noindex");
206
+ } else if (robots.index === true) {
207
+ directives.push("index");
208
+ }
209
+ if (robots.follow === false) {
210
+ directives.push("nofollow");
211
+ } else if (robots.follow === true) {
212
+ directives.push("follow");
213
+ }
214
+ if (robots.noarchive) {
215
+ directives.push("noarchive");
216
+ }
217
+ if (robots.nosnippet) {
218
+ directives.push("nosnippet");
219
+ }
220
+ if (robots.notranslate) {
221
+ directives.push("notranslate");
222
+ }
223
+ if (robots.maxSnippet !== void 0) {
224
+ directives.push(`max-snippet:${robots.maxSnippet}`);
225
+ }
226
+ if (robots.maxImagePreview !== void 0) {
227
+ directives.push(`max-image-preview:${robots.maxImagePreview}`);
228
+ }
229
+ if (robots.maxVideoPreview !== void 0) {
230
+ directives.push(`max-video-preview:${robots.maxVideoPreview}`);
231
+ }
232
+ if (robots.unavailableAfter) {
233
+ directives.push(`unavailable_after:${robots.unavailableAfter}`);
234
+ }
235
+ return directives.join(", ");
236
+ }
237
+ function generateRobotsTag(robots) {
238
+ if (!robots) {
239
+ return null;
240
+ }
241
+ const content = typeof robots === "string" ? robots : robotsMetaToString(robots);
242
+ if (!content) {
243
+ return null;
244
+ }
245
+ return {
246
+ tag: "meta",
247
+ attrs: { name: "robots", content },
248
+ key: "meta:name:robots"
249
+ };
250
+ }
251
+ function generateGooglebotTag(googleBot) {
252
+ if (!googleBot) {
253
+ return null;
254
+ }
255
+ const content = typeof googleBot === "string" ? googleBot : robotsMetaToString(googleBot);
256
+ if (!content) {
257
+ return null;
258
+ }
259
+ return {
260
+ tag: "meta",
261
+ attrs: { name: "googlebot", content },
262
+ key: "meta:name:googlebot"
263
+ };
264
+ }
265
+ function generateVerificationTags(verification) {
266
+ if (!verification) {
267
+ return [];
268
+ }
269
+ const tags = [];
270
+ if (verification.google) {
271
+ tags.push({
272
+ tag: "meta",
273
+ attrs: { name: "google-site-verification", content: verification.google },
274
+ key: "meta:name:google-site-verification"
275
+ });
276
+ }
277
+ if (verification.bing) {
278
+ tags.push({
279
+ tag: "meta",
280
+ attrs: { name: "msvalidate.01", content: verification.bing },
281
+ key: "meta:name:msvalidate.01"
282
+ });
283
+ }
284
+ if (verification.yandex) {
285
+ tags.push({
286
+ tag: "meta",
287
+ attrs: { name: "yandex-verification", content: verification.yandex },
288
+ key: "meta:name:yandex-verification"
289
+ });
290
+ }
291
+ if (verification.pinterest) {
292
+ tags.push({
293
+ tag: "meta",
294
+ attrs: { name: "p:domain_verify", content: verification.pinterest },
295
+ key: "meta:name:p:domain_verify"
296
+ });
297
+ }
298
+ if (verification.norton) {
299
+ tags.push({
300
+ tag: "meta",
301
+ attrs: { name: "norton-safeweb-site-verification", content: verification.norton },
302
+ key: "meta:name:norton-safeweb-site-verification"
303
+ });
304
+ }
305
+ return tags;
306
+ }
307
+ function generateCanonicalTag(canonical) {
308
+ if (!canonical) {
309
+ return null;
310
+ }
311
+ return {
312
+ tag: "link",
313
+ attrs: { rel: "canonical", href: canonical },
314
+ key: "link:canonical"
315
+ };
316
+ }
317
+ function generateBaseTag(base) {
318
+ if (!base) {
319
+ return null;
320
+ }
321
+ return {
322
+ tag: "base",
323
+ attrs: { href: base },
324
+ key: "base"
325
+ };
326
+ }
327
+ function generateAlternateTags(alternates) {
328
+ if (!alternates || alternates.length === 0) {
329
+ return [];
330
+ }
331
+ return alternates.map((alt, index) => {
332
+ const attrs = {
333
+ rel: "alternate",
334
+ href: alt.href
335
+ };
336
+ if (alt.hreflang) {
337
+ attrs.hreflang = alt.hreflang;
338
+ }
339
+ if (alt.media) {
340
+ attrs.media = alt.media;
341
+ }
342
+ if (alt.type) {
343
+ attrs.type = alt.type;
344
+ }
345
+ if (alt.title) {
346
+ attrs.title = alt.title;
347
+ }
348
+ return {
349
+ tag: "link",
350
+ attrs,
351
+ key: `link:alternate:${alt.hreflang || index}`
352
+ };
353
+ });
354
+ }
355
+ function generateManifestTag(manifest) {
356
+ if (!manifest) {
357
+ return null;
358
+ }
359
+ return {
360
+ tag: "link",
361
+ attrs: { rel: "manifest", href: manifest },
362
+ key: "link:manifest"
363
+ };
364
+ }
365
+ function generateMetaTags(metadata, options) {
366
+ const tags = [];
367
+ tags.push(generateCharsetTag(metadata.charset));
368
+ const base = generateBaseTag(metadata.base);
369
+ if (base) tags.push(base);
370
+ const viewport = generateViewportTag(metadata.viewport);
371
+ if (viewport) tags.push(viewport);
372
+ const title = generateTitleTag(
373
+ metadata.title,
374
+ metadata.titleTemplate || options?.titleTemplate,
375
+ metadata.defaultTitle || options?.defaultTitle
376
+ );
377
+ if (title) tags.push(title);
378
+ const description = generateDescriptionTag(metadata.description);
379
+ if (description) tags.push(description);
380
+ const keywords = generateKeywordsTag(metadata.keywords);
381
+ if (keywords) tags.push(keywords);
382
+ const author = generateAuthorTag(metadata.author);
383
+ if (author) tags.push(author);
384
+ const generator = generateGeneratorTag(metadata.generator);
385
+ if (generator) tags.push(generator);
386
+ const appName = generateApplicationNameTag(metadata.applicationName);
387
+ if (appName) tags.push(appName);
388
+ const referrer = generateReferrerTag(metadata.referrer);
389
+ if (referrer) tags.push(referrer);
390
+ tags.push(...generateThemeColorTags(metadata.themeColor));
391
+ const colorScheme = generateColorSchemeTag(metadata.colorScheme);
392
+ if (colorScheme) tags.push(colorScheme);
393
+ const robots = generateRobotsTag(metadata.robots);
394
+ if (robots) tags.push(robots);
395
+ const googleBot = generateGooglebotTag(metadata.googleBot);
396
+ if (googleBot) tags.push(googleBot);
397
+ tags.push(...generateVerificationTags(metadata.verification));
398
+ const canonical = generateCanonicalTag(metadata.canonical);
399
+ if (canonical) tags.push(canonical);
400
+ tags.push(...generateAlternateTags(metadata.alternates));
401
+ const manifest = generateManifestTag(metadata.manifest);
402
+ if (manifest) tags.push(manifest);
403
+ if (metadata.other) {
404
+ for (const [name, content] of Object.entries(metadata.other)) {
405
+ tags.push({
406
+ tag: "meta",
407
+ attrs: { name, content },
408
+ key: `meta:name:${name}`
409
+ });
410
+ }
411
+ }
412
+ return tags;
413
+ }
414
+
415
+ // src/opengraph.ts
416
+ function ogTag(property, content) {
417
+ if (content === void 0 || content === null || content === "") {
418
+ return null;
419
+ }
420
+ return {
421
+ tag: "meta",
422
+ attrs: { property: `og:${property}`, content: String(content) },
423
+ key: `meta:property:og:${property}`
424
+ };
425
+ }
426
+ function articleTag(property, content) {
427
+ if (!content) {
428
+ return null;
429
+ }
430
+ return {
431
+ tag: "meta",
432
+ attrs: { property: `article:${property}`, content },
433
+ key: `meta:property:article:${property}`
434
+ };
435
+ }
436
+ function profileTag(property, content) {
437
+ if (!content) {
438
+ return null;
439
+ }
440
+ return {
441
+ tag: "meta",
442
+ attrs: { property: `profile:${property}`, content },
443
+ key: `meta:property:profile:${property}`
444
+ };
445
+ }
446
+ function bookTag(property, content) {
447
+ if (!content) {
448
+ return null;
449
+ }
450
+ return {
451
+ tag: "meta",
452
+ attrs: { property: `book:${property}`, content },
453
+ key: `meta:property:book:${property}`
454
+ };
455
+ }
456
+ function productTag(property, content) {
457
+ if (content === void 0 || content === null || content === "") {
458
+ return null;
459
+ }
460
+ return {
461
+ tag: "meta",
462
+ attrs: { property: `product:${property}`, content: String(content) },
463
+ key: `meta:property:product:${property}`
464
+ };
465
+ }
466
+ function generateImageTags(images) {
467
+ if (!images || images.length === 0) {
468
+ return [];
469
+ }
470
+ const tags = [];
471
+ images.forEach((image, index) => {
472
+ const suffix = index > 0 ? `:${index}` : "";
473
+ tags.push({
474
+ tag: "meta",
475
+ attrs: { property: "og:image", content: image.url },
476
+ key: `meta:property:og:image${suffix}`
477
+ });
478
+ if (image.secureUrl) {
479
+ tags.push({
480
+ tag: "meta",
481
+ attrs: { property: "og:image:secure_url", content: image.secureUrl },
482
+ key: `meta:property:og:image:secure_url${suffix}`
483
+ });
484
+ }
485
+ if (image.type) {
486
+ tags.push({
487
+ tag: "meta",
488
+ attrs: { property: "og:image:type", content: image.type },
489
+ key: `meta:property:og:image:type${suffix}`
490
+ });
491
+ }
492
+ if (image.width) {
493
+ tags.push({
494
+ tag: "meta",
495
+ attrs: { property: "og:image:width", content: String(image.width) },
496
+ key: `meta:property:og:image:width${suffix}`
497
+ });
498
+ }
499
+ if (image.height) {
500
+ tags.push({
501
+ tag: "meta",
502
+ attrs: { property: "og:image:height", content: String(image.height) },
503
+ key: `meta:property:og:image:height${suffix}`
504
+ });
505
+ }
506
+ if (image.alt) {
507
+ tags.push({
508
+ tag: "meta",
509
+ attrs: { property: "og:image:alt", content: image.alt },
510
+ key: `meta:property:og:image:alt${suffix}`
511
+ });
512
+ }
513
+ });
514
+ return tags;
515
+ }
516
+ function generateVideoTags(videos) {
517
+ if (!videos || videos.length === 0) {
518
+ return [];
519
+ }
520
+ const tags = [];
521
+ videos.forEach((video, index) => {
522
+ const suffix = index > 0 ? `:${index}` : "";
523
+ tags.push({
524
+ tag: "meta",
525
+ attrs: { property: "og:video", content: video.url },
526
+ key: `meta:property:og:video${suffix}`
527
+ });
528
+ if (video.secureUrl) {
529
+ tags.push({
530
+ tag: "meta",
531
+ attrs: { property: "og:video:secure_url", content: video.secureUrl },
532
+ key: `meta:property:og:video:secure_url${suffix}`
533
+ });
534
+ }
535
+ if (video.type) {
536
+ tags.push({
537
+ tag: "meta",
538
+ attrs: { property: "og:video:type", content: video.type },
539
+ key: `meta:property:og:video:type${suffix}`
540
+ });
541
+ }
542
+ if (video.width) {
543
+ tags.push({
544
+ tag: "meta",
545
+ attrs: { property: "og:video:width", content: String(video.width) },
546
+ key: `meta:property:og:video:width${suffix}`
547
+ });
548
+ }
549
+ if (video.height) {
550
+ tags.push({
551
+ tag: "meta",
552
+ attrs: { property: "og:video:height", content: String(video.height) },
553
+ key: `meta:property:og:video:height${suffix}`
554
+ });
555
+ }
556
+ });
557
+ return tags;
558
+ }
559
+ function generateAudioTags(audio) {
560
+ if (!audio || audio.length === 0) {
561
+ return [];
562
+ }
563
+ const tags = [];
564
+ audio.forEach((a, index) => {
565
+ const suffix = index > 0 ? `:${index}` : "";
566
+ tags.push({
567
+ tag: "meta",
568
+ attrs: { property: "og:audio", content: a.url },
569
+ key: `meta:property:og:audio${suffix}`
570
+ });
571
+ if (a.secureUrl) {
572
+ tags.push({
573
+ tag: "meta",
574
+ attrs: { property: "og:audio:secure_url", content: a.secureUrl },
575
+ key: `meta:property:og:audio:secure_url${suffix}`
576
+ });
577
+ }
578
+ if (a.type) {
579
+ tags.push({
580
+ tag: "meta",
581
+ attrs: { property: "og:audio:type", content: a.type },
582
+ key: `meta:property:og:audio:type${suffix}`
583
+ });
584
+ }
585
+ });
586
+ return tags;
587
+ }
588
+ function generateArticleTags(article) {
589
+ if (!article) {
590
+ return [];
591
+ }
592
+ const tags = [];
593
+ const publishedTime = articleTag("published_time", article.publishedTime);
594
+ if (publishedTime) tags.push(publishedTime);
595
+ const modifiedTime = articleTag("modified_time", article.modifiedTime);
596
+ if (modifiedTime) tags.push(modifiedTime);
597
+ const expirationTime = articleTag("expiration_time", article.expirationTime);
598
+ if (expirationTime) tags.push(expirationTime);
599
+ const section = articleTag("section", article.section);
600
+ if (section) tags.push(section);
601
+ if (article.authors) {
602
+ article.authors.forEach((author, index) => {
603
+ tags.push({
604
+ tag: "meta",
605
+ attrs: { property: "article:author", content: author },
606
+ key: `meta:property:article:author:${index}`
607
+ });
608
+ });
609
+ }
610
+ if (article.tags) {
611
+ article.tags.forEach((tag, index) => {
612
+ tags.push({
613
+ tag: "meta",
614
+ attrs: { property: "article:tag", content: tag },
615
+ key: `meta:property:article:tag:${index}`
616
+ });
617
+ });
618
+ }
619
+ return tags;
620
+ }
621
+ function generateProfileTags(profile) {
622
+ if (!profile) {
623
+ return [];
624
+ }
625
+ const tags = [];
626
+ const firstName = profileTag("first_name", profile.firstName);
627
+ if (firstName) tags.push(firstName);
628
+ const lastName = profileTag("last_name", profile.lastName);
629
+ if (lastName) tags.push(lastName);
630
+ const username = profileTag("username", profile.username);
631
+ if (username) tags.push(username);
632
+ const gender = profileTag("gender", profile.gender);
633
+ if (gender) tags.push(gender);
634
+ return tags;
635
+ }
636
+ function generateBookTags(book) {
637
+ if (!book) {
638
+ return [];
639
+ }
640
+ const tags = [];
641
+ if (book.authors) {
642
+ book.authors.forEach((author, index) => {
643
+ tags.push({
644
+ tag: "meta",
645
+ attrs: { property: "book:author", content: author },
646
+ key: `meta:property:book:author:${index}`
647
+ });
648
+ });
649
+ }
650
+ const isbn = bookTag("isbn", book.isbn);
651
+ if (isbn) tags.push(isbn);
652
+ const releaseDate = bookTag("release_date", book.releaseDate);
653
+ if (releaseDate) tags.push(releaseDate);
654
+ if (book.tags) {
655
+ book.tags.forEach((tag, index) => {
656
+ tags.push({
657
+ tag: "meta",
658
+ attrs: { property: "book:tag", content: tag },
659
+ key: `meta:property:book:tag:${index}`
660
+ });
661
+ });
662
+ }
663
+ return tags;
664
+ }
665
+ function generateProductTags(product) {
666
+ if (!product) {
667
+ return [];
668
+ }
669
+ const tags = [];
670
+ const price = productTag("price:amount", product.priceAmount);
671
+ if (price) tags.push(price);
672
+ const currency = productTag("price:currency", product.priceCurrency);
673
+ if (currency) tags.push(currency);
674
+ const availability = productTag("availability", product.availability);
675
+ if (availability) tags.push(availability);
676
+ const condition = productTag("condition", product.condition);
677
+ if (condition) tags.push(condition);
678
+ const retailerId = productTag("retailer_item_id", product.retailerItemId);
679
+ if (retailerId) tags.push(retailerId);
680
+ return tags;
681
+ }
682
+ function generateOpenGraphTags(og) {
683
+ if (!og) {
684
+ return [];
685
+ }
686
+ const tags = [];
687
+ const type = ogTag("type", og.type || "website");
688
+ if (type) tags.push(type);
689
+ const title = ogTag("title", og.title);
690
+ if (title) tags.push(title);
691
+ const description = ogTag("description", og.description);
692
+ if (description) tags.push(description);
693
+ const url = ogTag("url", og.url);
694
+ if (url) tags.push(url);
695
+ const siteName = ogTag("site_name", og.siteName);
696
+ if (siteName) tags.push(siteName);
697
+ const locale = ogTag("locale", og.locale);
698
+ if (locale) tags.push(locale);
699
+ if (og.alternateLocales) {
700
+ og.alternateLocales.forEach((alternateLocale, index) => {
701
+ tags.push({
702
+ tag: "meta",
703
+ attrs: { property: "og:locale:alternate", content: alternateLocale },
704
+ key: `meta:property:og:locale:alternate:${index}`
705
+ });
706
+ });
707
+ }
708
+ const determiner = ogTag("determiner", og.determiner);
709
+ if (determiner) tags.push(determiner);
710
+ tags.push(...generateImageTags(og.images));
711
+ tags.push(...generateVideoTags(og.videos));
712
+ tags.push(...generateAudioTags(og.audio));
713
+ tags.push(...generateArticleTags(og.article));
714
+ tags.push(...generateProfileTags(og.profile));
715
+ tags.push(...generateBookTags(og.book));
716
+ tags.push(...generateProductTags(og.product));
717
+ return tags;
718
+ }
719
+ function generateOpenGraphFallback(title, description, url) {
720
+ const tags = [];
721
+ const typeTag = ogTag("type", "website");
722
+ if (typeTag) tags.push(typeTag);
723
+ const titleTag = ogTag("title", title);
724
+ if (titleTag) tags.push(titleTag);
725
+ const descTag = ogTag("description", description);
726
+ if (descTag) tags.push(descTag);
727
+ const urlTag = ogTag("url", url);
728
+ if (urlTag) tags.push(urlTag);
729
+ return tags;
730
+ }
731
+
732
+ // src/twitter.ts
733
+ function twitterTag(name, content) {
734
+ if (!content) {
735
+ return null;
736
+ }
737
+ return {
738
+ tag: "meta",
739
+ attrs: { name: `twitter:${name}`, content },
740
+ key: `meta:name:twitter:${name}`
741
+ };
742
+ }
743
+ function normalizeImage(image) {
744
+ if (!image) {
745
+ return void 0;
746
+ }
747
+ if (typeof image === "string") {
748
+ return { url: image };
749
+ }
750
+ return image;
751
+ }
752
+ function generateTwitterTags(twitter) {
753
+ if (!twitter) {
754
+ return [];
755
+ }
756
+ const tags = [];
757
+ const card = twitterTag("card", twitter.card || "summary");
758
+ if (card) tags.push(card);
759
+ const title = twitterTag("title", twitter.title);
760
+ if (title) tags.push(title);
761
+ const description = twitterTag("description", twitter.description);
762
+ if (description) tags.push(description);
763
+ const site = twitterTag("site", twitter.site);
764
+ if (site) tags.push(site);
765
+ const siteId = twitterTag("site:id", twitter.siteId);
766
+ if (siteId) tags.push(siteId);
767
+ const creator = twitterTag("creator", twitter.creator);
768
+ if (creator) tags.push(creator);
769
+ const creatorId = twitterTag("creator:id", twitter.creatorId);
770
+ if (creatorId) tags.push(creatorId);
771
+ const image = normalizeImage(twitter.image);
772
+ if (image) {
773
+ const imageTag = twitterTag("image", image.url);
774
+ if (imageTag) tags.push(imageTag);
775
+ if (image.alt) {
776
+ const imageAlt = twitterTag("image:alt", image.alt);
777
+ if (imageAlt) tags.push(imageAlt);
778
+ }
779
+ }
780
+ if (twitter.images && twitter.images.length > 0) {
781
+ twitter.images.forEach((img, index) => {
782
+ if (index >= 4) return;
783
+ const normalizedImg = typeof img === "string" ? { url: img } : img;
784
+ tags.push({
785
+ tag: "meta",
786
+ attrs: { name: `twitter:image${index}`, content: normalizedImg.url },
787
+ key: `meta:name:twitter:image${index}`
788
+ });
789
+ if (normalizedImg.alt) {
790
+ tags.push({
791
+ tag: "meta",
792
+ attrs: { name: `twitter:image${index}:alt`, content: normalizedImg.alt },
793
+ key: `meta:name:twitter:image${index}:alt`
794
+ });
795
+ }
796
+ });
797
+ }
798
+ if (twitter.player) {
799
+ const playerUrl = twitterTag("player", twitter.player.url);
800
+ if (playerUrl) tags.push(playerUrl);
801
+ const playerWidth = twitterTag("player:width", String(twitter.player.width));
802
+ if (playerWidth) tags.push(playerWidth);
803
+ const playerHeight = twitterTag("player:height", String(twitter.player.height));
804
+ if (playerHeight) tags.push(playerHeight);
805
+ if (twitter.player.stream) {
806
+ const stream = twitterTag("player:stream", twitter.player.stream);
807
+ if (stream) tags.push(stream);
808
+ }
809
+ }
810
+ if (twitter.app) {
811
+ const appName = twitterTag("app:name:iphone", twitter.app.name);
812
+ if (appName) tags.push(appName);
813
+ tags.push({
814
+ tag: "meta",
815
+ attrs: { name: "twitter:app:name:ipad", content: twitter.app.name },
816
+ key: "meta:name:twitter:app:name:ipad"
817
+ });
818
+ tags.push({
819
+ tag: "meta",
820
+ attrs: { name: "twitter:app:name:googleplay", content: twitter.app.name },
821
+ key: "meta:name:twitter:app:name:googleplay"
822
+ });
823
+ if (twitter.app.id.iphone) {
824
+ const iphoneId = twitterTag("app:id:iphone", twitter.app.id.iphone);
825
+ if (iphoneId) tags.push(iphoneId);
826
+ }
827
+ if (twitter.app.id.ipad) {
828
+ const ipadId = twitterTag("app:id:ipad", twitter.app.id.ipad);
829
+ if (ipadId) tags.push(ipadId);
830
+ }
831
+ if (twitter.app.id.googleplay) {
832
+ const googleId = twitterTag("app:id:googleplay", twitter.app.id.googleplay);
833
+ if (googleId) tags.push(googleId);
834
+ }
835
+ if (twitter.app.url) {
836
+ if (twitter.app.url.iphone) {
837
+ const iphoneUrl = twitterTag("app:url:iphone", twitter.app.url.iphone);
838
+ if (iphoneUrl) tags.push(iphoneUrl);
839
+ }
840
+ if (twitter.app.url.ipad) {
841
+ const ipadUrl = twitterTag("app:url:ipad", twitter.app.url.ipad);
842
+ if (ipadUrl) tags.push(ipadUrl);
843
+ }
844
+ if (twitter.app.url.googleplay) {
845
+ const googleUrl = twitterTag("app:url:googleplay", twitter.app.url.googleplay);
846
+ if (googleUrl) tags.push(googleUrl);
847
+ }
848
+ }
849
+ }
850
+ if (twitter.label1) {
851
+ const label1 = twitterTag("label1", twitter.label1);
852
+ if (label1) tags.push(label1);
853
+ }
854
+ if (twitter.data1) {
855
+ const data1 = twitterTag("data1", twitter.data1);
856
+ if (data1) tags.push(data1);
857
+ }
858
+ if (twitter.label2) {
859
+ const label2 = twitterTag("label2", twitter.label2);
860
+ if (label2) tags.push(label2);
861
+ }
862
+ if (twitter.data2) {
863
+ const data2 = twitterTag("data2", twitter.data2);
864
+ if (data2) tags.push(data2);
865
+ }
866
+ return tags;
867
+ }
868
+ function generateTwitterFallback(title, description, image, cardType = "summary") {
869
+ const tags = [];
870
+ const card = twitterTag("card", cardType);
871
+ if (card) tags.push(card);
872
+ const titleTag = twitterTag("title", title);
873
+ if (titleTag) tags.push(titleTag);
874
+ const descTag = twitterTag("description", description);
875
+ if (descTag) tags.push(descTag);
876
+ const imageTag = twitterTag("image", image);
877
+ if (imageTag) tags.push(imageTag);
878
+ return tags;
879
+ }
880
+
881
+ // src/index.ts
882
+ function createSEO(config = {}) {
883
+ const fullConfig = {
884
+ baseUrl: config.baseUrl || "",
885
+ titleTemplate: config.titleTemplate,
886
+ defaultTitle: config.defaultTitle,
887
+ defaultOpenGraph: config.defaultOpenGraph,
888
+ defaultTwitter: config.defaultTwitter,
889
+ features: {
890
+ openGraph: config.features?.openGraph ?? true,
891
+ twitter: config.features?.twitter ?? true,
892
+ robots: config.features?.robots ?? true
893
+ }
894
+ };
895
+ return {
896
+ config: fullConfig,
897
+ render(metadata) {
898
+ const tags = generateAllTags(metadata, fullConfig);
899
+ const html = renderTags(tags);
900
+ return { tags, html };
901
+ },
902
+ merge(parent, child) {
903
+ return mergeMetadata(parent, child);
904
+ },
905
+ resolveTitle(metadata) {
906
+ return resolveTitleWithTemplate(
907
+ metadata.title,
908
+ metadata.titleTemplate || fullConfig.titleTemplate,
909
+ metadata.defaultTitle || fullConfig.defaultTitle
910
+ );
911
+ },
912
+ canonical(path) {
913
+ if (!fullConfig.baseUrl) {
914
+ return path;
915
+ }
916
+ const base = fullConfig.baseUrl.endsWith("/") ? fullConfig.baseUrl.slice(0, -1) : fullConfig.baseUrl;
917
+ const normalizedPath = path.startsWith("/") ? path : "/" + path;
918
+ return base + normalizedPath;
919
+ }
920
+ };
921
+ }
922
+ function mergeMetadata(parent, child) {
923
+ return {
924
+ ...parent,
925
+ ...child,
926
+ // Merge Open Graph
927
+ openGraph: child.openGraph ? { ...parent.openGraph, ...child.openGraph } : parent.openGraph,
928
+ // Merge Twitter
929
+ twitter: child.twitter ? { ...parent.twitter, ...child.twitter } : parent.twitter,
930
+ // Merge Apple
931
+ apple: child.apple ? { ...parent.apple, ...child.apple } : parent.apple,
932
+ // Merge Icons
933
+ icons: child.icons ? { ...parent.icons, ...child.icons } : parent.icons,
934
+ // Merge verification
935
+ verification: child.verification ? { ...parent.verification, ...child.verification } : parent.verification,
936
+ // Merge other
937
+ other: child.other ? { ...parent.other, ...child.other } : parent.other
938
+ };
939
+ }
940
+ function resolveTitleWithTemplate(title, template, defaultTitle) {
941
+ const resolvedTitle = title || defaultTitle || "";
942
+ if (!resolvedTitle) {
943
+ return "";
944
+ }
945
+ if (template && title) {
946
+ return template.replace("%s", title);
947
+ }
948
+ return resolvedTitle;
949
+ }
950
+ function generateAllTags(metadata, config) {
951
+ const tags = [];
952
+ tags.push(...generateMetaTags(metadata, {
953
+ titleTemplate: config.titleTemplate,
954
+ defaultTitle: config.defaultTitle
955
+ }));
956
+ if (config.features?.openGraph !== false) {
957
+ if (metadata.openGraph) {
958
+ const og = { ...config.defaultOpenGraph, ...metadata.openGraph };
959
+ tags.push(...generateOpenGraphTags(og));
960
+ } else if (metadata.title || metadata.description) {
961
+ tags.push(...generateOpenGraphFallback(
962
+ metadata.title,
963
+ metadata.description,
964
+ metadata.canonical
965
+ ));
966
+ }
967
+ }
968
+ if (config.features?.twitter !== false) {
969
+ if (metadata.twitter) {
970
+ const twitter = { ...config.defaultTwitter, ...metadata.twitter };
971
+ tags.push(...generateTwitterTags(twitter));
972
+ } else if (metadata.title || metadata.description) {
973
+ const ogImage = metadata.openGraph?.images?.[0];
974
+ const imageUrl = typeof ogImage === "string" ? ogImage : ogImage?.url;
975
+ tags.push(...generateTwitterFallback(
976
+ metadata.title,
977
+ metadata.description,
978
+ imageUrl
979
+ ));
980
+ }
981
+ }
982
+ if (metadata.icons) {
983
+ tags.push(...generateIconTags(metadata.icons));
984
+ }
985
+ if (metadata.apple) {
986
+ tags.push(...generateAppleTags(metadata.apple));
987
+ }
988
+ return tags;
989
+ }
990
+ function generateIconTags(icons) {
991
+ if (!icons) return [];
992
+ const tags = [];
993
+ if (icons.icon) {
994
+ const iconList = Array.isArray(icons.icon) ? icons.icon : [icons.icon];
995
+ iconList.forEach((icon, index) => {
996
+ const iconData = typeof icon === "string" ? { url: icon } : icon;
997
+ tags.push({
998
+ tag: "link",
999
+ attrs: {
1000
+ rel: "icon",
1001
+ href: iconData.url,
1002
+ type: iconData.type,
1003
+ sizes: iconData.sizes
1004
+ },
1005
+ key: `link:icon:${index}`
1006
+ });
1007
+ });
1008
+ }
1009
+ if (icons.shortcut) {
1010
+ const shortcut = typeof icons.shortcut === "string" ? { url: icons.shortcut } : icons.shortcut;
1011
+ tags.push({
1012
+ tag: "link",
1013
+ attrs: {
1014
+ rel: "shortcut icon",
1015
+ href: shortcut.url,
1016
+ type: shortcut.type
1017
+ },
1018
+ key: "link:shortcut-icon"
1019
+ });
1020
+ }
1021
+ if (icons.apple) {
1022
+ const appleList = Array.isArray(icons.apple) ? icons.apple : [icons.apple];
1023
+ appleList.forEach((icon, index) => {
1024
+ const iconData = typeof icon === "string" ? { url: icon } : icon;
1025
+ tags.push({
1026
+ tag: "link",
1027
+ attrs: {
1028
+ rel: "apple-touch-icon",
1029
+ href: iconData.url,
1030
+ sizes: iconData.sizes
1031
+ },
1032
+ key: `link:apple-touch-icon:${index}`
1033
+ });
1034
+ });
1035
+ }
1036
+ return tags;
1037
+ }
1038
+ function generateAppleTags(apple) {
1039
+ if (!apple) return [];
1040
+ const tags = [];
1041
+ if (apple.mobileWebAppCapable) {
1042
+ tags.push({
1043
+ tag: "meta",
1044
+ attrs: { name: "apple-mobile-web-app-capable", content: "yes" },
1045
+ key: "meta:name:apple-mobile-web-app-capable"
1046
+ });
1047
+ }
1048
+ if (apple.statusBarStyle) {
1049
+ tags.push({
1050
+ tag: "meta",
1051
+ attrs: { name: "apple-mobile-web-app-status-bar-style", content: apple.statusBarStyle },
1052
+ key: "meta:name:apple-mobile-web-app-status-bar-style"
1053
+ });
1054
+ }
1055
+ if (apple.title) {
1056
+ tags.push({
1057
+ tag: "meta",
1058
+ attrs: { name: "apple-mobile-web-app-title", content: apple.title },
1059
+ key: "meta:name:apple-mobile-web-app-title"
1060
+ });
1061
+ }
1062
+ if (apple.touchIcon) {
1063
+ const touchIcon = typeof apple.touchIcon === "string" ? { url: apple.touchIcon } : apple.touchIcon;
1064
+ tags.push({
1065
+ tag: "link",
1066
+ attrs: {
1067
+ rel: "apple-touch-icon",
1068
+ href: touchIcon.url,
1069
+ sizes: touchIcon.sizes
1070
+ },
1071
+ key: "link:apple-touch-icon-from-apple"
1072
+ });
1073
+ }
1074
+ return tags;
1075
+ }
1076
+
1077
+ // src/json-ld/index.ts
1078
+ function createJsonLd(schema, options) {
1079
+ const data = {
1080
+ "@context": "https://schema.org",
1081
+ ...schema
1082
+ };
1083
+ return JSON.stringify(data);
1084
+ }
1085
+
1086
+ // src/vue/index.ts
1087
+ var SEO_KEY = /* @__PURE__ */ Symbol("flight-seo");
1088
+ function provideSEO(defaultMetadata = {}) {
1089
+ const metadata = reactive({ ...defaultMetadata });
1090
+ const state = {
1091
+ metadata,
1092
+ setMetadata: (newMetadata) => {
1093
+ Object.assign(metadata, newMetadata);
1094
+ },
1095
+ addMetadata: (additionalMetadata) => {
1096
+ Object.assign(metadata, mergeMetadata(metadata, additionalMetadata));
1097
+ }
1098
+ };
1099
+ provide(SEO_KEY, state);
1100
+ return state;
1101
+ }
1102
+ function injectSEO() {
1103
+ return inject(SEO_KEY);
1104
+ }
1105
+ function useSEO(metadata, options = {}) {
1106
+ const { immediate = true } = options;
1107
+ const seoContext = injectSEO();
1108
+ const metadataRef = computed(() => {
1109
+ if ("value" in metadata) {
1110
+ return metadata.value;
1111
+ }
1112
+ return metadata;
1113
+ });
1114
+ const updateHead = () => {
1115
+ if (typeof document === "undefined") return;
1116
+ const meta = metadataRef.value;
1117
+ if (meta.title) {
1118
+ const title = meta.titleTemplate ? meta.titleTemplate.replace("%s", meta.title) : meta.title;
1119
+ document.title = title;
1120
+ }
1121
+ if (meta.description) {
1122
+ updateMetaTag("description", meta.description);
1123
+ }
1124
+ if (meta.canonical) {
1125
+ updateLinkTag("canonical", meta.canonical);
1126
+ }
1127
+ if (meta.openGraph) {
1128
+ updateOpenGraphTags(meta.openGraph);
1129
+ }
1130
+ if (meta.twitter) {
1131
+ updateTwitterTags(meta.twitter);
1132
+ }
1133
+ if (seoContext) {
1134
+ seoContext.addMetadata(meta);
1135
+ }
1136
+ };
1137
+ watch(metadataRef, updateHead, { deep: true, immediate });
1138
+ onMounted(() => {
1139
+ if (immediate) {
1140
+ updateHead();
1141
+ }
1142
+ });
1143
+ }
1144
+ function useSeoMeta(input) {
1145
+ const inputRef = computed(() => {
1146
+ if ("value" in input) {
1147
+ return input.value;
1148
+ }
1149
+ return input;
1150
+ });
1151
+ const metadata = computed(() => {
1152
+ const i = inputRef.value;
1153
+ return {
1154
+ title: i.title,
1155
+ titleTemplate: i.titleTemplate,
1156
+ description: i.description,
1157
+ keywords: i.keywords,
1158
+ author: i.author,
1159
+ robots: i.robots ? { index: !i.robots.includes("noindex"), follow: !i.robots.includes("nofollow") } : void 0,
1160
+ canonical: i.canonical,
1161
+ openGraph: i.ogTitle || i.ogDescription || i.ogImage ? {
1162
+ title: i.ogTitle,
1163
+ description: i.ogDescription,
1164
+ url: i.ogUrl,
1165
+ type: i.ogType,
1166
+ siteName: i.ogSiteName,
1167
+ locale: i.ogLocale,
1168
+ images: i.ogImage ? [{ url: i.ogImage }] : void 0
1169
+ } : void 0,
1170
+ twitter: i.twitterCard || i.twitterTitle || i.twitterImage ? {
1171
+ card: i.twitterCard,
1172
+ title: i.twitterTitle,
1173
+ description: i.twitterDescription,
1174
+ site: i.twitterSite,
1175
+ creator: i.twitterCreator,
1176
+ image: i.twitterImage ? { url: i.twitterImage } : void 0
1177
+ } : void 0
1178
+ };
1179
+ });
1180
+ useSEO(metadata);
1181
+ }
1182
+ function useJsonLd(schema, options) {
1183
+ const schemaRef = computed(() => {
1184
+ if ("value" in schema) {
1185
+ return schema.value;
1186
+ }
1187
+ return schema;
1188
+ });
1189
+ const scriptId = options?.id ? `json-ld-${options.id}` : "json-ld-data";
1190
+ let scriptElement = null;
1191
+ const updateScript = () => {
1192
+ if (typeof document === "undefined") return;
1193
+ const jsonLd = createJsonLd(schemaRef.value);
1194
+ if (!scriptElement) {
1195
+ scriptElement = document.getElementById(scriptId);
1196
+ if (!scriptElement) {
1197
+ scriptElement = document.createElement("script");
1198
+ scriptElement.type = "application/ld+json";
1199
+ scriptElement.id = scriptId;
1200
+ document.head.appendChild(scriptElement);
1201
+ }
1202
+ }
1203
+ scriptElement.textContent = jsonLd;
1204
+ };
1205
+ watch(schemaRef, updateScript, { deep: true, immediate: true });
1206
+ onMounted(updateScript);
1207
+ onUnmounted(() => {
1208
+ if (scriptElement && scriptElement.parentNode) {
1209
+ scriptElement.parentNode.removeChild(scriptElement);
1210
+ }
1211
+ });
1212
+ }
1213
+ function renderMetadataToString(metadata) {
1214
+ const seo = createSEO();
1215
+ const { html } = seo.render(metadata);
1216
+ return html;
1217
+ }
1218
+ function updateMetaTag(name, content) {
1219
+ let meta = document.querySelector(`meta[name="${name}"]`);
1220
+ if (!meta) {
1221
+ meta = document.createElement("meta");
1222
+ meta.setAttribute("name", name);
1223
+ document.head.appendChild(meta);
1224
+ }
1225
+ meta.setAttribute("content", content);
1226
+ }
1227
+ function updateLinkTag(rel, href) {
1228
+ let link = document.querySelector(`link[rel="${rel}"]`);
1229
+ if (!link) {
1230
+ link = document.createElement("link");
1231
+ link.setAttribute("rel", rel);
1232
+ document.head.appendChild(link);
1233
+ }
1234
+ link.setAttribute("href", href);
1235
+ }
1236
+ function updateOpenGraphTags(og) {
1237
+ const properties = {
1238
+ "og:title": og.title,
1239
+ "og:description": og.description,
1240
+ "og:type": og.type,
1241
+ "og:url": og.url,
1242
+ "og:site_name": og.siteName,
1243
+ "og:locale": og.locale
1244
+ };
1245
+ for (const [property, content] of Object.entries(properties)) {
1246
+ if (content) {
1247
+ updatePropertyMetaTag(property, content);
1248
+ }
1249
+ }
1250
+ if (og.images && og.images.length > 0) {
1251
+ updatePropertyMetaTag("og:image", og.images[0].url);
1252
+ }
1253
+ }
1254
+ function updateTwitterTags(twitter) {
1255
+ const names = {
1256
+ "twitter:card": twitter.card,
1257
+ "twitter:title": twitter.title,
1258
+ "twitter:description": twitter.description,
1259
+ "twitter:site": twitter.site,
1260
+ "twitter:creator": twitter.creator
1261
+ };
1262
+ for (const [name, content] of Object.entries(names)) {
1263
+ if (content) {
1264
+ updateNameMetaTag(name, content);
1265
+ }
1266
+ }
1267
+ if (twitter.image) {
1268
+ const imageUrl = typeof twitter.image === "string" ? twitter.image : twitter.image.url;
1269
+ updateNameMetaTag("twitter:image", imageUrl);
1270
+ }
1271
+ }
1272
+ function updatePropertyMetaTag(property, content) {
1273
+ let meta = document.querySelector(`meta[property="${property}"]`);
1274
+ if (!meta) {
1275
+ meta = document.createElement("meta");
1276
+ meta.setAttribute("property", property);
1277
+ document.head.appendChild(meta);
1278
+ }
1279
+ meta.setAttribute("content", content);
1280
+ }
1281
+ function updateNameMetaTag(name, content) {
1282
+ let meta = document.querySelector(`meta[name="${name}"]`);
1283
+ if (!meta) {
1284
+ meta = document.createElement("meta");
1285
+ meta.setAttribute("name", name);
1286
+ document.head.appendChild(meta);
1287
+ }
1288
+ meta.setAttribute("content", content);
1289
+ }
1290
+
1291
+ export { injectSEO, provideSEO, renderMetadataToString, useJsonLd, useSEO, useSeoMeta };
1292
+ //# sourceMappingURL=index.js.map
1293
+ //# sourceMappingURL=index.js.map