@webnumseoagent/next 0.1.8 → 0.1.10

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.
@@ -17,11 +17,15 @@ function normalize(entries) {
17
17
  let lastmod;
18
18
  if (e.lastModified)
19
19
  lastmod = e.lastModified instanceof Date ? e.lastModified.toISOString() : String(e.lastModified);
20
+ const images = Array.isArray(e.images)
21
+ ? e.images.filter((i) => typeof i === "string" && !!i)
22
+ : [];
20
23
  out.push({
21
24
  loc: e.url,
22
25
  lastmod,
23
26
  changefreq: typeof e.changeFrequency === "string" ? e.changeFrequency : undefined,
24
27
  priority: typeof e.priority === "number" ? e.priority : undefined,
28
+ images: images.length ? images : undefined,
25
29
  });
26
30
  }
27
31
  return out;
@@ -63,6 +67,7 @@ function pi(origin) {
63
67
  return `<?xml version="1.0" encoding="UTF-8"?>\n<?xml-stylesheet type="text/xsl" href="${escapeXml(origin + "/sitemap.xsl")}"?>\n`;
64
68
  }
65
69
  function urlsetXml(origin, entries) {
70
+ const hasImages = entries.some((e) => e.images && e.images.length);
66
71
  const body = entries
67
72
  .map((e) => {
68
73
  const parts = [` <loc>${escapeXml(e.loc)}</loc>`];
@@ -72,10 +77,15 @@ function urlsetXml(origin, entries) {
72
77
  parts.push(` <changefreq>${escapeXml(e.changefreq)}</changefreq>`);
73
78
  if (typeof e.priority === "number")
74
79
  parts.push(` <priority>${e.priority}</priority>`);
80
+ for (const img of e.images ?? [])
81
+ parts.push(` <image:image><image:loc>${escapeXml(img)}</image:loc></image:image>`);
75
82
  return ` <url>\n${parts.join("\n")}\n </url>`;
76
83
  })
77
84
  .join("\n");
78
- return pi(origin) + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${body}\n</urlset>\n`;
85
+ const ns = hasImages
86
+ ? `xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"`
87
+ : `xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"`;
88
+ return pi(origin) + `<urlset ${ns}>\n${body}\n</urlset>\n`;
79
89
  }
80
90
  function indexXml(origin, groups) {
81
91
  const body = groups
@@ -1 +1 @@
1
- export declare const SITEMAP_XSL = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"1.0\"\n xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n xmlns:s=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n<xsl:output method=\"html\" encoding=\"UTF-8\" indent=\"yes\"/>\n<xsl:template match=\"/\">\n<html lang=\"ru\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n<title>XML-\u043A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430</title>\n<style>\n *{box-sizing:border-box}\n body{font-family:-apple-system,system-ui,'Segoe UI',Roboto,sans-serif;color:#1e293b;margin:0;background:#f8fafc}\n .head{background:#2563eb;color:#fff;padding:28px 40px}\n .head h1{margin:0 0 8px;font-size:26px;font-weight:600}\n .head p{margin:0;font-size:14px;opacity:.92;max-width:920px;line-height:1.55}\n .head a{color:#fff}\n .wrap{padding:24px 40px}\n .count{color:#475569;font-size:14px;margin:0 0 14px}\n table{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}\n th{background:#eff2f7;color:#334155;text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.03em;padding:12px 16px}\n td{padding:11px 16px;font-size:14px;border-top:1px solid #f1f5f9}\n tr:hover td{background:#f8fafc}\n td a{color:#2563eb;text-decoration:none;word-break:break-all}\n td a:hover{text-decoration:underline}\n .n{width:80px;color:#64748b;text-align:right}\n .d{width:140px;color:#64748b;white-space:nowrap}\n</style>\n</head>\n<body>\n<div class=\"head\">\n <h1>XML-\u043A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430</h1>\n <p>\u0421\u043F\u0438\u0441\u043E\u043A \u0441\u0442\u0440\u0430\u043D\u0438\u0446 \u0434\u043B\u044F \u043F\u043E\u0438\u0441\u043A\u043E\u0432\u044B\u0445 \u0441\u0438\u0441\u0442\u0435\u043C. \u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u043E\u0439 <b>SEO Agent</b>. \u041F\u043E\u0434\u0440\u043E\u0431\u043D\u0435\u0435 \u043E\u0431 <a href=\"https://www.sitemaps.org/\">XML-\u043A\u0430\u0440\u0442\u0430\u0445 \u0441\u0430\u0439\u0442\u0430</a>.</p>\n</div>\n<div class=\"wrap\">\n<xsl:choose>\n <xsl:when test=\"s:sitemapindex\">\n <p class=\"count\">\u0418\u043D\u0434\u0435\u043A\u0441\u043D\u044B\u0439 \u0444\u0430\u0439\u043B \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 <b><xsl:value-of select=\"count(s:sitemapindex/s:sitemap)\"/></b> \u043A\u0430\u0440\u0442(\u044B) \u0441\u0430\u0439\u0442\u0430.</p>\n <table>\n <tr><th>\u041A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430</th></tr>\n <xsl:for-each select=\"s:sitemapindex/s:sitemap\">\n <tr><td><a href=\"{s:loc}\"><xsl:value-of select=\"s:loc\"/></a></td></tr>\n </xsl:for-each>\n </table>\n </xsl:when>\n <xsl:otherwise>\n <p class=\"count\">\u042D\u0442\u0430 \u043A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 <b><xsl:value-of select=\"count(s:urlset/s:url)\"/></b> URL.</p>\n <table>\n <tr><th class=\"n\">#</th><th>URL</th><th class=\"d\">\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0438\u0437\u043C.</th></tr>\n <xsl:for-each select=\"s:urlset/s:url\">\n <tr>\n <td class=\"n\"><xsl:value-of select=\"position()\"/></td>\n <td><a href=\"{s:loc}\"><xsl:value-of select=\"s:loc\"/></a></td>\n <td class=\"d\"><xsl:value-of select=\"substring(s:lastmod,1,10)\"/></td>\n </tr>\n </xsl:for-each>\n </table>\n </xsl:otherwise>\n</xsl:choose>\n</div>\n</body>\n</html>\n</xsl:template>\n</xsl:stylesheet>\n";
1
+ export declare const SITEMAP_XSL = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"1.0\"\n xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n xmlns:s=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\">\n<xsl:output method=\"html\" encoding=\"UTF-8\" indent=\"yes\"/>\n<xsl:template match=\"/\">\n<html lang=\"ru\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n<title>XML-\u043A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430</title>\n<style>\n *{box-sizing:border-box}\n body{font-family:-apple-system,system-ui,'Segoe UI',Roboto,sans-serif;color:#1e293b;margin:0;background:#f8fafc}\n .head{background:#2563eb;color:#fff;padding:28px 40px}\n .head h1{margin:0 0 8px;font-size:26px;font-weight:600}\n .head p{margin:0;font-size:14px;opacity:.92;max-width:920px;line-height:1.55}\n .head a{color:#fff}\n .wrap{padding:24px 40px}\n .count{color:#475569;font-size:14px;margin:0 0 14px}\n .back{margin:0 0 12px}\n .back a{color:#2563eb;text-decoration:none;font-size:14px;font-weight:500}\n .back a:hover{text-decoration:underline}\n table{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}\n th{background:#eff2f7;color:#334155;text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.03em;padding:12px 16px}\n td{padding:11px 16px;font-size:14px;border-top:1px solid #f1f5f9}\n tr:hover td{background:#f8fafc}\n td a{color:#2563eb;text-decoration:none;word-break:break-all}\n td a:hover{text-decoration:underline}\n .n{width:80px;color:#64748b;text-align:right}\n .i{width:120px;color:#64748b;text-align:center}\n .d{width:140px;color:#64748b;white-space:nowrap}\n</style>\n</head>\n<body>\n<div class=\"head\">\n <h1>XML-\u043A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430</h1>\n <p>\u0421\u043F\u0438\u0441\u043E\u043A \u0441\u0442\u0440\u0430\u043D\u0438\u0446 \u0434\u043B\u044F \u043F\u043E\u0438\u0441\u043A\u043E\u0432\u044B\u0445 \u0441\u0438\u0441\u0442\u0435\u043C. \u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u043E\u0439 <b>SEO Agent</b>. \u041F\u043E\u0434\u0440\u043E\u0431\u043D\u0435\u0435 \u043E\u0431 <a href=\"https://www.sitemaps.org/\">XML-\u043A\u0430\u0440\u0442\u0430\u0445 \u0441\u0430\u0439\u0442\u0430</a>.</p>\n</div>\n<div class=\"wrap\">\n<xsl:choose>\n <xsl:when test=\"s:sitemapindex\">\n <p class=\"count\">\u0418\u043D\u0434\u0435\u043A\u0441\u043D\u044B\u0439 \u0444\u0430\u0439\u043B \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 <b><xsl:value-of select=\"count(s:sitemapindex/s:sitemap)\"/></b> \u043A\u0430\u0440\u0442(\u044B) \u0441\u0430\u0439\u0442\u0430.</p>\n <table>\n <tr><th>\u041A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430</th></tr>\n <xsl:for-each select=\"s:sitemapindex/s:sitemap\">\n <tr><td><a href=\"{s:loc}\"><xsl:value-of select=\"s:loc\"/></a></td></tr>\n </xsl:for-each>\n </table>\n </xsl:when>\n <xsl:otherwise>\n <p class=\"back\"><a href=\"/sitemap.xml\">\u2190 \u0418\u043D\u0434\u0435\u043A\u0441 \u043A\u0430\u0440\u0442\u044B \u0441\u0430\u0439\u0442\u0430</a></p>\n <p class=\"count\">\u042D\u0442\u0430 \u043A\u0430\u0440\u0442\u0430 \u0441\u0430\u0439\u0442\u0430 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 <b><xsl:value-of select=\"count(s:urlset/s:url)\"/></b> URL.</p>\n <table>\n <tr><th class=\"n\">#</th><th>URL</th><th class=\"i\">\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F</th><th class=\"d\">\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u0438\u0437\u043C.</th></tr>\n <xsl:for-each select=\"s:urlset/s:url\">\n <tr>\n <td class=\"n\"><xsl:value-of select=\"position()\"/></td>\n <td><a href=\"{s:loc}\"><xsl:value-of select=\"s:loc\"/></a></td>\n <td class=\"i\"><xsl:value-of select=\"count(image:image)\"/></td>\n <td class=\"d\"><xsl:value-of select=\"substring(s:lastmod,1,10)\"/></td>\n </tr>\n </xsl:for-each>\n </table>\n </xsl:otherwise>\n</xsl:choose>\n</div>\n</body>\n</html>\n</xsl:template>\n</xsl:stylesheet>\n";
@@ -6,7 +6,8 @@ exports.SITEMAP_XSL = void 0;
6
6
  exports.SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
7
7
  <xsl:stylesheet version="1.0"
8
8
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
9
- xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9">
9
+ xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9"
10
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
10
11
  <xsl:output method="html" encoding="UTF-8" indent="yes"/>
11
12
  <xsl:template match="/">
12
13
  <html lang="ru">
@@ -23,6 +24,9 @@ exports.SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
23
24
  .head a{color:#fff}
24
25
  .wrap{padding:24px 40px}
25
26
  .count{color:#475569;font-size:14px;margin:0 0 14px}
27
+ .back{margin:0 0 12px}
28
+ .back a{color:#2563eb;text-decoration:none;font-size:14px;font-weight:500}
29
+ .back a:hover{text-decoration:underline}
26
30
  table{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}
27
31
  th{background:#eff2f7;color:#334155;text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.03em;padding:12px 16px}
28
32
  td{padding:11px 16px;font-size:14px;border-top:1px solid #f1f5f9}
@@ -30,6 +34,7 @@ exports.SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
30
34
  td a{color:#2563eb;text-decoration:none;word-break:break-all}
31
35
  td a:hover{text-decoration:underline}
32
36
  .n{width:80px;color:#64748b;text-align:right}
37
+ .i{width:120px;color:#64748b;text-align:center}
33
38
  .d{width:140px;color:#64748b;white-space:nowrap}
34
39
  </style>
35
40
  </head>
@@ -50,13 +55,15 @@ exports.SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
50
55
  </table>
51
56
  </xsl:when>
52
57
  <xsl:otherwise>
58
+ <p class="back"><a href="/sitemap.xml">← Индекс карты сайта</a></p>
53
59
  <p class="count">Эта карта сайта содержит <b><xsl:value-of select="count(s:urlset/s:url)"/></b> URL.</p>
54
60
  <table>
55
- <tr><th class="n">#</th><th>URL</th><th class="d">Последнее изм.</th></tr>
61
+ <tr><th class="n">#</th><th>URL</th><th class="i">Изображения</th><th class="d">Последнее изм.</th></tr>
56
62
  <xsl:for-each select="s:urlset/s:url">
57
63
  <tr>
58
64
  <td class="n"><xsl:value-of select="position()"/></td>
59
65
  <td><a href="{s:loc}"><xsl:value-of select="s:loc"/></a></td>
66
+ <td class="i"><xsl:value-of select="count(image:image)"/></td>
60
67
  <td class="d"><xsl:value-of select="substring(s:lastmod,1,10)"/></td>
61
68
  </tr>
62
69
  </xsl:for-each>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webnumseoagent/next",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Runtime SEO adapter + installer for Next.js App Router sites (SEO Agent platform)",
5
5
  "main": "dist/client.js",
6
6
  "types": "dist/client.d.ts",
package/sitemap-build.ts CHANGED
@@ -17,19 +17,33 @@ interface NEntry {
17
17
  lastmod?: string;
18
18
  changefreq?: string;
19
19
  priority?: number;
20
+ images?: string[];
20
21
  }
21
22
 
23
+ // Запись карты может нести доп. поля (напр. images) сверх типа Next — читаем через каст.
24
+ type RawEntry = {
25
+ url?: unknown;
26
+ lastModified?: string | Date;
27
+ changeFrequency?: string;
28
+ priority?: number;
29
+ images?: unknown;
30
+ };
31
+
22
32
  function normalize(entries: MetadataRoute.Sitemap): NEntry[] {
23
33
  const out: NEntry[] = [];
24
- for (const e of entries) {
34
+ for (const e of entries as RawEntry[]) {
25
35
  if (!e || typeof e.url !== "string") continue;
26
36
  let lastmod: string | undefined;
27
37
  if (e.lastModified) lastmod = e.lastModified instanceof Date ? e.lastModified.toISOString() : String(e.lastModified);
38
+ const images = Array.isArray(e.images)
39
+ ? e.images.filter((i: unknown): i is string => typeof i === "string" && !!i)
40
+ : [];
28
41
  out.push({
29
42
  loc: e.url,
30
43
  lastmod,
31
44
  changefreq: typeof e.changeFrequency === "string" ? e.changeFrequency : undefined,
32
45
  priority: typeof e.priority === "number" ? e.priority : undefined,
46
+ images: images.length ? images : undefined,
33
47
  });
34
48
  }
35
49
  return out;
@@ -73,16 +87,21 @@ function pi(origin: string): string {
73
87
  }
74
88
 
75
89
  function urlsetXml(origin: string, entries: NEntry[]): string {
90
+ const hasImages = entries.some((e) => e.images && e.images.length);
76
91
  const body = entries
77
92
  .map((e) => {
78
93
  const parts = [` <loc>${escapeXml(e.loc)}</loc>`];
79
94
  if (e.lastmod) parts.push(` <lastmod>${escapeXml(e.lastmod)}</lastmod>`);
80
95
  if (e.changefreq) parts.push(` <changefreq>${escapeXml(e.changefreq)}</changefreq>`);
81
96
  if (typeof e.priority === "number") parts.push(` <priority>${e.priority}</priority>`);
97
+ for (const img of e.images ?? []) parts.push(` <image:image><image:loc>${escapeXml(img)}</image:loc></image:image>`);
82
98
  return ` <url>\n${parts.join("\n")}\n </url>`;
83
99
  })
84
100
  .join("\n");
85
- return pi(origin) + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${body}\n</urlset>\n`;
101
+ const ns = hasImages
102
+ ? `xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"`
103
+ : `xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"`;
104
+ return pi(origin) + `<urlset ${ns}>\n${body}\n</urlset>\n`;
86
105
  }
87
106
 
88
107
  function indexXml(origin: string, groups: string[]): string {
package/sitemap-xsl.ts CHANGED
@@ -3,7 +3,8 @@
3
3
  export const SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
4
4
  <xsl:stylesheet version="1.0"
5
5
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
6
- xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9">
6
+ xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9"
7
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
7
8
  <xsl:output method="html" encoding="UTF-8" indent="yes"/>
8
9
  <xsl:template match="/">
9
10
  <html lang="ru">
@@ -20,6 +21,9 @@ export const SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
20
21
  .head a{color:#fff}
21
22
  .wrap{padding:24px 40px}
22
23
  .count{color:#475569;font-size:14px;margin:0 0 14px}
24
+ .back{margin:0 0 12px}
25
+ .back a{color:#2563eb;text-decoration:none;font-size:14px;font-weight:500}
26
+ .back a:hover{text-decoration:underline}
23
27
  table{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}
24
28
  th{background:#eff2f7;color:#334155;text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.03em;padding:12px 16px}
25
29
  td{padding:11px 16px;font-size:14px;border-top:1px solid #f1f5f9}
@@ -27,6 +31,7 @@ export const SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
27
31
  td a{color:#2563eb;text-decoration:none;word-break:break-all}
28
32
  td a:hover{text-decoration:underline}
29
33
  .n{width:80px;color:#64748b;text-align:right}
34
+ .i{width:120px;color:#64748b;text-align:center}
30
35
  .d{width:140px;color:#64748b;white-space:nowrap}
31
36
  </style>
32
37
  </head>
@@ -47,13 +52,15 @@ export const SITEMAP_XSL = `<?xml version="1.0" encoding="UTF-8"?>
47
52
  </table>
48
53
  </xsl:when>
49
54
  <xsl:otherwise>
55
+ <p class="back"><a href="/sitemap.xml">← Индекс карты сайта</a></p>
50
56
  <p class="count">Эта карта сайта содержит <b><xsl:value-of select="count(s:urlset/s:url)"/></b> URL.</p>
51
57
  <table>
52
- <tr><th class="n">#</th><th>URL</th><th class="d">Последнее изм.</th></tr>
58
+ <tr><th class="n">#</th><th>URL</th><th class="i">Изображения</th><th class="d">Последнее изм.</th></tr>
53
59
  <xsl:for-each select="s:urlset/s:url">
54
60
  <tr>
55
61
  <td class="n"><xsl:value-of select="position()"/></td>
56
62
  <td><a href="{s:loc}"><xsl:value-of select="s:loc"/></a></td>
63
+ <td class="i"><xsl:value-of select="count(image:image)"/></td>
57
64
  <td class="d"><xsl:value-of select="substring(s:lastmod,1,10)"/></td>
58
65
  </tr>
59
66
  </xsl:for-each>