@hkdigital/lib-core 0.5.93 → 0.5.94

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.
Files changed (37) hide show
  1. package/dist/config/generators/imagetools.d.ts +14 -0
  2. package/dist/config/generators/imagetools.js +55 -0
  3. package/dist/config/imagetools.d.ts +12 -0
  4. package/dist/meta/README.md +92 -0
  5. package/dist/meta/components/Favicons.svelte +30 -0
  6. package/dist/meta/components/Favicons.svelte.d.ts +103 -0
  7. package/dist/meta/components/PWA.svelte +51 -0
  8. package/dist/meta/components/PWA.svelte.d.ts +103 -0
  9. package/dist/meta/components/SEO.svelte +146 -0
  10. package/dist/meta/components/SEO.svelte.d.ts +108 -0
  11. package/dist/meta/components.d.ts +3 -0
  12. package/dist/meta/components.js +3 -0
  13. package/dist/meta/config.typedef.d.ts +98 -0
  14. package/dist/meta/config.typedef.js +44 -0
  15. package/dist/meta/typedef.d.ts +3 -0
  16. package/dist/meta/typedef.js +14 -0
  17. package/dist/meta/utils/lang.d.ts +29 -0
  18. package/dist/meta/utils/lang.js +84 -0
  19. package/dist/meta/utils/robots.d.ts +1 -0
  20. package/dist/meta/utils/robots.js +1 -0
  21. package/dist/meta/utils/sitemap.d.ts +1 -0
  22. package/dist/meta/utils/sitemap.js +1 -0
  23. package/dist/meta/utils.d.ts +3 -0
  24. package/dist/meta/utils.js +11 -0
  25. package/package.json +1 -1
  26. package/dist/meta/robots.d.ts +0 -1
  27. package/dist/meta/robots.js +0 -5
  28. package/dist/meta/sitemap.d.ts +0 -1
  29. package/dist/meta/sitemap.js +0 -5
  30. /package/dist/meta/{robots/index.d.ts → utils/robots/robots.d.ts} +0 -0
  31. /package/dist/meta/{robots/index.js → utils/robots/robots.js} +0 -0
  32. /package/dist/meta/{robots → utils/robots}/typedef.d.ts +0 -0
  33. /package/dist/meta/{robots → utils/robots}/typedef.js +0 -0
  34. /package/dist/meta/{sitemap/index.d.ts → utils/sitemap/sitemap.d.ts} +0 -0
  35. /package/dist/meta/{sitemap/index.js → utils/sitemap/sitemap.js} +0 -0
  36. /package/dist/meta/{sitemap → utils/sitemap}/typedef.d.ts +0 -0
  37. /package/dist/meta/{sitemap → utils/sitemap}/typedef.js +0 -0
@@ -8,6 +8,12 @@
8
8
  * @param {number[]} [options.faviconSizes=FAVICON_SIZES]
9
9
  * @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
10
10
  *
11
+ * @param {{width: number, height: number}} [options.seoLandscapeSize]
12
+ * SEO landscape image size
13
+ *
14
+ * @param {{width: number, height: number}} [options.seoSquareSize]
15
+ * SEO square image size
16
+ *
11
17
  * @returns {(
12
18
  * entries: [string, string[]][]
13
19
  * ) => (Record<string, string | string[]>[])}
@@ -17,6 +23,14 @@ export function generateResponseConfigs(options?: {
17
23
  thumbnailWidth?: number | undefined;
18
24
  faviconSizes?: number[] | undefined;
19
25
  appleTouchSizes?: number[] | undefined;
26
+ seoLandscapeSize?: {
27
+ width: number;
28
+ height: number;
29
+ } | undefined;
30
+ seoSquareSize?: {
31
+ width: number;
32
+ height: number;
33
+ } | undefined;
20
34
  }): (entries: [string, string[]][]) => (Record<string, string | string[]>[]);
