@saasmakers/ui 1.4.38 → 1.4.40

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.
@@ -121,4 +121,4 @@ declare module 'vue' {
121
121
  }
122
122
  }
123
123
 
124
- export * from './bases'
124
+ export {}
package/nuxt.config.ts CHANGED
@@ -4,6 +4,7 @@ import { defineNuxtConfig } from 'nuxt/config'
4
4
  import uno from './uno.config.js'
5
5
 
6
6
  const currentDir = path.dirname(fileURLToPath(import.meta.url))
7
+ const nuxtSeoPatchesDir = path.join(currentDir, 'server/patches/nuxt-seo')
7
8
 
8
9
  export default defineNuxtConfig({
9
10
  modules: [
@@ -117,4 +118,17 @@ export default defineNuxtConfig({
117
118
  shortcuts: uno.shortcuts,
118
119
  theme: uno.theme,
119
120
  },
121
+
122
+ // h3 v2 (Nuxt 4.4+) compatibility shims for @nuxtjs/robots and @nuxtjs/sitemap
123
+ nitro: {
124
+ alias: {
125
+ '@nuxtjs/robots/dist/runtime/server/middleware/injectContext.js': path.join(nuxtSeoPatchesDir, 'robots-injectContext.js'),
126
+ '@nuxtjs/sitemap/dist/runtime/server/routes/sitemap.xsl.js': path.join(nuxtSeoPatchesDir, 'routes/sitemap.xsl.js'),
127
+ '@nuxtjs/sitemap/dist/runtime/server/sitemap/builder/sitemap-index.js': path.join(nuxtSeoPatchesDir, 'sitemap/builder/sitemap-index.js'),
128
+ '@nuxtjs/sitemap/dist/runtime/server/sitemap/builder/sitemap.js': path.join(nuxtSeoPatchesDir, 'sitemap/builder/sitemap.js'),
129
+ '@nuxtjs/sitemap/dist/runtime/server/sitemap/event-handlers.js': path.join(nuxtSeoPatchesDir, 'sitemap/event-handlers.js'),
130
+ '@nuxtjs/sitemap/dist/runtime/server/sitemap/nitro.js': path.join(nuxtSeoPatchesDir, 'sitemap/nitro.js'),
131
+ '@nuxtjs/sitemap/dist/runtime/server/utils/h3-compat.js': path.join(nuxtSeoPatchesDir, 'utils/h3-compat.js'),
132
+ },
133
+ },
120
134
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "1.4.38",
3
+ "version": "1.4.40",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",
@@ -8,56 +8,62 @@
8
8
  "sideEffects": false,
9
9
  "type": "module",
10
10
  "exports": {
11
- ".": "./nuxt.config.ts"
11
+ ".": {
12
+ "types": "./app/types/global.d.ts",
13
+ "default": "./nuxt.config.ts"
14
+ }
12
15
  },
13
16
  "main": "nuxt.config.ts",
14
17
  "types": "app/types/global.d.ts",
15
18
  "files": [
16
19
  "app",
17
20
  "public",
21
+ "server",
18
22
  "uno.config.ts"
19
23
  ],
24
+ "scripts": {
25
+ "lint": "eslint . --max-warnings=0",
26
+ "prepare": "nuxi prepare",
27
+ "typecheck": "nuxi typecheck"
28
+ },
20
29
  "dependencies": {
21
- "@capacitor/preferences": "8.0.1",
22
- "@nuxt/icon": "2.2.3",
23
- "@nuxt/scripts": "0.13.2",
24
- "@nuxtjs/color-mode": "3.5.2",
25
- "@nuxtjs/i18n": "10.4.0",
26
- "@nuxtjs/plausible": "2.0.1",
27
- "@nuxtjs/robots": "5.6.7",
28
- "@nuxtjs/sitemap": "7.5.0",
29
- "@pinia/nuxt": "0.11.3",
30
- "@unhead/vue": "2.0.19",
31
- "@unocss/nuxt": "66.7.0",
32
- "@unocss/reset": "66.7.0",
33
- "@vuelidate/core": "2.0.3",
34
- "@vuelidate/validators": "2.0.4",
35
- "@vueuse/components": "14.3.0",
36
- "@vueuse/core": "14.3.0",
37
- "@vueuse/nuxt": "14.3.0",
38
- "chartist": "1.5.0",
39
- "floating-vue": "5.2.2",
40
- "motion-v": "1.7.4",
41
- "numbro": "2.5.0",
42
- "snarkdown": "2.0.0",
43
- "unocss": "66.7.0",
44
- "vue": "3.5.35",
45
- "vue-router": "4.6.4"
30
+ "@capacitor/preferences": "catalog:",
31
+ "@nuxt/icon": "catalog:",
32
+ "@nuxt/scripts": "catalog:",
33
+ "@nuxtjs/color-mode": "catalog:",
34
+ "@nuxtjs/i18n": "catalog:",
35
+ "@nuxtjs/plausible": "catalog:",
36
+ "@nuxtjs/robots": "catalog:",
37
+ "@nuxtjs/sitemap": "catalog:",
38
+ "@pinia/nuxt": "catalog:",
39
+ "@unhead/vue": "catalog:",
40
+ "@unocss/nuxt": "catalog:",
41
+ "@unocss/reset": "catalog:",
42
+ "@vuelidate/core": "catalog:",
43
+ "@vuelidate/validators": "catalog:",
44
+ "@vueuse/components": "catalog:",
45
+ "@vueuse/core": "catalog:",
46
+ "@vueuse/nuxt": "catalog:",
47
+ "chartist": "catalog:",
48
+ "floating-vue": "catalog:",
49
+ "motion-v": "catalog:",
50
+ "numbro": "catalog:",
51
+ "pinia": "catalog:",
52
+ "snarkdown": "catalog:",
53
+ "unocss": "catalog:",
54
+ "vue": "catalog:",
55
+ "vue-router": "catalog:"
46
56
  },
47
57
  "devDependencies": {
48
- "nuxt": "4.2.2",
49
- "typescript": "5.9.3",
50
- "@saasmakers/shared": "0.2.5"
58
+ "@saasmakers/shared": "workspace:*",
59
+ "nuxt": "catalog:",
60
+ "typescript": "catalog:"
51
61
  },
52
62
  "peerDependencies": {
53
63
  "@saasmakers/shared": "^0.2.0",
54
- "nuxt": "4.2.2"
64
+ "nuxt": "^4.4.0"
55
65
  },
56
66
  "publishConfig": {
57
67
  "access": "public"
58
- },
59
- "scripts": {
60
- "lint": "eslint . --max-warnings=0",
61
- "typecheck": "nuxi typecheck"
62
68
  }
63
- }
69
+ }
@@ -0,0 +1,35 @@
1
+ import { defineEventHandler, getQuery, setHeader } from "h3";
2
+ import { parseQuery } from "ufo";
3
+ import { getPathRobotConfig } from "../composables/getPathRobotConfig.js";
4
+ import { useRuntimeConfigNuxtRobots } from "../composables/useRuntimeConfigNuxtRobots.js";
5
+
6
+ function safeGetQuery(event) {
7
+ try {
8
+ return getQuery(event);
9
+ }
10
+ catch {
11
+ const path = event.path || event.node?.req?.url || "/";
12
+ const searchIndex = path.indexOf("?");
13
+ const search = searchIndex === -1 ? "" : path.slice(searchIndex);
14
+ return parseQuery(search);
15
+ }
16
+ }
17
+
18
+ export default defineEventHandler(async (e) => {
19
+ if (e.path === "/robots.txt" || e.path.startsWith("/__") || e.path.startsWith("/api") || e.path.startsWith("/_nuxt"))
20
+ return;
21
+ const nuxtRobotsConfig = useRuntimeConfigNuxtRobots(e);
22
+ if (nuxtRobotsConfig) {
23
+ const { header } = nuxtRobotsConfig;
24
+ const robotConfig = getPathRobotConfig(e, { skipSiteIndexable: Boolean(safeGetQuery(e)?.mockProductionEnv) });
25
+ if (header) {
26
+ setHeader(e, "X-Robots-Tag", robotConfig.rule);
27
+ }
28
+ e.context.robots = robotConfig;
29
+ if (import.meta.dev) {
30
+ const productionRobotConfig = getPathRobotConfig(e, { skipSiteIndexable: true });
31
+ setHeader(e, "X-Robots-Production", productionRobotConfig.rule);
32
+ e.context.robotsProduction = productionRobotConfig;
33
+ }
34
+ }
35
+ });
@@ -0,0 +1,452 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { safeGetHeader, safeGetQuery, safeSetHeader } from "../utils/h3-compat.js";
3
+ import { getQuery, parseURL, withQuery } from "ufo";
4
+ import { getSiteConfig } from "#site-config/server/composables";
5
+ import { createSitePathResolver } from "#site-config/server/composables/utils";
6
+ import { useSitemapRuntimeConfig, xmlEscape } from "../utils.js";
7
+ export default defineEventHandler(async (e) => {
8
+ const fixPath = createSitePathResolver(e, { absolute: false, withBase: true });
9
+ const { sitemapName: fallbackSitemapName, cacheMaxAgeSeconds, version, xslColumns, xslTips } = useSitemapRuntimeConfig();
10
+ safeSetHeader(e, "Content-Type", "application/xslt+xml");
11
+ if (cacheMaxAgeSeconds)
12
+ safeSetHeader(e, "Cache-Control", `public, max-age=${cacheMaxAgeSeconds}, must-revalidate`);
13
+ else
14
+ safeSetHeader(e, "Cache-Control", `no-cache, no-store`);
15
+ const { name: siteName, url: siteUrl } = getSiteConfig(e);
16
+ const referrer = safeGetHeader(e, "Referer") || "/";
17
+ const referrerPath = parseURL(referrer).pathname;
18
+ const isNotIndexButHasIndex = referrerPath !== "/sitemap.xml" && referrerPath !== "/sitemap_index.xml" && referrerPath.endsWith(".xml");
19
+ const sitemapName = parseURL(referrer).pathname.split("/").pop()?.split("-sitemap")[0] || fallbackSitemapName;
20
+ const title = `${siteName}${sitemapName !== "sitemap.xml" ? ` - ${sitemapName === "sitemap_index.xml" ? "index" : sitemapName}` : ""}`.replace(/&/g, "&");
21
+ const isIndexPage = referrerPath === "/sitemap.xml" || referrerPath === "/sitemap_index.xml";
22
+ const canonicalQuery = getQuery(referrer).canonical;
23
+ const isShowingCanonical = typeof canonicalQuery !== "undefined" && canonicalQuery !== "false";
24
+ const debugUrl = xmlEscape(withQuery("/__sitemap__/debug.json", { sitemap: sitemapName }));
25
+ const devUrl = xmlEscape(referrerPath);
26
+ const prodUrl = xmlEscape(withQuery(referrerPath, { canonical: "" }));
27
+ const fetchErrors = [];
28
+ const xslQuery = safeGetQuery(e);
29
+ if (xslQuery.error_messages) {
30
+ const errorMessages = xslQuery.error_messages;
31
+ const errorUrls = xslQuery.error_urls;
32
+ if (errorMessages) {
33
+ const messages = Array.isArray(errorMessages) ? errorMessages : [errorMessages];
34
+ const urls = Array.isArray(errorUrls) ? errorUrls : errorUrls ? [errorUrls] : [];
35
+ messages.forEach((msg, i) => {
36
+ const errorParts = [xmlEscape(msg)];
37
+ if (urls[i])
38
+ errorParts.push(xmlEscape(urls[i]));
39
+ fetchErrors.push(`<span class="error-item">${errorParts.join(" \u2014 ")}</span>`);
40
+ });
41
+ }
42
+ }
43
+ const hasRuntimeErrors = fetchErrors.length > 0;
44
+ const showDevTools = import.meta.dev && xslTips !== false;
45
+ const hints = [
46
+ `This is an XSL sitemap (CSS for XML). Disable with <code>xsl: false</code>`,
47
+ `View the raw XML by adding <code>?canonical</code> to the URL`,
48
+ `Check <code>/__sitemap__/debug.json</code> for full sitemap diagnostics`
49
+ ];
50
+ const hint = hints[Math.floor(Math.random() * hints.length)];
51
+ let columns = [...xslColumns];
52
+ if (!columns.length) {
53
+ columns = [
54
+ { label: "URL", width: "50%" },
55
+ { label: "Images", width: "25%", select: "count(image:image)" },
56
+ { label: "Last Updated", width: "25%", select: "concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))" }
57
+ ];
58
+ }
59
+ return `<?xml version="1.0" encoding="UTF-8"?>
60
+ <xsl:stylesheet version="2.0"
61
+ xmlns:html="http://www.w3.org/TR/REC-html40"
62
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
63
+ xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
64
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
65
+ xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
66
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
67
+ <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
68
+ <xsl:template match="/">
69
+ <html xmlns="http://www.w3.org/1999/xhtml">
70
+ <head>
71
+ <title>XML Sitemap</title>
72
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
73
+ <style type="text/css">
74
+ :root {
75
+ --accent: #00dc82;
76
+ --accent-hover: #00b86b;
77
+ --bg: #0a0a0a;
78
+ --bg-elevated: #141414;
79
+ --bg-subtle: #1a1a1a;
80
+ --border: #262626;
81
+ --border-subtle: #1f1f1f;
82
+ --text: #e5e5e5;
83
+ --text-muted: #737373;
84
+ --text-faint: #525252;
85
+ --error: #ef4444;
86
+ --error-bg: rgba(239,68,68,0.1);
87
+ --warning: #f59e0b;
88
+ }
89
+ * { box-sizing: border-box; }
90
+ body {
91
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
92
+ font-size: 13px;
93
+ color: var(--text);
94
+ background: var(--bg);
95
+ margin: 0;
96
+ padding: 0;
97
+ line-height: 1.6;
98
+ -webkit-font-smoothing: antialiased;
99
+ }
100
+ a { color: inherit; transition: color 0.15s; }
101
+ a:hover { color: var(--accent); }
102
+
103
+ /* Debug bar (dev only) */
104
+ .debug-bar {
105
+ position: fixed;
106
+ bottom: 0.75rem;
107
+ left: 50%;
108
+ transform: translateX(-50%);
109
+ width: 80%;
110
+ background: var(--bg-elevated);
111
+ border: 1px solid var(--border);
112
+ border-radius: 10px;
113
+ padding: 0 1rem;
114
+ height: 2.5rem;
115
+ display: flex;
116
+ align-items: center;
117
+ gap: 0.75rem;
118
+ z-index: 100;
119
+ font-size: 11px;
120
+ }
121
+ .debug-bar-brand {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 0.5rem;
125
+ color: var(--text-muted);
126
+ text-decoration: none;
127
+ }
128
+ .debug-bar-brand:hover { color: var(--text); }
129
+ .debug-bar-brand svg { flex-shrink: 0; }
130
+ .debug-bar-hint {
131
+ color: var(--text-faint);
132
+ margin-right: auto;
133
+ white-space: nowrap;
134
+ overflow: hidden;
135
+ text-overflow: ellipsis;
136
+ }
137
+ .debug-bar-hint code {
138
+ background: var(--bg-subtle);
139
+ padding: 0.1rem 0.3rem;
140
+ border-radius: 3px;
141
+ font-size: 10px;
142
+ }
143
+ .mode-badge {
144
+ font-size: 9px;
145
+ font-weight: 600;
146
+ text-transform: uppercase;
147
+ letter-spacing: 0.04em;
148
+ padding: 0.2rem 0.4rem;
149
+ border-radius: 3px;
150
+ }
151
+ .mode-dev { background: rgba(245,158,11,0.15); color: var(--warning); }
152
+ .mode-prod { background: rgba(0,220,130,0.12); color: var(--accent); }
153
+ .mode-toggle {
154
+ display: inline-flex;
155
+ border-radius: 4px;
156
+ overflow: hidden;
157
+ background: var(--bg-subtle);
158
+ padding: 2px;
159
+ gap: 1px;
160
+ }
161
+ .mode-toggle a {
162
+ padding: 0.2rem 0.4rem;
163
+ font-size: 9px;
164
+ font-weight: 500;
165
+ text-decoration: none;
166
+ color: var(--text-muted);
167
+ border-radius: 2px;
168
+ transition: all 0.15s;
169
+ }
170
+ .mode-toggle a:hover { color: var(--text); }
171
+ .mode-toggle a.active {
172
+ background: var(--accent);
173
+ color: #0a0a0a;
174
+ }
175
+ .btn {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ gap: 0.25rem;
179
+ padding: 0.25rem 0.5rem;
180
+ border-radius: 4px;
181
+ text-decoration: none;
182
+ font-size: 10px;
183
+ font-weight: 500;
184
+ transition: all 0.15s;
185
+ }
186
+ .btn-primary {
187
+ background: var(--accent);
188
+ color: #0a0a0a;
189
+ }
190
+ .btn-primary:hover { background: var(--accent-hover); color: #0a0a0a; }
191
+ .btn svg { width: 12px; height: 12px; }
192
+
193
+ /* Error banner */
194
+ .error-banner {
195
+ background: var(--error-bg);
196
+ border-bottom: 1px solid rgba(239,68,68,0.2);
197
+ padding: 0.75rem 1.5rem;
198
+ color: #fca5a5;
199
+ font-size: 12px;
200
+ }
201
+ .error-banner strong { color: var(--error); }
202
+ .error-item { display: block; margin-top: 0.375rem; color: #fca5a5; }
203
+ .error-debug-link {
204
+ display: inline-flex;
205
+ align-items: center;
206
+ gap: 0.25rem;
207
+ margin-top: 0.625rem;
208
+ padding: 0.25rem 0.5rem;
209
+ background: var(--error);
210
+ color: #fff;
211
+ border-radius: 4px;
212
+ font-size: 11px;
213
+ font-weight: 500;
214
+ text-decoration: none;
215
+ transition: background 0.15s;
216
+ }
217
+ .error-debug-link:hover { background: #dc2626; color: #fff; }
218
+
219
+ /* Main content */
220
+ .container {
221
+ max-width: 1200px;
222
+ margin: 0 auto;
223
+ padding: 1.5rem;
224
+ }
225
+ .header {
226
+ margin-bottom: 1.25rem;
227
+ }
228
+ .header h1 {
229
+ font-size: 1rem;
230
+ font-weight: 600;
231
+ margin: 0 0 0.25rem 0;
232
+ color: var(--text);
233
+ }
234
+ .header-meta {
235
+ color: var(--text-muted);
236
+ font-size: 12px;
237
+ }
238
+ .header-meta a {
239
+ color: var(--text-muted);
240
+ text-decoration: underline;
241
+ text-decoration-color: var(--border);
242
+ text-underline-offset: 2px;
243
+ }
244
+ .header-meta a:hover { color: var(--accent); text-decoration-color: var(--accent); }
245
+
246
+ /* Table */
247
+ .table-wrap {
248
+ border: 1px solid var(--border);
249
+ border-radius: 8px;
250
+ overflow: hidden;
251
+ background: var(--bg-elevated);
252
+ }
253
+ table {
254
+ width: 100%;
255
+ border-collapse: collapse;
256
+ }
257
+ th {
258
+ text-align: left;
259
+ padding: 0.625rem 1rem;
260
+ font-size: 10px;
261
+ font-weight: 600;
262
+ text-transform: uppercase;
263
+ letter-spacing: 0.05em;
264
+ color: var(--text-muted);
265
+ background: var(--bg-subtle);
266
+ border-bottom: 1px solid var(--border);
267
+ }
268
+ td {
269
+ padding: 0.5rem 1rem;
270
+ border-bottom: 1px solid var(--border-subtle);
271
+ font-size: 12px;
272
+ color: var(--text);
273
+ }
274
+ tr:last-child td { border-bottom: none; }
275
+ tr:hover td { background: rgba(255,255,255,0.02); }
276
+ td a {
277
+ text-decoration: none;
278
+ word-break: break-all;
279
+ color: var(--text);
280
+ }
281
+ td a:hover { color: var(--accent); }
282
+ .inline-warning {
283
+ font-size: 11px;
284
+ color: var(--warning);
285
+ margin-top: 0.25rem;
286
+ line-height: 1.4;
287
+ }
288
+ .inline-warning::before {
289
+ content: "\u26A0 ";
290
+ }
291
+ .count {
292
+ display: inline-block;
293
+ min-width: 1.25rem;
294
+ padding: 0.125rem 0.375rem;
295
+ background: var(--bg-subtle);
296
+ border-radius: 4px;
297
+ text-align: center;
298
+ font-size: 11px;
299
+ color: var(--text-muted);
300
+ font-variant-numeric: tabular-nums;
301
+ }
302
+ .count:empty::before { content: "0"; }
303
+
304
+ /* Light mode */
305
+ @media (prefers-color-scheme: light) {
306
+ :root {
307
+ --accent: #00a963;
308
+ --accent-hover: #008f54;
309
+ --bg: #ffffff;
310
+ --bg-elevated: #f5f5f5;
311
+ --bg-subtle: #ebebeb;
312
+ --border: #d4d4d4;
313
+ --border-subtle: #e5e5e5;
314
+ --text: #171717;
315
+ --text-muted: #525252;
316
+ --text-faint: #737373;
317
+ --error: #dc2626;
318
+ --error-bg: rgba(220,38,38,0.08);
319
+ --warning: #b45309;
320
+ }
321
+ tr:hover td { background: rgba(0,0,0,0.02); }
322
+ .btn-primary { color: #fff; }
323
+ .btn-primary:hover { color: #fff; }
324
+ .mode-toggle a.active { color: #fff; }
325
+ .error-banner { color: #991b1b; }
326
+ .error-item { color: #b91c1c; }
327
+ .error-debug-link { color: #fff; }
328
+ .error-debug-link:hover { color: #fff; }
329
+ }
330
+
331
+ .debug-bar-version {
332
+ color: var(--text-faint);
333
+ font-size: 10px;
334
+ }
335
+
336
+ /* Responsive */
337
+ @media (max-width: 640px) {
338
+ .debug-bar { padding: 0 0.75rem; gap: 0.5rem; width: 95%; }
339
+ .debug-bar-brand span { display: none; }
340
+ .debug-bar-hint { display: none; }
341
+ .debug-bar-version { display: none; }
342
+ .mode-badge { display: none; }
343
+ .container { padding: 1rem; }
344
+ th, td { padding: 0.5rem 0.75rem; }
345
+ }
346
+ ${showDevTools ? "body { padding-bottom: 3.5rem; }" : ""}
347
+ </style>
348
+ </head>
349
+ <body>
350
+ ${hasRuntimeErrors ? `<div class="error-banner">
351
+ <strong>Sitemap Generation Errors</strong>
352
+ ${fetchErrors.join("")}
353
+ <a href="${debugUrl}" target="_blank" class="error-debug-link">View Debug Info \u2192</a>
354
+ </div>` : ""}
355
+ <div class="container">
356
+ <div class="header">
357
+ <h1>${xmlEscape(title)}</h1>
358
+ <div class="header-meta">
359
+ ${isNotIndexButHasIndex ? `Part of <a href="${xmlEscape(fixPath("/sitemap_index.xml"))}">${xmlEscape(fixPath("/sitemap_index.xml"))}</a> \xB7 ` : ""}
360
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0">
361
+ <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> sitemaps
362
+ </xsl:if>
363
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1">
364
+ <xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs
365
+ </xsl:if>
366
+ </div>
367
+ </div>
368
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0">
369
+ <div class="table-wrap">
370
+ <table>
371
+ <thead>
372
+ <tr>
373
+ <th style="width:70%">Sitemap</th>
374
+ <th style="width:30%">Last Modified</th>
375
+ </tr>
376
+ </thead>
377
+ <tbody>
378
+ <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
379
+ <xsl:variable name="sitemapURL">
380
+ <xsl:value-of select="sitemap:loc"/>
381
+ </xsl:variable>
382
+ <tr>
383
+ <td>
384
+ <a href="{$sitemapURL}">
385
+ <xsl:value-of select="sitemap:loc"/>
386
+ </a>
387
+ </td>
388
+ <td>
389
+ <xsl:value-of
390
+ select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
391
+ </td>
392
+ </tr>
393
+ </xsl:for-each>
394
+ </tbody>
395
+ </table>
396
+ </div>
397
+ </xsl:if>
398
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1">
399
+ <div class="table-wrap">
400
+ <table>
401
+ <thead>
402
+ <tr>
403
+ ${columns.map((c) => `<th style="width:${c.width}">${c.label}</th>`).join("\n")}
404
+ </tr>
405
+ </thead>
406
+ <tbody>
407
+ <xsl:for-each select="sitemap:urlset/sitemap:url">
408
+ <tr>
409
+ <td>
410
+ <xsl:variable name="itemURL">
411
+ <xsl:value-of select="sitemap:loc"/>
412
+ </xsl:variable>
413
+ <a href="{$itemURL}">
414
+ <xsl:value-of select="sitemap:loc"/>
415
+ </a>
416
+ ${showDevTools ? `<xsl:for-each select="comment()[starts-with(normalize-space(.), 'WARN:')]">
417
+ <div class="inline-warning">
418
+ <xsl:value-of select="substring-after(normalize-space(.), 'WARN:')"/>
419
+ </div>
420
+ </xsl:for-each>` : ""}
421
+ </td>
422
+ ${columns.filter((c) => c.label !== "URL").map((c) => `<td><span class="count"><xsl:value-of select="${c.select}"/></span></td>`).join("\n")}
423
+ </tr>
424
+ </xsl:for-each>
425
+ </tbody>
426
+ </table>
427
+ </div>
428
+ </xsl:if>
429
+ </div>
430
+ ${showDevTools ? `<div class="debug-bar">
431
+ <a href="${xmlEscape(fixPath("/sitemap_index.xml"))}" class="debug-bar-brand">
432
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 32 32"><path fill="#00dc82" d="M4 26h4v4H4zm10 0h4v4h-4zm10 0h4v4h-4zm1-10h-8v-2h-2v2H7a2 2 0 0 0-2 2v6h2v-6h8v6h2v-6h8v6h2v-6a2 2 0 0 0-2-2zM9 2v10h14V2zm2 2h2v6h-2zm10 6h-6V4h6z"/></svg>
433
+ <span>Sitemap Debug Bar</span>
434
+ </a>
435
+ <span class="debug-bar-version">v${version} \xB7 ${xmlEscape(siteUrl)}</span>
436
+ <span class="debug-bar-hint">Hint: ${hint}</span>
437
+ ${isIndexPage ? `<span class="mode-badge ${isShowingCanonical ? "mode-prod" : "mode-dev"}">${isShowingCanonical ? "Prod" : "Dev"}</span>
438
+ <div class="mode-toggle">
439
+ <a href="${isShowingCanonical ? devUrl : "#"}" class="${!isShowingCanonical ? "active" : ""}">Dev</a>
440
+ <a href="${!isShowingCanonical ? prodUrl : "#"}" class="${isShowingCanonical ? "active" : ""}">Prod</a>
441
+ </div>` : ""}
442
+ <a href="${debugUrl}" target="_blank" class="btn btn-primary">
443
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
444
+ Debug
445
+ </a>
446
+ </div>` : ""}
447
+ </body>
448
+ </html>
449
+ </xsl:template>
450
+ </xsl:stylesheet>
451
+ `;
452
+ });