@seoengine.ai/next-llm-ready 1.0.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/LICENSE +21 -0
- package/README.md +858 -0
- package/dist/api/index.cjs +624 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +295 -0
- package/dist/api/index.d.ts +295 -0
- package/dist/api/index.js +613 -0
- package/dist/api/index.js.map +1 -0
- package/dist/hooks/index.cjs +619 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +257 -0
- package/dist/hooks/index.d.ts +257 -0
- package/dist/hooks/index.js +611 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.cjs +1609 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +850 -0
- package/dist/index.d.ts +850 -0
- package/dist/index.js +1576 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +398 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +134 -0
- package/dist/server/index.d.ts +134 -0
- package/dist/server/index.js +390 -0
- package/dist/server/index.js.map +1 -0
- package/dist/styles.css +855 -0
- package/package.json +118 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var server = require('next/server');
|
|
4
|
+
|
|
5
|
+
// src/api/llms-txt-handler.ts
|
|
6
|
+
|
|
7
|
+
// src/server/generate-llms-txt.ts
|
|
8
|
+
function generateLLMsTxt(config) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
parts.push(`# ${config.siteName}`);
|
|
11
|
+
parts.push("");
|
|
12
|
+
if (config.siteDescription) {
|
|
13
|
+
parts.push(`> ${config.siteDescription}`);
|
|
14
|
+
parts.push("");
|
|
15
|
+
}
|
|
16
|
+
if (config.headerText) {
|
|
17
|
+
parts.push(config.headerText);
|
|
18
|
+
} else {
|
|
19
|
+
parts.push("This file provides LLM-friendly access to the content on this website.");
|
|
20
|
+
}
|
|
21
|
+
parts.push("");
|
|
22
|
+
parts.push("---");
|
|
23
|
+
parts.push(`- **Site URL**: ${config.siteUrl}`);
|
|
24
|
+
parts.push("- **Format**: Append `?llm=1` to any page URL to get markdown");
|
|
25
|
+
parts.push("---");
|
|
26
|
+
parts.push("");
|
|
27
|
+
if (config.content.length > 0) {
|
|
28
|
+
parts.push("## Available Content");
|
|
29
|
+
parts.push("");
|
|
30
|
+
for (const item of config.content) {
|
|
31
|
+
const llmUrl = appendQueryParam(item.url, "llm", "1");
|
|
32
|
+
parts.push(`### [${item.title}](${llmUrl})`);
|
|
33
|
+
if (item.type) {
|
|
34
|
+
parts.push(`- **Type**: ${item.type}`);
|
|
35
|
+
}
|
|
36
|
+
if (item.date) {
|
|
37
|
+
parts.push(`- **Date**: ${item.date}`);
|
|
38
|
+
}
|
|
39
|
+
if (item.description) {
|
|
40
|
+
parts.push(`- **Description**: ${item.description}`);
|
|
41
|
+
}
|
|
42
|
+
parts.push(`- **Markdown URL**: ${llmUrl}`);
|
|
43
|
+
parts.push("");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
parts.push("---");
|
|
47
|
+
parts.push("");
|
|
48
|
+
if (config.footerText) {
|
|
49
|
+
parts.push(config.footerText);
|
|
50
|
+
} else {
|
|
51
|
+
parts.push("*Generated by [next-llm-ready](https://seoengine.ai) - Make your content AI-ready*");
|
|
52
|
+
}
|
|
53
|
+
return parts.join("\n");
|
|
54
|
+
}
|
|
55
|
+
function appendQueryParam(url, key, value) {
|
|
56
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
57
|
+
return `${url}${separator}${key}=${value}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/api/llms-txt-handler.ts
|
|
61
|
+
function createLLMsTxtHandler(options) {
|
|
62
|
+
return async function handler(request) {
|
|
63
|
+
try {
|
|
64
|
+
const siteConfig = await options.getSiteConfig();
|
|
65
|
+
const content = await options.getContent();
|
|
66
|
+
const config = {
|
|
67
|
+
siteName: siteConfig.siteName,
|
|
68
|
+
siteDescription: siteConfig.siteDescription,
|
|
69
|
+
siteUrl: siteConfig.siteUrl,
|
|
70
|
+
content,
|
|
71
|
+
headerText: options.headerText,
|
|
72
|
+
footerText: options.footerText
|
|
73
|
+
};
|
|
74
|
+
const llmsTxt = generateLLMsTxt(config);
|
|
75
|
+
return new server.NextResponse(llmsTxt, {
|
|
76
|
+
status: 200,
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
79
|
+
"Cache-Control": options.cacheControl || "public, max-age=3600",
|
|
80
|
+
"X-Robots-Tag": "noindex"
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("Error generating llms.txt:", error);
|
|
85
|
+
return new server.NextResponse("Error generating llms.txt", { status: 500 });
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function createLLMsTxtPageHandler(options) {
|
|
90
|
+
return async function handler(req, res) {
|
|
91
|
+
if (req.method !== "GET") {
|
|
92
|
+
res.status(405).end("Method Not Allowed");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const siteConfig = await options.getSiteConfig();
|
|
97
|
+
const content = await options.getContent();
|
|
98
|
+
const config = {
|
|
99
|
+
siteName: siteConfig.siteName,
|
|
100
|
+
siteDescription: siteConfig.siteDescription,
|
|
101
|
+
siteUrl: siteConfig.siteUrl,
|
|
102
|
+
content,
|
|
103
|
+
headerText: options.headerText,
|
|
104
|
+
footerText: options.footerText
|
|
105
|
+
};
|
|
106
|
+
const llmsTxt = generateLLMsTxt(config);
|
|
107
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
108
|
+
res.setHeader("Cache-Control", options.cacheControl || "public, max-age=3600");
|
|
109
|
+
res.setHeader("X-Robots-Tag", "noindex");
|
|
110
|
+
res.status(200).end(llmsTxt);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error("Error generating llms.txt:", error);
|
|
113
|
+
res.status(500).end("Error generating llms.txt");
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/utils/html-to-markdown.ts
|
|
119
|
+
var defaultOptions = {
|
|
120
|
+
preserveLineBreaks: true,
|
|
121
|
+
convertImages: true,
|
|
122
|
+
convertLinks: true,
|
|
123
|
+
stripScripts: true,
|
|
124
|
+
customHandlers: {}
|
|
125
|
+
};
|
|
126
|
+
function htmlToMarkdown(html, options = {}) {
|
|
127
|
+
const opts = { ...defaultOptions, ...options };
|
|
128
|
+
if (typeof window === "undefined") {
|
|
129
|
+
return serverSideConvert(html, opts);
|
|
130
|
+
}
|
|
131
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
132
|
+
return convertNode(doc.body, opts);
|
|
133
|
+
}
|
|
134
|
+
function serverSideConvert(html, opts) {
|
|
135
|
+
let markdown = html;
|
|
136
|
+
if (opts.stripScripts) {
|
|
137
|
+
markdown = markdown.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
|
|
138
|
+
markdown = markdown.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "");
|
|
139
|
+
}
|
|
140
|
+
markdown = markdown.replace(/<h1[^>]*>(.*?)<\/h1>/gi, "\n# $1\n");
|
|
141
|
+
markdown = markdown.replace(/<h2[^>]*>(.*?)<\/h2>/gi, "\n## $1\n");
|
|
142
|
+
markdown = markdown.replace(/<h3[^>]*>(.*?)<\/h3>/gi, "\n### $1\n");
|
|
143
|
+
markdown = markdown.replace(/<h4[^>]*>(.*?)<\/h4>/gi, "\n#### $1\n");
|
|
144
|
+
markdown = markdown.replace(/<h5[^>]*>(.*?)<\/h5>/gi, "\n##### $1\n");
|
|
145
|
+
markdown = markdown.replace(/<h6[^>]*>(.*?)<\/h6>/gi, "\n###### $1\n");
|
|
146
|
+
markdown = markdown.replace(/<(strong|b)[^>]*>(.*?)<\/\1>/gi, "**$2**");
|
|
147
|
+
markdown = markdown.replace(/<(em|i)[^>]*>(.*?)<\/\1>/gi, "*$2*");
|
|
148
|
+
if (opts.convertLinks) {
|
|
149
|
+
markdown = markdown.replace(/<a[^>]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>/gi, "[$2]($1)");
|
|
150
|
+
}
|
|
151
|
+
if (opts.convertImages) {
|
|
152
|
+
markdown = markdown.replace(
|
|
153
|
+
/<img[^>]*src=["']([^"']*)["'][^>]*alt=["']([^"']*)["'][^>]*\/?>/gi,
|
|
154
|
+
""
|
|
155
|
+
);
|
|
156
|
+
markdown = markdown.replace(
|
|
157
|
+
/<img[^>]*alt=["']([^"']*)["'][^>]*src=["']([^"']*)["'][^>]*\/?>/gi,
|
|
158
|
+
""
|
|
159
|
+
);
|
|
160
|
+
markdown = markdown.replace(/<img[^>]*src=["']([^"']*)["'][^>]*\/?>/gi, "");
|
|
161
|
+
}
|
|
162
|
+
markdown = markdown.replace(/<ul[^>]*>/gi, "\n");
|
|
163
|
+
markdown = markdown.replace(/<\/ul>/gi, "\n");
|
|
164
|
+
markdown = markdown.replace(/<ol[^>]*>/gi, "\n");
|
|
165
|
+
markdown = markdown.replace(/<\/ol>/gi, "\n");
|
|
166
|
+
markdown = markdown.replace(/<li[^>]*>(.*?)<\/li>/gi, "- $1\n");
|
|
167
|
+
markdown = markdown.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, (_, content) => {
|
|
168
|
+
return "\n> " + content.trim().replace(/\n/g, "\n> ") + "\n";
|
|
169
|
+
});
|
|
170
|
+
markdown = markdown.replace(/<pre[^>]*><code[^>]*>(.*?)<\/code><\/pre>/gis, "\n```\n$1\n```\n");
|
|
171
|
+
markdown = markdown.replace(/<code[^>]*>(.*?)<\/code>/gi, "`$1`");
|
|
172
|
+
markdown = markdown.replace(/<p[^>]*>(.*?)<\/p>/gis, "\n$1\n");
|
|
173
|
+
markdown = markdown.replace(/<br\s*\/?>/gi, "\n");
|
|
174
|
+
markdown = markdown.replace(/<hr[^>]*\/?>/gi, "\n---\n");
|
|
175
|
+
markdown = markdown.replace(/<[^>]+>/g, "");
|
|
176
|
+
markdown = decodeHTMLEntities(markdown);
|
|
177
|
+
markdown = markdown.replace(/\n{3,}/g, "\n\n");
|
|
178
|
+
markdown = markdown.trim();
|
|
179
|
+
return markdown;
|
|
180
|
+
}
|
|
181
|
+
function convertNode(node, opts) {
|
|
182
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
183
|
+
return node.textContent || "";
|
|
184
|
+
}
|
|
185
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
const element = node;
|
|
189
|
+
const tagName = element.tagName.toLowerCase();
|
|
190
|
+
if (opts.customHandlers?.[tagName]) {
|
|
191
|
+
return opts.customHandlers[tagName](element);
|
|
192
|
+
}
|
|
193
|
+
if (opts.stripScripts && (tagName === "script" || tagName === "style")) {
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
const childContent = Array.from(element.childNodes).map((child) => convertNode(child, opts)).join("");
|
|
197
|
+
switch (tagName) {
|
|
198
|
+
case "h1":
|
|
199
|
+
return `
|
|
200
|
+
# ${childContent.trim()}
|
|
201
|
+
`;
|
|
202
|
+
case "h2":
|
|
203
|
+
return `
|
|
204
|
+
## ${childContent.trim()}
|
|
205
|
+
`;
|
|
206
|
+
case "h3":
|
|
207
|
+
return `
|
|
208
|
+
### ${childContent.trim()}
|
|
209
|
+
`;
|
|
210
|
+
case "h4":
|
|
211
|
+
return `
|
|
212
|
+
#### ${childContent.trim()}
|
|
213
|
+
`;
|
|
214
|
+
case "h5":
|
|
215
|
+
return `
|
|
216
|
+
##### ${childContent.trim()}
|
|
217
|
+
`;
|
|
218
|
+
case "h6":
|
|
219
|
+
return `
|
|
220
|
+
###### ${childContent.trim()}
|
|
221
|
+
`;
|
|
222
|
+
case "p":
|
|
223
|
+
return `
|
|
224
|
+
${childContent.trim()}
|
|
225
|
+
`;
|
|
226
|
+
case "br":
|
|
227
|
+
return opts.preserveLineBreaks ? "\n" : " ";
|
|
228
|
+
case "strong":
|
|
229
|
+
case "b":
|
|
230
|
+
return `**${childContent}**`;
|
|
231
|
+
case "em":
|
|
232
|
+
case "i":
|
|
233
|
+
return `*${childContent}*`;
|
|
234
|
+
case "u":
|
|
235
|
+
return `_${childContent}_`;
|
|
236
|
+
case "s":
|
|
237
|
+
case "strike":
|
|
238
|
+
case "del":
|
|
239
|
+
return `~~${childContent}~~`;
|
|
240
|
+
case "a":
|
|
241
|
+
if (opts.convertLinks) {
|
|
242
|
+
const href = element.getAttribute("href") || "";
|
|
243
|
+
return `[${childContent}](${href})`;
|
|
244
|
+
}
|
|
245
|
+
return childContent;
|
|
246
|
+
case "img":
|
|
247
|
+
if (opts.convertImages) {
|
|
248
|
+
const src = element.getAttribute("src") || "";
|
|
249
|
+
const alt = element.getAttribute("alt") || "";
|
|
250
|
+
return ``;
|
|
251
|
+
}
|
|
252
|
+
return "";
|
|
253
|
+
case "ul":
|
|
254
|
+
return `
|
|
255
|
+
${childContent}
|
|
256
|
+
`;
|
|
257
|
+
case "ol":
|
|
258
|
+
return `
|
|
259
|
+
${childContent}
|
|
260
|
+
`;
|
|
261
|
+
case "li":
|
|
262
|
+
return `- ${childContent.trim()}
|
|
263
|
+
`;
|
|
264
|
+
case "blockquote":
|
|
265
|
+
return `
|
|
266
|
+
> ${childContent.trim().replace(/\n/g, "\n> ")}
|
|
267
|
+
`;
|
|
268
|
+
case "pre":
|
|
269
|
+
const codeElement = element.querySelector("code");
|
|
270
|
+
const lang = codeElement?.className.match(/language-(\w+)/)?.[1] || "";
|
|
271
|
+
const code = codeElement?.textContent || childContent;
|
|
272
|
+
return `
|
|
273
|
+
\`\`\`${lang}
|
|
274
|
+
${code.trim()}
|
|
275
|
+
\`\`\`
|
|
276
|
+
`;
|
|
277
|
+
case "code":
|
|
278
|
+
if (element.parentElement?.tagName.toLowerCase() === "pre") {
|
|
279
|
+
return childContent;
|
|
280
|
+
}
|
|
281
|
+
return `\`${childContent}\``;
|
|
282
|
+
case "hr":
|
|
283
|
+
return "\n---\n";
|
|
284
|
+
case "table":
|
|
285
|
+
return convertTable(element);
|
|
286
|
+
case "div":
|
|
287
|
+
case "section":
|
|
288
|
+
case "article":
|
|
289
|
+
case "main":
|
|
290
|
+
case "aside":
|
|
291
|
+
case "header":
|
|
292
|
+
case "footer":
|
|
293
|
+
case "nav":
|
|
294
|
+
return childContent;
|
|
295
|
+
default:
|
|
296
|
+
return childContent;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function convertTable(table) {
|
|
300
|
+
const rows = table.querySelectorAll("tr");
|
|
301
|
+
if (rows.length === 0) return "";
|
|
302
|
+
let markdown = "\n";
|
|
303
|
+
let headerProcessed = false;
|
|
304
|
+
rows.forEach((row, index) => {
|
|
305
|
+
const cells = row.querySelectorAll("th, td");
|
|
306
|
+
const rowContent = Array.from(cells).map((cell) => cell.textContent?.trim() || "").join(" | ");
|
|
307
|
+
markdown += `| ${rowContent} |
|
|
308
|
+
`;
|
|
309
|
+
if (!headerProcessed && (row.querySelector("th") || index === 0)) {
|
|
310
|
+
const separator = Array.from(cells).map(() => "---").join(" | ");
|
|
311
|
+
markdown += `| ${separator} |
|
|
312
|
+
`;
|
|
313
|
+
headerProcessed = true;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
return markdown + "\n";
|
|
317
|
+
}
|
|
318
|
+
function decodeHTMLEntities(text) {
|
|
319
|
+
const entities = {
|
|
320
|
+
"&": "&",
|
|
321
|
+
"<": "<",
|
|
322
|
+
">": ">",
|
|
323
|
+
""": '"',
|
|
324
|
+
"'": "'",
|
|
325
|
+
"'": "'",
|
|
326
|
+
" ": " ",
|
|
327
|
+
"—": "\u2014",
|
|
328
|
+
"–": "\u2013",
|
|
329
|
+
"…": "\u2026",
|
|
330
|
+
"©": "\xA9",
|
|
331
|
+
"®": "\xAE",
|
|
332
|
+
"™": "\u2122"
|
|
333
|
+
};
|
|
334
|
+
let result = text;
|
|
335
|
+
for (const [entity, char] of Object.entries(entities)) {
|
|
336
|
+
result = result.replace(new RegExp(entity, "g"), char);
|
|
337
|
+
}
|
|
338
|
+
result = result.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(parseInt(num, 10)));
|
|
339
|
+
result = result.replace(
|
|
340
|
+
/&#x([0-9a-f]+);/gi,
|
|
341
|
+
(_, hex) => String.fromCharCode(parseInt(hex, 16))
|
|
342
|
+
);
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
function countWords(text) {
|
|
346
|
+
return text.replace(/[^\w\s]/g, "").split(/\s+/).filter((word) => word.length > 0).length;
|
|
347
|
+
}
|
|
348
|
+
function calculateReadingTime(text, wordsPerMinute = 200) {
|
|
349
|
+
const words = countWords(text);
|
|
350
|
+
return Math.ceil(words / wordsPerMinute);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/server/generate-markdown.ts
|
|
354
|
+
function generateMarkdown(content) {
|
|
355
|
+
const parts = [];
|
|
356
|
+
if (content.promptPrefix?.trim()) {
|
|
357
|
+
parts.push(content.promptPrefix.trim());
|
|
358
|
+
parts.push("");
|
|
359
|
+
}
|
|
360
|
+
parts.push(`# ${content.title}`);
|
|
361
|
+
parts.push("");
|
|
362
|
+
if (content.excerpt) {
|
|
363
|
+
parts.push(`> ${content.excerpt}`);
|
|
364
|
+
parts.push("");
|
|
365
|
+
}
|
|
366
|
+
parts.push("---");
|
|
367
|
+
parts.push(`- **Source**: ${content.url}`);
|
|
368
|
+
if (content.date) {
|
|
369
|
+
parts.push(`- **Date**: ${content.date}`);
|
|
370
|
+
}
|
|
371
|
+
if (content.modifiedDate) {
|
|
372
|
+
parts.push(`- **Modified**: ${content.modifiedDate}`);
|
|
373
|
+
}
|
|
374
|
+
if (content.author) {
|
|
375
|
+
parts.push(`- **Author**: ${content.author}`);
|
|
376
|
+
}
|
|
377
|
+
if (content.categories?.length) {
|
|
378
|
+
parts.push(`- **Categories**: ${content.categories.join(", ")}`);
|
|
379
|
+
}
|
|
380
|
+
if (content.tags?.length) {
|
|
381
|
+
parts.push(`- **Tags**: ${content.tags.join(", ")}`);
|
|
382
|
+
}
|
|
383
|
+
if (content.readingTime) {
|
|
384
|
+
parts.push(`- **Reading Time**: ${content.readingTime} min`);
|
|
385
|
+
}
|
|
386
|
+
parts.push("---");
|
|
387
|
+
parts.push("");
|
|
388
|
+
if (content.content) {
|
|
389
|
+
const contentMarkdown = isHTML(content.content) ? htmlToMarkdown(content.content) : content.content;
|
|
390
|
+
parts.push(contentMarkdown);
|
|
391
|
+
}
|
|
392
|
+
const markdown = parts.join("\n").trim();
|
|
393
|
+
const wordCount = countWords(markdown);
|
|
394
|
+
const readingTime = calculateReadingTime(markdown);
|
|
395
|
+
const headings = extractMarkdownHeadings(markdown);
|
|
396
|
+
return {
|
|
397
|
+
markdown,
|
|
398
|
+
wordCount,
|
|
399
|
+
readingTime,
|
|
400
|
+
headings
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function generateMarkdownString(content) {
|
|
404
|
+
return generateMarkdown(content).markdown;
|
|
405
|
+
}
|
|
406
|
+
function isHTML(str) {
|
|
407
|
+
return /<[a-z][\s\S]*>/i.test(str);
|
|
408
|
+
}
|
|
409
|
+
function extractMarkdownHeadings(markdown) {
|
|
410
|
+
const headings = [];
|
|
411
|
+
const lines = markdown.split("\n");
|
|
412
|
+
let index = 0;
|
|
413
|
+
for (const line of lines) {
|
|
414
|
+
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
415
|
+
if (match) {
|
|
416
|
+
const level = match[1].length;
|
|
417
|
+
const text = match[2].trim();
|
|
418
|
+
const id = generateSlug(text, index);
|
|
419
|
+
headings.push({ id, text, level });
|
|
420
|
+
index++;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return headings;
|
|
424
|
+
}
|
|
425
|
+
function generateSlug(text, index) {
|
|
426
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
427
|
+
return slug || `heading-${index}`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/api/markdown-handler.ts
|
|
431
|
+
function withLLMParam(options) {
|
|
432
|
+
return async function middleware(request) {
|
|
433
|
+
const url = new URL(request.url);
|
|
434
|
+
const llmParam = url.searchParams.get("llm");
|
|
435
|
+
if (llmParam !== "1") {
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const pathname = url.pathname;
|
|
440
|
+
const content = await options.getContent(pathname);
|
|
441
|
+
if (!content) {
|
|
442
|
+
return new server.NextResponse("Content not found", { status: 404 });
|
|
443
|
+
}
|
|
444
|
+
const markdown = generateMarkdownString(content);
|
|
445
|
+
return new server.NextResponse(markdown, {
|
|
446
|
+
status: 200,
|
|
447
|
+
headers: {
|
|
448
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
449
|
+
"Cache-Control": options.cacheControl || "public, max-age=3600",
|
|
450
|
+
"X-Robots-Tag": "noindex",
|
|
451
|
+
...options.cors && {
|
|
452
|
+
"Access-Control-Allow-Origin": "*",
|
|
453
|
+
"Access-Control-Allow-Methods": "GET"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
} catch (error) {
|
|
458
|
+
console.error("Error generating markdown:", error);
|
|
459
|
+
return new server.NextResponse("Error generating markdown", { status: 500 });
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function createMarkdownHandler(options) {
|
|
464
|
+
return async function handler(request, { params }) {
|
|
465
|
+
try {
|
|
466
|
+
const slug = params?.slug || "";
|
|
467
|
+
const content = await options.getContent(slug);
|
|
468
|
+
if (!content) {
|
|
469
|
+
return new server.NextResponse("Content not found", {
|
|
470
|
+
status: 404,
|
|
471
|
+
headers: { "Content-Type": "text/plain" }
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const markdown = generateMarkdownString(content);
|
|
475
|
+
return new server.NextResponse(markdown, {
|
|
476
|
+
status: 200,
|
|
477
|
+
headers: {
|
|
478
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
479
|
+
"Cache-Control": options.cacheControl || "public, max-age=3600",
|
|
480
|
+
"X-Robots-Tag": "noindex",
|
|
481
|
+
...options.cors && {
|
|
482
|
+
"Access-Control-Allow-Origin": "*",
|
|
483
|
+
"Access-Control-Allow-Methods": "GET"
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
} catch (error) {
|
|
488
|
+
console.error("Error generating markdown:", error);
|
|
489
|
+
return new server.NextResponse("Error generating markdown", { status: 500 });
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function hasLLMParam(request) {
|
|
494
|
+
const url = new URL(request.url);
|
|
495
|
+
return url.searchParams.get("llm") === "1";
|
|
496
|
+
}
|
|
497
|
+
function createInMemoryStorage() {
|
|
498
|
+
const events = [];
|
|
499
|
+
return {
|
|
500
|
+
save: async (event) => {
|
|
501
|
+
events.push(event);
|
|
502
|
+
},
|
|
503
|
+
getAll: async () => [...events],
|
|
504
|
+
clear: async () => {
|
|
505
|
+
events.length = 0;
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function createAnalyticsHandler(options) {
|
|
510
|
+
const requestCounts = /* @__PURE__ */ new Map();
|
|
511
|
+
return async function handler(request) {
|
|
512
|
+
if (request.method === "OPTIONS") {
|
|
513
|
+
return new server.NextResponse(null, {
|
|
514
|
+
status: 204,
|
|
515
|
+
headers: {
|
|
516
|
+
"Access-Control-Allow-Origin": "*",
|
|
517
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
518
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
if (request.method !== "POST") {
|
|
523
|
+
return new server.NextResponse("Method Not Allowed", { status: 405 });
|
|
524
|
+
}
|
|
525
|
+
if (options.rateLimit) {
|
|
526
|
+
const clientIp = request.headers.get("x-forwarded-for") || "unknown";
|
|
527
|
+
const now = Date.now();
|
|
528
|
+
const windowMs = 6e4;
|
|
529
|
+
const clientData = requestCounts.get(clientIp) || { count: 0, resetAt: now + windowMs };
|
|
530
|
+
if (now > clientData.resetAt) {
|
|
531
|
+
clientData.count = 0;
|
|
532
|
+
clientData.resetAt = now + windowMs;
|
|
533
|
+
}
|
|
534
|
+
clientData.count++;
|
|
535
|
+
requestCounts.set(clientIp, clientData);
|
|
536
|
+
if (clientData.count > options.rateLimit) {
|
|
537
|
+
return new server.NextResponse("Rate limit exceeded", {
|
|
538
|
+
status: 429,
|
|
539
|
+
headers: {
|
|
540
|
+
"Retry-After": Math.ceil((clientData.resetAt - now) / 1e3).toString()
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const body = await request.json();
|
|
547
|
+
const event = {
|
|
548
|
+
action: body.action || "copy",
|
|
549
|
+
contentId: body.contentId,
|
|
550
|
+
url: body.url || request.headers.get("referer"),
|
|
551
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
552
|
+
metadata: body.metadata
|
|
553
|
+
};
|
|
554
|
+
await options.storage.save(event);
|
|
555
|
+
const headers = {
|
|
556
|
+
"Content-Type": "application/json"
|
|
557
|
+
};
|
|
558
|
+
if (options.cors) {
|
|
559
|
+
headers["Access-Control-Allow-Origin"] = "*";
|
|
560
|
+
}
|
|
561
|
+
return server.NextResponse.json({ success: true, event }, { headers });
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error("Analytics tracking error:", error);
|
|
564
|
+
return server.NextResponse.json({ success: false, error: "Failed to track event" }, { status: 500 });
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function createAnalyticsPageHandler(options) {
|
|
569
|
+
return async function handler(req, res) {
|
|
570
|
+
if (req.method !== "POST") {
|
|
571
|
+
res.status(405).end("Method Not Allowed");
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
const body = req.body;
|
|
576
|
+
const event = {
|
|
577
|
+
action: body?.action || "copy",
|
|
578
|
+
contentId: body?.contentId,
|
|
579
|
+
url: body?.url || req.headers?.referer,
|
|
580
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
581
|
+
metadata: body?.metadata
|
|
582
|
+
};
|
|
583
|
+
await options.storage.save(event);
|
|
584
|
+
if (options.cors) {
|
|
585
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
586
|
+
}
|
|
587
|
+
res.status(200).json({ success: true, event });
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.error("Analytics tracking error:", error);
|
|
590
|
+
res.status(500).json({ success: false, error: "Failed to track event" });
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
async function aggregateEvents(storage) {
|
|
595
|
+
const events = await storage.getAll();
|
|
596
|
+
const counts = {
|
|
597
|
+
copy: 0,
|
|
598
|
+
view: 0,
|
|
599
|
+
download: 0
|
|
600
|
+
};
|
|
601
|
+
for (const event of events) {
|
|
602
|
+
if (event.action in counts) {
|
|
603
|
+
counts[event.action]++;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return counts;
|
|
607
|
+
}
|
|
608
|
+
async function getEventsForContent(storage, contentId) {
|
|
609
|
+
const events = await storage.getAll();
|
|
610
|
+
return events.filter((event) => event.contentId === contentId);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
exports.aggregateEvents = aggregateEvents;
|
|
614
|
+
exports.createAnalyticsHandler = createAnalyticsHandler;
|
|
615
|
+
exports.createAnalyticsPageHandler = createAnalyticsPageHandler;
|
|
616
|
+
exports.createInMemoryStorage = createInMemoryStorage;
|
|
617
|
+
exports.createLLMsTxtHandler = createLLMsTxtHandler;
|
|
618
|
+
exports.createLLMsTxtPageHandler = createLLMsTxtPageHandler;
|
|
619
|
+
exports.createMarkdownHandler = createMarkdownHandler;
|
|
620
|
+
exports.getEventsForContent = getEventsForContent;
|
|
621
|
+
exports.hasLLMParam = hasLLMParam;
|
|
622
|
+
exports.withLLMParam = withLLMParam;
|
|
623
|
+
//# sourceMappingURL=index.cjs.map
|
|
624
|
+
//# sourceMappingURL=index.cjs.map
|