21
35
  /**
22
36
  * Configures and returns a function that can be used as
@@ -21,6 +21,9 @@ const APPLE_TOUCH_SIZES = [
21
21
  180 // iPhone retina, iOS home screen
22
22
  ];
23
23
 
24
+ const SEO_LANDSCAPE_SIZE = { width: 1200, height: 630 };
25
+ const SEO_SQUARE_SIZE = { width: 1200, height: 1200 };
26
+
24
27
  const DEFAULT_PRESETS = {
25
28
  default: {
26
29
  format: 'avif',
@@ -70,6 +73,12 @@ const DEFAULT_PRESETS = {
70
73
  * @param {number[]} [options.faviconSizes=FAVICON_SIZES]
71
74
  * @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
72
75
  *
76
+ * @param {{width: number, height: number}} [options.seoLandscapeSize]
77
+ * SEO landscape image size
78
+ *
79
+ * @param {{width: number, height: number}} [options.seoSquareSize]
80
+ * SEO square image size
81
+ *
73
82
  * @returns {(
74
83
  * entries: [string, string[]][]
75
84
  * ) => (Record<string, string | string[]>[])}
@@ -115,15 +124,25 @@ export function generateResponseConfigs(options) {
115
124
 
116
125
  // @ts-ignore
117
126
  const isAppleTouchIcon = !!entries.find(([key]) => key === 'apple-touch-icons');
127
+
128
+ // @ts-ignore
129
+ const isSeoLandscape = !!entries.find(([key]) => key === 'seo-landscape');
130
+
131
+ // @ts-ignore
132
+ const isSeoSquare = !!entries.find(([key]) => key === 'seo-square');
118
133
  // console.log('responsiveConfig found:', !!responsiveConfig);
119
134
 
120
135
  const widths = options?.widths ?? DEFAULT_WIDTHS;
121
136
  const faviconSizes = options?.faviconSizes ?? FAVICON_SIZES;
122
137
  const appleTouchSizes = options?.appleTouchSizes ?? APPLE_TOUCH_SIZES;
138
+ const seoLandscapeSize = options?.seoLandscapeSize ?? SEO_LANDSCAPE_SIZE;
139
+ const seoSquareSize = options?.seoSquareSize ?? SEO_SQUARE_SIZE;
123
140
 
124
141
  delete configPairs.responsive;
125
142
  delete configPairs.favicons;
126
143
  delete configPairs['apple-touch-icons'];
144
+ delete configPairs['seo-landscape'];
145
+ delete configPairs['seo-square'];
127
146
 
128
147
  // Always include the main image(s) and a thumbnail version
129
148
  const thumbnailConfig = {
@@ -163,6 +182,30 @@ export function generateResponseConfigs(options) {
163
182
  return appleTouchConfigs;
164
183
  }
165
184
 
185
+ // Handle seo-landscape directive - generate landscape SEO image
186
+
187
+ if (isSeoLandscape) {
188
+ return [{
189
+ ...configPairs,
190
+ w: String(seoLandscapeSize.width),
191
+ h: String(seoLandscapeSize.height),
192
+ format: 'jpg',
193
+ quality: '95'
194
+ }];
195
+ }
196
+
197
+ // Handle seo-square directive - generate square SEO image
198
+
199
+ if (isSeoSquare) {
200
+ return [{
201
+ ...configPairs,
202
+ w: String(seoSquareSize.width),
203
+ h: String(seoSquareSize.height),
204
+ format: 'jpg',
205
+ quality: '95'
206
+ }];
207
+ }
208
+
166
209
  if (!responsiveConfig) {
167
210
  // Directive 'responsive' was not set => return original + thumbnail
168
211
  const originalConfig = configPairs; // No 'w' means original dimensions
@@ -227,6 +270,18 @@ export function generateDefaultDirectives(options) {
227
270
  params.set('as', 'metadata');
228
271
  }
229
272
 
273
+ // > Return picture (URL) if directive 'seo-landscape' is set
274
+
275
+ if (params.has('seo-landscape')) {
276
+ params.set('as', 'picture');
277
+ }
278
+
279
+ // > Return picture (URL) if directive 'seo-square' is set
280
+
281
+ if (params.has('seo-square')) {
282
+ params.set('as', 'picture');
283
+ }
284
+
230
285
  // > Process presets
231
286
 
232
287
  if (presetName) {
@@ -84,3 +84,15 @@ declare module '*?apple-touch-icons' {
84
84
  const out: ImageSource;
85
85
  export default out;
86
86
  }
87
+
88
+ // Generate SEO landscape image (1200x630)
89
+ declare module '*?seo-landscape' {
90
+ const out: string;
91
+ export default out;
92
+ }
93
+
94
+ // Generate SEO square image (1200x1200)
95
+ declare module '*?seo-square' {
96
+ const out: string;
97
+ export default out;
98
+ }
@@ -62,6 +62,12 @@ export const GET = async ({ url }) => {
62
62
  * Supports wildcards (e.g., '*.example.com')
63
63
  * @property {string[]} [disallowedPaths]
64
64
  * Paths to block from indexing (e.g., '/admin', '/api/*')
65
+ * @property {boolean} [allowAiTraining=true]
66
+ * Allow AI training bots to crawl content for model training.
67
+ * Set to false to block bots like GPTBot, Google-Extended, CCBot, etc.
68
+ * @property {boolean} [allowAiReading=true]
69
+ * Allow AI assistants/chatbots to read content for user responses.
70
+ * Set to false to block bots like ChatGPT-User, Claude-Web, etc.
65
71
  */
66
72
  ```
67
73
 
@@ -108,6 +114,54 @@ const config = {
108
114
  // Sitemap: https://example.com/sitemap.xml
109
115
  ```
110
116
 
