@cocreate/sitemap 1.1.0 → 1.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.
- package/CHANGELOG.md +14 -0
- package/package.json +5 -2
- package/src/index.js +168 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.2.0](https://github.com/CoCreate-app/CoCreate-sitemap/compare/v1.1.0...v1.2.0) (2024-08-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add sitemap to sitemapindex if not already in index ([0ff6911](https://github.com/CoCreate-app/CoCreate-sitemap/commit/0ff6911fe81eeb05295639fb984401d3e76b90ad))
|
|
7
|
+
* noindex metatag return and handling sitemap generation ([058116a](https://github.com/CoCreate-app/CoCreate-sitemap/commit/058116a34c44100bfa4ea1b20afe13fd489bc642))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* added node-html-parser ([397c590](https://github.com/CoCreate-app/CoCreate-sitemap/commit/397c5908af1149d2d60d7c431a8a0f1bd60789f4))
|
|
13
|
+
* parseHtml function to read and generate nested sitemaps using sitemap-* attributes and metatags ([64edbdb](https://github.com/CoCreate-app/CoCreate-sitemap/commit/64edbdbdcee6f64bc5080997557f9691f072381c))
|
|
14
|
+
|
|
1
15
|
# [1.1.0](https://github.com/CoCreate-app/CoCreate-sitemap/compare/v1.0.0...v1.1.0) (2024-08-24)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/sitemap",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A simple sitemap component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sitemap",
|
|
@@ -43,5 +43,8 @@
|
|
|
43
43
|
"type": "GitHub Sponsors ❤",
|
|
44
44
|
"url": "https://github.com/sponsors/CoCreate-app"
|
|
45
45
|
},
|
|
46
|
-
"main": "./src/index.js"
|
|
46
|
+
"main": "./src/index.js",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"node-html-parser": "^6.1.13"
|
|
49
|
+
}
|
|
47
50
|
}
|
package/src/index.js
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// you must obtain a commercial license from CoCreate LLC.
|
|
21
21
|
// For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
|
|
22
22
|
|
|
23
|
+
const { parse } = require("node-html-parser");
|
|
24
|
+
|
|
23
25
|
class CoCreateSitemap {
|
|
24
26
|
constructor(crud) {
|
|
25
27
|
this.crud = crud;
|
|
@@ -35,7 +37,8 @@ class CoCreateSitemap {
|
|
|
35
37
|
return;
|
|
36
38
|
|
|
37
39
|
// Check if the file is HTML and contains a noindex meta tag
|
|
38
|
-
if (file['content-type'] === 'text/html'
|
|
40
|
+
if (file['content-type'] === 'text/html'
|
|
41
|
+
&& /<meta\s+name=["']robots["']\s+content=["'][^"']*noindex[^"']*["']/i.test(file.src))
|
|
39
42
|
return;
|
|
40
43
|
|
|
41
44
|
// Compare the lastmod date in the sitemap with the modified.on date
|
|
@@ -55,9 +58,9 @@ class CoCreateSitemap {
|
|
|
55
58
|
|
|
56
59
|
let { mainSitemap, sitemap } = await this.getSitemap(file, host);
|
|
57
60
|
|
|
58
|
-
if (file.
|
|
61
|
+
if (file.pathname) {
|
|
59
62
|
// Perform regex search starting at the pathname
|
|
60
|
-
const regexPattern = `<url>\\s*<loc>.*?${file.
|
|
63
|
+
const regexPattern = `<url>\\s*<loc>.*?${file.pathname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?</loc>[\\s\\S]*?</url>`;
|
|
61
64
|
const match = sitemap.src.match(new RegExp(regexPattern));
|
|
62
65
|
|
|
63
66
|
if (match) {
|
|
@@ -85,35 +88,43 @@ class CoCreateSitemap {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
createEntry(file) {
|
|
88
|
-
|
|
89
|
-
const priority = Math.max(0.1, 1.0 - (depth - 1) * 0.1).toFixed(1);
|
|
90
|
-
|
|
91
|
-
const defaultKeys = {
|
|
92
|
-
loc: file.pathname,
|
|
93
|
-
lastmod: file.modified.on,
|
|
94
|
-
changefreq: 'monthly', // Example default value
|
|
95
|
-
priority: priority,
|
|
96
|
-
};
|
|
97
|
-
// Merge default keys with file.sitemap, prioritizing file.sitemap values
|
|
98
|
-
file.sitemap = { ...defaultKeys, ...file.sitemap };
|
|
91
|
+
file.sitemap.loc = file.pathname;
|
|
99
92
|
file.sitemap.lastmod = file.modified.on;
|
|
100
93
|
|
|
94
|
+
if (file['content-type'] === 'text/html') {
|
|
95
|
+
parseHtml(file)
|
|
96
|
+
|
|
97
|
+
if (file.sitemap.type !== 'news') {
|
|
98
|
+
if (file.sitemap.changefreq)
|
|
99
|
+
file.sitemap.changefreq = 'monthly';
|
|
100
|
+
|
|
101
|
+
if (!file.sitemap.priority) {
|
|
102
|
+
const depth = (file.pathname.match(/\//g) || []).length;
|
|
103
|
+
file.sitemap.priority = Math.max(0.1, 1.0 - (depth - 1) * 0.1).toFixed(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
let entry = `\t<url>\n`;
|
|
102
109
|
|
|
103
110
|
for (const key of Object.keys(file.sitemap)) {
|
|
104
|
-
if (key === 'pathname')
|
|
111
|
+
if (key === 'pathname' || key === 'type')
|
|
105
112
|
continue
|
|
106
|
-
|
|
113
|
+
let value = file.sitemap[key];
|
|
107
114
|
|
|
108
|
-
if (typeof value === 'object' && value !== null) {
|
|
109
|
-
|
|
115
|
+
if (typeof value === 'object' && value !== null && !(value instanceof Date)) {
|
|
116
|
+
if (!Array.isArray(value))
|
|
117
|
+
value = [value]
|
|
118
|
+
for (let i = 0; i < value.length; i++) {
|
|
119
|
+
entry += `\t\t<${key}:${key}>\n`;
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
for (const nestedKey of Object.keys(value[i])) {
|
|
122
|
+
const nestedValue = value[nestedKey];
|
|
123
|
+
entry += `\t\t\t<${key}:${nestedKey}>${nestedValue}</${key}:${nestedKey}>\n`;
|
|
124
|
+
}
|
|
115
125
|
|
|
116
|
-
|
|
126
|
+
entry += `\t\t</${key}:${key}>\n`;
|
|
127
|
+
}
|
|
117
128
|
} else {
|
|
118
129
|
entry += `\t\t<${key}>${value}</${key}>\n`;
|
|
119
130
|
}
|
|
@@ -167,8 +178,8 @@ class CoCreateSitemap {
|
|
|
167
178
|
type = 'image';
|
|
168
179
|
} else if (file['content-type'].startsWith('video/')) {
|
|
169
180
|
type = 'video';
|
|
170
|
-
} else if (file.sitemap.news) {
|
|
171
|
-
|
|
181
|
+
} else if (file.sitemap.type === 'news') {
|
|
182
|
+
type = 'news';
|
|
172
183
|
}
|
|
173
184
|
|
|
174
185
|
let name = `sitemap`
|
|
@@ -180,24 +191,37 @@ class CoCreateSitemap {
|
|
|
180
191
|
if (index) {
|
|
181
192
|
sitemap.pathname = `/${name}${index}.xml`
|
|
182
193
|
sitemap = await this.readSitemap(sitemap, host);
|
|
194
|
+
} else {
|
|
195
|
+
index = 1
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
// Check if there's room in the last index sitemap
|
|
186
199
|
if (!this.checkSitemap(sitemap.src)) {
|
|
187
200
|
if (sitemap.src)
|
|
188
201
|
index += 1
|
|
202
|
+
else
|
|
203
|
+
sitemap.src = this.createSitemap(type);
|
|
204
|
+
|
|
189
205
|
sitemap.name = `${name}${index}.xml`
|
|
190
206
|
sitemap.pathname = `/${name}${index}.xml`
|
|
191
|
-
sitemap.src = this.createSitemap(type);
|
|
192
|
-
|
|
193
|
-
// Add the new sitemap entry
|
|
194
|
-
const indexEntry = `\n<sitemap>\n\t<loc>{{$host}}/${name}${index}.xml</loc>\n</sitemap>`;
|
|
195
|
-
mainSitemap.src = mainSitemap.src.replace('</sitemapindex>', `${indexEntry}\n</sitemapindex>`);
|
|
196
207
|
|
|
197
208
|
}
|
|
198
209
|
|
|
199
210
|
}
|
|
200
211
|
|
|
212
|
+
// Create the regex pattern to match the <sitemap> block containing the specific <loc> for the pathname
|
|
213
|
+
const regexPattern = `<sitemap>\\s*<loc>[^<]*${sitemap.pathname}[^<]*</loc>[\\s\\S]*?</sitemap>`;
|
|
214
|
+
|
|
215
|
+
// Execute the regex match against the sitemap index source
|
|
216
|
+
const match = mainSitemap.src.match(new RegExp(regexPattern));
|
|
217
|
+
|
|
218
|
+
// Check if a match is found
|
|
219
|
+
if (!match) {
|
|
220
|
+
//TODO: if sitemap found but not in index should we add to sitemap pathname to index or should we check the sitmap for the next index available see if room add or create new index.
|
|
221
|
+
const indexEntry = `\t<sitemap>\n\t\t<loc>{{$host}}${sitemap.pathname}</loc>\n</sitemap>`;
|
|
222
|
+
mainSitemap.src = mainSitemap.src.replace('</sitemapindex>', `${indexEntry}\n</sitemapindex>`);
|
|
223
|
+
}
|
|
224
|
+
|
|
201
225
|
return { mainSitemap, sitemap }
|
|
202
226
|
}
|
|
203
227
|
|
|
@@ -223,12 +247,24 @@ class CoCreateSitemap {
|
|
|
223
247
|
}
|
|
224
248
|
|
|
225
249
|
createSitemap(type) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
250
|
+
const xmlDeclaration = `<?xml version="1.0" encoding="UTF-8"?>\n`;
|
|
251
|
+
const sitemapNamespace = 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
|
|
252
|
+
|
|
253
|
+
if (type === 'main') {
|
|
254
|
+
return `${xmlDeclaration}<sitemapindex ${sitemapNamespace}>\n</sitemapindex>`;
|
|
255
|
+
} else {
|
|
256
|
+
const imageNamespace = 'xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"';
|
|
257
|
+
const videoNamespace = 'xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"';
|
|
258
|
+
const newsNamespace = 'xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"';
|
|
259
|
+
|
|
260
|
+
if (type === 'image') {
|
|
261
|
+
return `${xmlDeclaration}<urlset ${sitemapNamespace} ${imageNamespace}>\n</urlset>`;
|
|
262
|
+
} else if (type === 'video') {
|
|
263
|
+
return `${xmlDeclaration}<urlset ${sitemapNamespace} ${videoNamespace}>\n</urlset>`;
|
|
264
|
+
} else { // For 'news' type or any other types
|
|
265
|
+
return `${xmlDeclaration}<urlset ${sitemapNamespace} ${newsNamespace} ${imageNamespace} ${videoNamespace}>\n</urlset>`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
232
268
|
}
|
|
233
269
|
|
|
234
270
|
async saveSitemap(file, host) {
|
|
@@ -259,7 +295,7 @@ class CoCreateSitemap {
|
|
|
259
295
|
const regex = new RegExp(`\\/${filename}(\\d*)\\.xml<\\/loc>`, 'g');
|
|
260
296
|
const matches = mainSitemap.src.match(regex);
|
|
261
297
|
|
|
262
|
-
return matches ? matches.length :
|
|
298
|
+
return matches ? matches.length : null;
|
|
263
299
|
} catch (err) {
|
|
264
300
|
console.error(`Error determining next sitemap index for ${filename}:`, err);
|
|
265
301
|
return null; // Or some default value or throw an error
|
|
@@ -293,6 +329,101 @@ class CoCreateSitemap {
|
|
|
293
329
|
}
|
|
294
330
|
}
|
|
295
331
|
|
|
332
|
+
parseHtml(file) {
|
|
333
|
+
const dom = parse(html);
|
|
334
|
+
const entries = dom.querySelectorAll('[sitemap="true"]');
|
|
335
|
+
|
|
336
|
+
let types = ['image', 'video', 'news']
|
|
337
|
+
|
|
338
|
+
const previousEntries = {}
|
|
339
|
+
for (let i = 0; i < types.length; i++) {
|
|
340
|
+
if (!file.sitemap[types[i]])
|
|
341
|
+
continue
|
|
342
|
+
if (Array.isArray(file.sitemap[types[i]]))
|
|
343
|
+
previousEntries[types[i]] = file.sitemap[types[i]]
|
|
344
|
+
else
|
|
345
|
+
previousEntries[types[i]] = [file.sitemap[types[i]]]
|
|
346
|
+
|
|
347
|
+
delete file.sitemap[types[i]]
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
for (let i = 0; i < entries.length; i++) {
|
|
351
|
+
let type = '', query = '';
|
|
352
|
+
let existingObject
|
|
353
|
+
let entryObject = {};
|
|
354
|
+
|
|
355
|
+
if (entries[i].tagName === 'IMG') { // Corrected to 'IMG' for images
|
|
356
|
+
type = 'image';
|
|
357
|
+
query = 'loc'
|
|
358
|
+
entryObject.loc = entries[i].src;
|
|
359
|
+
entryObject.title = entries[i].getAttribute('sitemap-title') || entries[i].getAttribute('title') || entries[i].getAttribute('alt');
|
|
360
|
+
entryObject.caption = entries[i].getAttribute('sitemap-caption') || entries[i].getAttribute('alt') || entryObject.title;
|
|
361
|
+
entryObject.geo_location = entries[i].getAttribute('sitemap-geo-location');
|
|
362
|
+
} else if (entries[i].tagName === 'VIDEO') {
|
|
363
|
+
type = 'video';
|
|
364
|
+
query = 'content_loc'
|
|
365
|
+
entryObject.content_loc = entries[i].src;
|
|
366
|
+
entryObject.title = entries[i].getAttribute('sitemap-title') || entries[i].getAttribute('title');
|
|
367
|
+
entryObject.description = entries[i].getAttribute('description'); // 'description' if available
|
|
368
|
+
entryObject.thumbnail_loc = entries[i].getAttribute('sitemap-thumbnail') || entries[i].getAttribute('poster');
|
|
369
|
+
entryObject.duration = entries[i].getAttribute('sitemap-duration');
|
|
370
|
+
} else {
|
|
371
|
+
type = 'news';
|
|
372
|
+
file.sitemap.type = 'news';
|
|
373
|
+
query = 'title'
|
|
374
|
+
entryObject.title = entries[i].getAttribute('sitemap-title');
|
|
375
|
+
if (!entryObject.title) {
|
|
376
|
+
const title = dom.querySelector('title');
|
|
377
|
+
entryObject.title = title ? title.text : '';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
entryObject.publication = {
|
|
381
|
+
name: entries[i].getAttribute('sitemap-publication-name'), // Use proper attribute
|
|
382
|
+
language: entries[i].getAttribute('sitemap-publication-language') // Use proper attribute
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
if (!entryObject.publication.language) {
|
|
386
|
+
// Fallback to HTML lang attribute
|
|
387
|
+
const htmlElement = dom.querySelector('html');
|
|
388
|
+
entryObject.publication.language = htmlElement ? htmlElement.getAttribute('lang') : null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
entryObject.publication_date = entries[i].getAttribute('sitemap-publication-date') || file.modified.on;
|
|
392
|
+
|
|
393
|
+
entryObject.keywords = entries[i].getAttribute('sitemap-keywords');
|
|
394
|
+
if (!entryObject.keywords) {
|
|
395
|
+
const keywords = dom.querySelector('meta[name="keywords"]');
|
|
396
|
+
entryObject.keywords = keywords ? keywords.getAttribute('content') : '';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
entryObject.genres = entries[i].getAttribute('sitemap-genres');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (previousEntries[type]) {
|
|
403
|
+
existingObject = previousEntries[type].find(item => item[query] === entryObject[query]);
|
|
404
|
+
entryObject = { ...existingObject, ...entryObject }
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
Object.keys(entryObject).forEach(key => {
|
|
408
|
+
if (!entryObject[key])
|
|
409
|
+
delete entryObject[key]
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
if (!file.sitemap[type])
|
|
413
|
+
file.sitemap[type] = []
|
|
414
|
+
|
|
415
|
+
file.sitemap[type].push(entryObject)
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (file.sitemap.type !== 'news') {
|
|
420
|
+
const priorityMeta = dom.querySelector('meta[name="sitemap-priority"]');
|
|
421
|
+
const changefreqMeta = dom.querySelector('meta[name="sitemap-changefreq"]');
|
|
422
|
+
file.sitemap.priority = priorityMeta ? priorityMeta.getAttribute('content') : file.sitemap.priority; // Default priority if not specified
|
|
423
|
+
file.sitemap.changefreq = changefreqMeta ? changefreqMeta.getAttribute('content') : file.sitemap.changefreq; // Default changefreq if not specified
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
}
|
|
296
427
|
}
|
|
297
428
|
|
|
298
429
|
module.exports = CoCreateSitemap;
|