117
+ **AI bot control:**
118
+
119
+ ```javascript
120
+ // Block all AI bots from training on your content
121
+ const config = {
122
+ allowedHosts: '*',
123
+ allowAiTraining: false
124
+ };
125
+
126
+ // Generates additional blocks:
127
+ // User-agent: GPTBot
128
+ // Disallow: /
129
+ //
130
+ // User-agent: Google-Extended
131
+ // Disallow: /
132
+ // ... (and other AI training bots)
133
+ ```
134
+
135
+ **AI training vs AI reading:**
136
+
137
+ ```javascript
138
+ // Block training but allow reading (ChatGPT can browse, but not train)
139
+ const config = {
140
+ allowAiTraining: false, // Blocks GPTBot, Google-Extended, CCBot
141
+ allowAiReading: true // Allows ChatGPT-User, Claude-Web
142
+ };
143
+
144
+ // Block both training and reading
145
+ const config = {
146
+ allowAiTraining: false,
147
+ allowAiReading: false
148
+ };
149
+ ```
150
+
151
+ **Blocked AI training bots:**
152
+ - `GPTBot` - OpenAI training
153
+ - `Google-Extended` - Google AI training
154
+ - `CCBot` - Common Crawl
155
+ - `anthropic-ai` - Anthropic training
156
+ - `FacebookBot` - Meta AI training
157
+ - `PerplexityBot` - Perplexity AI training
158
+ - And others (see `src/lib/meta/robots/index.js` for full list)
159
+
160
+ **Blocked AI reading bots:**
161
+ - `ChatGPT-User` - ChatGPT browsing
162
+ - `Claude-Web` - Claude browsing
163
+ - `cohere-ai` - Cohere browsing
164
+
111
165
  **Sitemap reference:**
112
166
 
113
167
  Sitemap reference is always included for allowed hosts:
@@ -277,6 +331,44 @@ const robotsConfig = {
277
331
  // All hosts → Allow + Sitemap
278
332
  ```
279
333
 
334
+ ### Block AI training but allow search engines
335
+
336
+ ```javascript
337
+ // Protect premium content from AI training while allowing SEO
338
+ const robotsConfig = {
339
+ allowedHosts: ['mysite.com', 'www.mysite.com'],
340
+ disallowedPaths: ['/admin'],
341
+ allowAiTraining: false, // Block GPTBot, Google-Extended, etc.
342
+ allowAiReading: true // Allow ChatGPT browsing (user queries)
343
+ };
344
+
345
+ // Generates:
346
+ // User-agent: *
347
+ // Allow: /
348
+ // Disallow: /admin
349
+ // Sitemap: https://mysite.com/sitemap.xml
350
+ //
351
+ // User-agent: GPTBot
352
+ // Disallow: /
353
+ //
354
+ // User-agent: Google-Extended
355
+ // Disallow: /
356
+ // ... (other AI training bots)
357
+ ```
358
+
359
+ ### Block all AI bots completely
360
+
361
+ ```javascript
362
+ // Block both AI training and AI reading/browsing
363
+ const robotsConfig = {
364
+ allowedHosts: ['mysite.com'],
365
+ allowAiTraining: false,
366
+ allowAiReading: false
367
+ };
368
+
369
+ // Blocks: GPTBot, Google-Extended, CCBot, ChatGPT-User, Claude-Web, etc.
370
+ ```
371
+
280
372
  ### Complex sitemap configuration
281
373
 
282
374
  ```javascript
@@ -0,0 +1,30 @@
1
+ <script>
2
+ /**
3
+ * Favicons component
4
+ *
5
+ * Generates favicon and apple-touch-icon links for browsers and PWAs.
6
+ *
7
+ * @typedef {import('../typedef.js').MetaConfig} MetaConfig
8
+ */
9
+
10
+ /** @type {{ config: MetaConfig }} */
11
+ let { config } = $props();
12
+
13
+ const { faviconImages, appleTouchIcons } = config;
14
+ </script>
15
+
16
+ <svelte:head>
17
+ <!-- Basic favicon configuration -->
18
+ {#each faviconImages as img}
19
+ <link
20
+ rel="icon"
21
+ type="image/png"
22
+ sizes="{img.width}x{img.width}"
23
+ href={img.src}
24
+ />
25
+ {/each}
26
+
27
+ {#each appleTouchIcons as img}
28
+ <link rel="apple-touch-icon" sizes="{img.width}x{img.width}" href={img.src} />
29
+ {/each}
30
+ </svelte:head>
@@ -0,0 +1,103 @@
1
+ export default Favicons;
2
+ type Favicons = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const Favicons: import("svelte").Component<{
7
+ config: import("../config.typedef.js").MetaConfig;
8
+ }, {}, "">;
9
+ type $$ComponentProps = {
10
+ config: {
11
+ /**
12
+ * - Full app name
13
+ */
14
+ name: string;
15
+ /**
16
+ * - Short app name (max 12 characters)
17
+ */
18
+ shortName: string;
19
+ /**
20
+ * - App description for search engines
21
+ *
22
+ * Language and locale
23
+ */
24
+ description: string;
25
+ /**
26
+ * Language configurations
27
+ */
28
+ languages: Record<string, {
29
+ lang: string;
30
+ locale: string;
31
+ }>;
32
+ /**
33
+ * - Default language code
34
+ */
35
+ defaultLanguage: string;
36
+ /**
37
+ * - Default locale
38
+ *
39
+ * PWA theme and colors
40
+ */
41
+ defaultLocale: string;
42
+ /**
43
+ * - Theme color
44
+ */
45
+ backgroundAndThemeColor: string;
46
+ /**
47
+ * - Theme color for browser UI
48
+ */
49
+ themeColor: string;
50
+ /**
51
+ * - Background color
52
+ */
53
+ backgroundColor: string;
54
+ /**
55
+ * - iOS status bar style
56
+ */
57
+ statusBarStyle: string;
58
+ /**
59
+ * - Screen orientation
60
+ */
61
+ orientation: string;
62
+ /**
63
+ * - Disable pinch-to-zoom
64
+ *
65
+ * SEO images
66
+ */
67
+ disablePageZoom: boolean;
68
+ /**
69
+ * - Landscape SEO image URL (1200×630)
70
+ */
71
+ SeoImageLandscape?: string | undefined;
72
+ /**
73
+ * - Square SEO image URL (1200×1200)
74
+ *
75
+ * Favicon images (processed by imagetools)
76
+ */
77
+ SeoImageSquare?: string | undefined;
78
+ /**
79
+ * Processed favicon images
80
+ */
81
+ faviconImages: Array<{
82
+ src: string;
83
+ width: number;
84
+ }>;
85
+ /**
86
+ * Processed apple-touch-icon images
87
+ *
88
+ * Site configuration
89
+ */
90
+ appleTouchIcons: Array<{
91
+ src: string;
92
+ width: number;
93
+ }>;
94
+ /**
95
+ * Routes for sitemap.xml
96
+ */
97
+ siteRoutes: import("../typedef.js").SitemapRoute[];
98
+ /**
99
+ * Robots.txt configuration
100
+ */
101
+ robotsConfig: import("../typedef.js").RobotsConfig;
102
+ };
103
+ };
@@ -0,0 +1,51 @@
1
+ <script>
2
+ import { onMount } from 'svelte';
3
+
4
+ /**
5
+ * PWA component
6
+ *
7
+ * Generates Progressive Web App meta tags and viewport configuration.
8
+ *
9
+ * @typedef {import('../typedef.js').MetaConfig} MetaConfig
10
+ */
11
+
12
+ /** @type {{ config: MetaConfig }} */
13
+ let { config } = $props();
14
+
15
+ const { themeColor, statusBarStyle, name, shortName, disablePageZoom } =
16
+ config;
17
+
18
+ let shouldSetTitle = $state(false);
19
+
20
+ onMount(() => {
21
+ // Check if title element exists and has content
22
+ const titleElement = document.querySelector('title');
23
+ const hasTitle = titleElement && titleElement.textContent.trim() !== '';
24
+
25
+ if (!hasTitle) {
26
+ shouldSetTitle = true;
27
+ }
28
+ });
29
+ </script>
30
+
31
+ <svelte:head>
32
+ {#if shouldSetTitle}
33
+ <title>{name}</title>
34
+ {/if}
35
+
36
+ {#if !disablePageZoom}
37
+ <meta name="viewport" content="width=device-width, initial-scale=1">
38
+ {:else}
39
+ <meta name="viewport"
40
+ content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no, width=device-width, viewport-fit=cover" />
41
+ {/if}
42
+
43
+ <meta name="theme-color" content="{themeColor}">
44
+ <link rel="manifest" href="/manifest.json">
45
+
46
+ <meta name="mobile-web-app-capable" content="yes">
47
+
48
+ <!-- iOS-specific meta tags -->
49
+ <meta name="apple-mobile-web-app-status-bar-style" content="{statusBarStyle}">
50
+ <meta name="apple-mobile-web-app-title" content="{shortName}">
51
+ </svelte:head>
@@ -0,0 +1,103 @@
1
+ export default PWA;
2
+ type PWA = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const PWA: import("svelte").Component<{
7
+ config: import("../config.typedef.js").MetaConfig;
8
+ }, {}, "">;
9
+ type $$ComponentProps = {
10
+ config: {
11
+ /**
12
+ * - Full app name
13
+ */
14
+ name: string;
15
+ /**
16
+ * - Short app name (max 12 characters)
17
+ */
18
+ shortName: string;
19
+ /**
20
+ * - App description for search engines
21
+ *
22
+ * Language and locale
23
+ */
24
+ description: string;
25
+ /**
26
+ * Language configurations
27
+ */
28
+ languages: Record<string, {
29
+ lang: string;
30
+ locale: string;
31
+ }>;
32
+ /**
33
+ * - Default language code
34
+ */
35
+ defaultLanguage: string;
36
+ /**
37
+ * - Default locale
38
+ *
39
+ * PWA theme and colors
40
+ */
41
+ defaultLocale: string;
42
+ /**
43
+ * - Theme color
44
+ */
45
+ backgroundAndThemeColor: string;
46
+ /**
47
+ * - Theme color for browser UI
48
+ */
49
+ themeColor: string;
50
+ /**
51
+ * - Background color
52
+ */
53
+ backgroundColor: string;
54
+ /**
55
+ * - iOS status bar style
56
+ */
57
+ statusBarStyle: string;
58
+ /**
59
+ * - Screen orientation
60
+ */
61
+ orientation: string;
62
+ /**
63
+ * - Disable pinch-to-zoom
64
+ *
65
+ * SEO images
66
+ */
67
+ disablePageZoom: boolean;
68
+ /**
69
+ * - Landscape SEO image URL (1200×630)
70
+ */
71
+ SeoImageLandscape?: string | undefined;
72
+ /**
73
+ * - Square SEO image URL (1200×1200)
74
+ *
75
+ * Favicon images (processed by imagetools)
76
+ */
77
+ SeoImageSquare?: string | undefined;
78
+ /**
79
+ * Processed favicon images
80
+ */
81
+ faviconImages: Array<{
82
+ src: string;
83
+ width: number;
84
+ }>;
85
+ /**
86
+ * Processed apple-touch-icon images
87
+ *
88
+ * Site configuration
89
+ */
90
+ appleTouchIcons: Array<{
91
+ src: string;
92
+ width: number;
93
+ }>;
94
+ /**
95
+ * Routes for sitemap.xml
96
+ */
97
+ siteRoutes: import("../typedef.js").SitemapRoute[];
98
+ /**
99
+ * Robots.txt configuration
100
+ */
101
+ robotsConfig: import("../typedef.js").RobotsConfig;
102
+ };
103
+ };
@@ -0,0 +1,146 @@
1
+ <script>
2
+ /**
3
+ * SEO component
4
+ *
5
+ * Generates SEO meta tags, Open Graph tags, and hreflang links.
6
+ *
7
+ * @typedef {import('../typedef.js').MetaConfig} MetaConfig
8
+ */
9
+
10
+ /**
11
+ * SEO component props
12
+ *
13
+ * @typedef {Object} SEOProps
14
+ * @property {MetaConfig} config - Configuration object
15
+ * @property {string} [title] - Page title (defaults to config name)
16
+ * @property {string} [description]
17
+ * Page description (defaults to config description)
18
+ * @property {string} [url] - Canonical URL for this page
19
+ * @property {string} [image]
20
+ * Social media preview image URL (defaults to landscape SEO image)
21
+ * @property {string} [imageAlt] - Alt text for social media image
22
+ * @property {string} [type] - Open Graph type (default: 'website')
23
+ * @property {string} [locale]
24
+ * Content locale (auto-detected from URL or defaults to config)
25
+ * @property {string} [siteName]
26
+ * Site name for Open Graph (defaults to config name)
27
+ * @property {Record<string, string>} [alternateUrls]
28
+ * Alternate language URLs for hreflang tags
29
+ * @property {string} [robots]
30
+ * Robots meta directives (e.g., 'noindex, nofollow')
31
+ * @property {boolean} [noAiTraining]
32
+ * Prevent AI training on this page content
33
+ */
34
+
35
+ /** @type {SEOProps} */
36
+ let {
37
+ config,
38
+ title,
39
+ description,
40
+ url = undefined,
41
+ image,
42
+ imageAlt,
43
+ type = 'website',
44
+ locale = undefined,
45
+ siteName,
46
+ alternateUrls = undefined,
47
+ robots = undefined,
48
+ noAiTraining = false
49
+ } = $props();
50
+
51
+ // Extract config values
52
+ const {
53
+ name: defaultTitle,
54
+ description: defaultDescription,
55
+ SeoImageLandscape,
56
+ SeoImageSquare,
57
+ defaultLocale,
58
+ languages
59
+ } = config;
60
+
61
+ // Use the landscape image as default (best for most platforms)
62
+ const defaultSeoImage = SeoImageLandscape || undefined;
63
+
64
+ // Apply defaults from config
65
+ title = title ?? defaultTitle;
66
+ description = description ?? defaultDescription;
67
+ image = image ?? defaultSeoImage;
68
+ imageAlt = imageAlt ?? title;
69
+ siteName = siteName ?? defaultTitle;
70
+
71
+ // Use locale from prop or fall back to config default
72
+ const finalLocale = locale || defaultLocale;
73
+ </script>
74
+
75
+ <svelte:head>
76
+ <!-- Page title (overrides %title% from app.html) -->
77
+ <title>{title}</title>
78
+
79
+ <!-- Basic SEO -->
80
+ <meta name="description" content={description}>
81
+
82
+ <!-- Robots directives -->
83
+ {#if robots}
84
+ <meta name="robots" content={robots}>
85
+ {/if}
86
+
87
+ <!-- AI training and reading restrictions -->
88
+ {#if noAiTraining}
89
+ <!-- Google AI -->
90
+ <meta name="google-extended" content="noindex, nofollow">
91
+ <!-- OpenAI (ChatGPT) -->
92
+ <meta name="OAI-SearchBot" content="noindex, nofollow">
93
+ <!-- Common Crawl -->
94
+ <meta name="CCBot" content="noindex, nofollow">
95
+ <!-- Anthropic Claude -->
96
+ <meta name="anthropic-ai" content="noindex, nofollow">
97
+ <!-- General AI crawlers -->
98
+ <meta name="robots" content="noai, noimageai">
99
+ {/if}
100
+
101
+ <!-- Open Graph / Facebook / LinkedIn / Discord -->
102
+ <meta property="og:type" content={type}>
103
+ <meta property="og:title" content={title}>
104
+ <meta property="og:description" content={description}>
105
+ <meta property="og:site_name" content={siteName}>
106
+ <meta property="og:locale" content={finalLocale}>
107
+
108
+ {#if url}
109
+ <meta property="og:url" content={url}>
110
+ <link rel="canonical" href={url}>
111
+ {/if}
112
+
113
+ {#if image}
114
+ <meta property="og:image" content={image}>
115
+ <meta property="og:image:alt" content={imageAlt}>
116
+ <!-- Image metadata (dimensions from preprocessor) -->
117
+ {#if image === SeoImageLandscape}
118
+ <meta property="og:image:width" content="1200">
119
+ <meta property="og:image:height" content="630">
120
+ <meta property="og:image:type" content="image/jpeg">
121
+ {/if}
122
+ {/if}
123
+
124
+ <!-- Additional square image for platforms that prefer it -->
125
+ {#if SeoImageSquare && SeoImageSquare !== image}
126
+ <meta property="og:image" content={SeoImageSquare}>
127
+ <meta property="og:image:width" content="1200">
128
+ <meta property="og:image:height" content="1200">
129
+ <meta property="og:image:type" content="image/jpeg">
130
+ {/if}
131
+
132
+ <!-- Alternate locales for Open Graph -->
133
+ {#each Object.entries(languages) as [code, config]}
134
+ {#if config.locale !== finalLocale}
135
+ <meta property="og:locale:alternate" content={config.locale}>
136
+ {/if}
137
+ {/each}
138
+
139
+ <!-- Alternate language URLs (hreflang) -->
140
+ {#if alternateUrls}
141
+ {#each Object.entries(alternateUrls) as [lang, href]}
142
+ <link rel="alternate" hreflang={lang} href={href}>
143
+ {/each}
144
+ {/if}
145
+
146
+ </svelte:head>
@@ -0,0 +1,108 @@
1
+ export default SEO;
2
+ type SEO = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<SEOProps>): void;
5
+ };
6
+ declare const SEO: import("svelte").Component<{
7
+ /**
8
+ * - Configuration object
9
+ */
10
+ config: import("../config.typedef.js").MetaConfig;
11
+ /**
12
+ * - Page title (defaults to config name)
13
+ */
14
+ title?: string | undefined;
15
+ /**
16
+ * Page description (defaults to config description)
17
+ */
18
+ description?: string | undefined;
19
+ /**
20
+ * - Canonical URL for this page
21
+ */
22
+ url?: string | undefined;
23
+ /**
24
+ * Social media preview image URL (defaults to landscape SEO image)
25
+ */
26
+ image?: string | undefined;
27
+ /**
28
+ * - Alt text for social media image
29
+ */
30
+ imageAlt?: string | undefined;
31
+ /**
32
+ * - Open Graph type (default: 'website')
33
+ */
34
+ type?: string | undefined;
35
+ /**
36
+ * Content locale (auto-detected from URL or defaults to config)
37
+ */
38
+ locale?: string | undefined;
39
+ /**
40
+ * Site name for Open Graph (defaults to config name)
41
+ */
42
+ siteName?: string | undefined;
43
+ /**
44
+ * Alternate language URLs for hreflang tags
45
+ */
46
+ alternateUrls?: Record<string, string> | undefined;
47
+ /**
48
+ * Robots meta directives (e.g., 'noindex, nofollow')
49
+ */
50
+ robots?: string | undefined;
51
+ /**
52
+ * Prevent AI training on this page content
53
+ */
54
+ noAiTraining?: boolean | undefined;
55
+ }, {}, "">;
56
+ /**
57
+ * SEO component props
58
+ */
59
+ type SEOProps = {
60
+ /**
61
+ * - Configuration object
62
+ */
63
+ config: import("../config.typedef.js").MetaConfig;
64
+ /**
65
+ * - Page title (defaults to config name)
66
+ */
67
+ title?: string | undefined;
68
+ /**
69
+ * Page description (defaults to config description)
70
+ */
71
+ description?: string | undefined;
72
+ /**
73
+ * - Canonical URL for this page
74
+ */
75
+ url?: string | undefined;
76
+ /**
77
+ * Social media preview image URL (defaults to landscape SEO image)
78
+ */
79
+ image?: string | undefined;
80
+ /**
81
+ * - Alt text for social media image
82
+ */
83
+ imageAlt?: string | undefined;
84
+ /**
85
+ * - Open Graph type (default: 'website')
86
+ */
87
+ type?: string | undefined;
88
+ /**
89
+ * Content locale (auto-detected from URL or defaults to config)
90
+ */
91
+ locale?: string | undefined;
92
+ /**
93
+ * Site name for Open Graph (defaults to config name)
94
+ */
95
+ siteName?: string | undefined;
96
+ /**
97
+ * Alternate language URLs for hreflang tags
98
+ */
99
+ alternateUrls?: Record<string, string> | undefined;
100
+ /**
101
+ * Robots meta directives (e.g., 'noindex, nofollow')
102
+ */
103
+ robots?: string | undefined;
104
+ /**
105
+ * Prevent AI training on this page content
106
+ */
107
+ noAiTraining?: boolean | undefined;
108
+ };
@@ -0,0 +1,3 @@
1
+ export { default as Favicons } from "./components/Favicons.svelte";
2
+ export { default as PWA } from "./components/PWA.svelte";
3
+ export { default as SEO } from "./components/SEO.svelte";
@@ -0,0 +1,3 @@
1
+ export { default as Favicons } from './components/Favicons.svelte';
2
+ export { default as PWA } from './components/PWA.svelte';
3
+ export { default as SEO } from './components/SEO.svelte';
@@ -0,0 +1,98 @@
1
+ declare const _default: {};
2
+ export default _default;
3
+ /**
4
+ * App identity
5
+ */
6
+ export type MetaConfig = {
7
+ /**
8
+ * - Full app name
9
+ */
10
+ name: string;
11
+ /**
12
+ * - Short app name (max 12 characters)
13
+ */
14
+ shortName: string;
15
+ /**
16
+ * - App description for search engines
17
+ *
18
+ * Language and locale
19
+ */
20
+ description: string;
21
+ /**
22
+ * Language configurations
23
+ */
24
+ languages: Record<string, {
25
+ lang: string;
26
+ locale: string;
27
+ }>;
28
+ /**
29
+ * - Default language code
30
+ */
31
+ defaultLanguage: string;
32
+ /**
33
+ * - Default locale
34
+ *
35
+ * PWA theme and colors
36
+ */
37
+ defaultLocale: string;
38
+ /**
39
+ * - Theme color
40
+ */
41
+ backgroundAndThemeColor: string;
42
+ /**
43
+ * - Theme color for browser UI
44
+ */
45
+ themeColor: string;
46
+ /**
47
+ * - Background color
48
+ */
49
+ backgroundColor: string;
50
+ /**
51
+ * - iOS status bar style
52
+ */
53
+ statusBarStyle: string;
54
+ /**
55
+ * - Screen orientation
56
+ */
57
+ orientation: string;
58
+ /**
59
+ * - Disable pinch-to-zoom
60
+ *
61
+ * SEO images
62
+ */
63
+ disablePageZoom: boolean;
64
+ /**
65
+ * - Landscape SEO image URL (1200×630)
66
+ */
67
+ SeoImageLandscape?: string | undefined;
68
+ /**
69
+ * - Square SEO image URL (1200×1200)
70
+ *
71
+ * Favicon images (processed by imagetools)
72
+ */
73
+ SeoImageSquare?: string | undefined;
74
+ /**
75
+ * Processed favicon images
76
+ */
77
+ faviconImages: Array<{
78
+ src: string;
79
+ width: number;
80
+ }>;
81
+ /**
82
+ * Processed apple-touch-icon images
83
+ *
84
+ * Site configuration
85
+ */
86
+ appleTouchIcons: Array<{
87
+ src: string;
88
+ width: number;
89
+ }>;
90
+ /**
91
+ * Routes for sitemap.xml
92
+ */
93
+ siteRoutes: import("./utils/sitemap/typedef.js").SitemapRoute[];
94
+ /**
95
+ * Robots.txt configuration
96
+ */
97
+ robotsConfig: import("./utils/robots/typedef.js").RobotsConfig;
98
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Meta configuration type definitions
3
+ */
4
+
5
+ /**
6
+ * @typedef {Object} MetaConfig
7
+ *
8
+ * App identity
9
+ * @property {string} name - Full app name
10
+ * @property {string} shortName - Short app name (max 12 characters)
11
+ * @property {string} description - App description for search engines
12
+ *
13
+ * Language and locale
14
+ * @property {Record<string, {lang: string, locale: string}>} languages
15
+ * Language configurations
16
+ * @property {string} defaultLanguage - Default language code
17
+ * @property {string} defaultLocale - Default locale
18
+ *
19
+ * PWA theme and colors
20
+ * @property {string} backgroundAndThemeColor - Theme color
21
+ * @property {string} themeColor - Theme color for browser UI
22
+ * @property {string} backgroundColor - Background color
23
+ * @property {string} statusBarStyle - iOS status bar style
24
+ * @property {string} orientation - Screen orientation
25
+ * @property {boolean} disablePageZoom - Disable pinch-to-zoom
26
+ *
27
+ * SEO images
28
+ * @property {string} [SeoImageLandscape] - Landscape SEO image URL (1200×630)
29
+ * @property {string} [SeoImageSquare] - Square SEO image URL (1200×1200)
30
+ *
31
+ * Favicon images (processed by imagetools)
32
+ * @property {Array<{src: string, width: number}>} faviconImages
33
+ * Processed favicon images
34
+ * @property {Array<{src: string, width: number}>} appleTouchIcons
35
+ * Processed apple-touch-icon images
36
+ *
37
+ * Site configuration
38
+ * @property {import('./utils/sitemap/typedef.js').SitemapRoute[]} siteRoutes
39
+ * Routes for sitemap.xml
40
+ * @property {import('./utils/robots/typedef.js').RobotsConfig} robotsConfig
41
+ * Robots.txt configuration
42
+ */
43
+
44
+ export default {};
@@ -0,0 +1,3 @@
1
+ export * from "./config.typedef.js";
2
+ export * from "./utils/robots/typedef.js";
3
+ export * from "./utils/sitemap/typedef.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Type definitions for meta utilities
3
+ *
4
+ * Re-exports all typedefs from utils subfolder for convenient importing
5
+ */
6
+
7
+ // Re-export config typedefs
8
+ export * from './config.typedef.js';
9
+
10
+ // Re-export robots typedefs
11
+ export * from './utils/robots/typedef.js';
12
+
13
+ // Re-export sitemap typedefs
14
+ export * from './utils/sitemap/typedef.js';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Create language utilities configured with your app config
3
+ *
4
+ * @param {Object} config - Configuration object
5
+ * @param {Record<string, {lang: string, locale: string}>} config.languages
6
+ * Language configurations
7
+ * @param {string} config.defaultLanguage - Default language code
8
+ * @param {string} config.name - App name (for %title% injection)
9
+ * @param {string} config.description - App description
10
+ *
11
+ * @returns {{
12
+ * getLangFromPath: Function,
13
+ * injectLang: Function,
14
+ * handleLang: Function
15
+ * }}
16
+ */
17
+ export function createLangUtils(config: {
18
+ languages: Record<string, {
19
+ lang: string;
20
+ locale: string;
21
+ }>;
22
+ defaultLanguage: string;
23
+ name: string;
24
+ description: string;
25
+ }): {
26
+ getLangFromPath: Function;
27
+ injectLang: Function;
28
+ handleLang: Function;
29
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Create language utilities configured with your app config
3
+ *
4
+ * @param {Object} config - Configuration object
5
+ * @param {Record<string, {lang: string, locale: string}>} config.languages
6
+ * Language configurations
7
+ * @param {string} config.defaultLanguage - Default language code
8
+ * @param {string} config.name - App name (for %title% injection)
9
+ * @param {string} config.description - App description
10
+ *
11
+ * @returns {{
12
+ * getLangFromPath: Function,
13
+ * injectLang: Function,
14
+ * handleLang: Function
15
+ * }}
16
+ */
17
+ export function createLangUtils(config) {
18
+ const { languages, defaultLanguage, name, description } = config;
19
+
20
+ /**
21
+ * Extract language code from URL pathname
22
+ *
23
+ * @param {string} pathname - URL pathname (e.g., '/en/shop')
24
+ *
25
+ * @returns {{
26
+ * langCode: string,
27
+ * lang: string,
28
+ * locale: string
29
+ * }}
30
+ */
31
+ function getLangFromPath(pathname) {
32
+ const match = pathname.match(/^\/([a-z]{2}(?:-[a-z]{2})?)\//i);
33
+ const langCode = match ? match[1].toLowerCase() : defaultLanguage;
34
+
35
+ const langConfig = languages[langCode] || languages[defaultLanguage];
36
+
37
+ return {
38
+ langCode,
39
+ lang: langConfig.lang,
40
+ locale: langConfig.locale
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Transform HTML to inject language, title, and description
46
+ *
47
+ * @param {string} html - HTML string with placeholders
48
+ * @param {string} lang - Language code (e.g., 'en-GB')
49
+ *
50
+ * @returns {string} HTML with values injected
51
+ */
52
+ function injectLang(html, lang) {
53
+ return html
54
+ .replace('%lang%', lang)
55
+ .replace('%title%', name)
56
+ .replace('%description%', description);
57
+ }
58
+
59
+ /**
60
+ * SvelteKit hook handler for language detection
61
+ *
62
+ * @param {object} event - SvelteKit event object
63
+ * @param {Function} resolve - SvelteKit resolve function
64
+ *
65
+ * @returns {Promise<Response>}
66
+ */
67
+ async function handleLang(event, resolve) {
68
+ const { langCode, lang, locale } = getLangFromPath(event.url.pathname);
69
+
70
+ event.locals.langCode = langCode;
71
+ event.locals.lang = lang;
72
+ event.locals.locale = locale;
73
+
74
+ return resolve(event, {
75
+ transformPageChunk: ({ html }) => injectLang(html, lang)
76
+ });
77
+ }
78
+
79
+ return {
80
+ getLangFromPath,
81
+ injectLang,
82
+ handleLang
83
+ };
84
+ }
@@ -0,0 +1 @@
1
+ export { generateRobotsTxt, isHostAllowed } from "./robots/robots.js";
@@ -0,0 +1 @@
1
+ export { generateRobotsTxt, isHostAllowed } from './robots/robots.js';
@@ -0,0 +1 @@
1
+ export { generateSitemap } from "./sitemap/sitemap.js";
@@ -0,0 +1 @@
1
+ export { generateSitemap } from './sitemap/sitemap.js';
@@ -0,0 +1,3 @@
1
+ export { createLangUtils } from "./utils/lang.js";
2
+ export { generateSitemap } from "./utils/sitemap.js";
3
+ export { generateRobotsTxt, isHostAllowed } from "./utils/robots.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Meta utilities for SEO, PWA, and favicons
3
+ *
4
+ * @module @hkdigital/lib-core/meta
5
+ */
6
+
7
+ // Language utilities
8
+ export { createLangUtils } from './utils/lang.js';
9
+
10
+ export { generateRobotsTxt, isHostAllowed } from './utils/robots.js';
11
+ export { generateSitemap } from './utils/sitemap.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.93",
3
+ "version": "0.5.94",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -1 +0,0 @@
1
- export { generateRobotsTxt, isHostAllowed } from "./robots/index.js";
@@ -1,5 +0,0 @@
1
- /**
2
- * Public exports for robots.txt utilities
3
- */
4
-
5
- export { generateRobotsTxt, isHostAllowed } from './robots/index.js';
@@ -1 +0,0 @@
1
- export { generateSitemap } from "./sitemap/index.js";
@@ -1,5 +0,0 @@
1
- /**
2
- * Public exports for sitemap utilities
3
- */
4
-
5
- export { generateSitemap } from './sitemap/index.js';
File without changes
File without changes
File without changes