@ampless/runtime 0.2.0-alpha.9 → 1.0.0-alpha.14
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/README.ja.md +35 -9
- package/README.md +35 -9
- package/dist/dispatchers/index.d.ts +14 -23
- package/dist/dispatchers/index.js +7 -22
- package/dist/index.d.ts +35 -26
- package/dist/index.js +176 -96
- package/dist/middleware.d.ts +33 -21
- package/dist/middleware.js +181 -19
- package/dist/routes/index.d.ts +53 -80
- package/dist/routes/index.js +85 -79
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -20,39 +20,25 @@ function createStorage(outputs) {
|
|
|
20
20
|
// src/posts.ts
|
|
21
21
|
import { cookies } from "next/headers";
|
|
22
22
|
import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";
|
|
23
|
-
|
|
24
|
-
if (typeof value !== "string") return value;
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(value);
|
|
27
|
-
} catch {
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
23
|
+
import { decodeAwsJson } from "ampless";
|
|
31
24
|
function decodeMetadata(value) {
|
|
32
25
|
if (value === null || value === void 0) return void 0;
|
|
33
|
-
const parsed =
|
|
26
|
+
const parsed = decodeAwsJson(value);
|
|
34
27
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
35
28
|
}
|
|
36
|
-
function safeJsonParse(value) {
|
|
37
|
-
try {
|
|
38
|
-
return JSON.parse(value);
|
|
39
|
-
} catch {
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
29
|
function toCorePost(p) {
|
|
44
30
|
return {
|
|
45
31
|
postId: p.postId,
|
|
46
|
-
siteId: p.siteId,
|
|
47
32
|
slug: p.slug,
|
|
48
33
|
title: p.title,
|
|
49
34
|
excerpt: p.excerpt ?? void 0,
|
|
50
35
|
format: p.format ?? "markdown",
|
|
51
|
-
body:
|
|
36
|
+
body: decodeAwsJson(p.body),
|
|
52
37
|
status: p.status ?? "draft",
|
|
53
38
|
publishedAt: p.publishedAt ?? void 0,
|
|
54
39
|
tags: (p.tags ?? []).filter((t) => typeof t === "string"),
|
|
55
|
-
metadata: decodeMetadata(p.metadata)
|
|
40
|
+
metadata: decodeMetadata(p.metadata),
|
|
41
|
+
updatedAt: p.updatedAt ?? void 0
|
|
56
42
|
};
|
|
57
43
|
}
|
|
58
44
|
function createPostsApi(outputs) {
|
|
@@ -66,7 +52,6 @@ function createPostsApi(outputs) {
|
|
|
66
52
|
return {
|
|
67
53
|
async listPublishedPosts(opts = {}) {
|
|
68
54
|
const { data, errors } = await client.queries.listPublishedPosts({
|
|
69
|
-
siteId: opts.siteId ?? "default",
|
|
70
55
|
from: opts.from,
|
|
71
56
|
to: opts.to,
|
|
72
57
|
limit: opts.limit ?? 20,
|
|
@@ -76,9 +61,8 @@ function createPostsApi(outputs) {
|
|
|
76
61
|
const items = (data?.items ?? []).filter((p) => p !== null).map(toCorePost);
|
|
77
62
|
return { items, nextToken: data?.nextToken ?? null };
|
|
78
63
|
},
|
|
79
|
-
async getPublishedPost(slug
|
|
64
|
+
async getPublishedPost(slug) {
|
|
80
65
|
const { data, errors } = await client.queries.getPublishedPost({
|
|
81
|
-
siteId: opts.siteId ?? "default",
|
|
82
66
|
slug
|
|
83
67
|
});
|
|
84
68
|
if (errors) throw new Error(errors[0]?.message ?? "Failed to get post");
|
|
@@ -86,7 +70,6 @@ function createPostsApi(outputs) {
|
|
|
86
70
|
},
|
|
87
71
|
async listPostsByTag(tag, opts = {}) {
|
|
88
72
|
const { data, errors } = await client.queries.listPostsByTag({
|
|
89
|
-
siteId: opts.siteId ?? "default",
|
|
90
73
|
tag,
|
|
91
74
|
limit: opts.limit ?? 20,
|
|
92
75
|
nextToken: opts.nextToken
|
|
@@ -99,25 +82,27 @@ function createPostsApi(outputs) {
|
|
|
99
82
|
}
|
|
100
83
|
|
|
101
84
|
// src/site-settings.ts
|
|
102
|
-
import {
|
|
85
|
+
import { unflattenSettings } from "ampless";
|
|
103
86
|
function createSiteSettings(cmsConfig, storage) {
|
|
104
|
-
async function fetchRemote(
|
|
87
|
+
async function fetchRemote() {
|
|
105
88
|
if (!storage.isStorageConfigured()) return null;
|
|
106
89
|
let url;
|
|
107
90
|
try {
|
|
108
|
-
url = storage.publicAssetUrl(
|
|
91
|
+
url = storage.publicAssetUrl("public/site-settings.json");
|
|
109
92
|
} catch {
|
|
110
93
|
return null;
|
|
111
94
|
}
|
|
112
|
-
const res = await fetch(url, {
|
|
95
|
+
const res = await fetch(url, {
|
|
96
|
+
next: { revalidate: 60, tags: ["site-settings"] }
|
|
97
|
+
});
|
|
113
98
|
if (!res.ok) return null;
|
|
114
99
|
const flat = await res.json();
|
|
115
100
|
return unflattenSettings(flat);
|
|
116
101
|
}
|
|
117
102
|
return {
|
|
118
|
-
async loadSiteSettings(
|
|
119
|
-
const remote = await fetchRemote(
|
|
120
|
-
const baseSite =
|
|
103
|
+
async loadSiteSettings() {
|
|
104
|
+
const remote = await fetchRemote().catch(() => null);
|
|
105
|
+
const baseSite = cmsConfig.site;
|
|
121
106
|
return {
|
|
122
107
|
site: {
|
|
123
108
|
name: remote?.site?.name ?? baseSite.name,
|
|
@@ -142,15 +127,14 @@ function createSiteSettings(cmsConfig, storage) {
|
|
|
142
127
|
}
|
|
143
128
|
|
|
144
129
|
// src/seo.ts
|
|
145
|
-
import { DEFAULT_SITE_ID as DEFAULT_SITE_ID2 } from "ampless";
|
|
146
130
|
function isPlugin(p) {
|
|
147
131
|
return typeof p === "object" && p !== null && "apiVersion" in p;
|
|
148
132
|
}
|
|
149
133
|
function createSeo(cmsConfig, settingsApi) {
|
|
150
134
|
const plugins = (cmsConfig.plugins ?? []).filter(isPlugin);
|
|
151
135
|
return {
|
|
152
|
-
async postMetadata(post
|
|
153
|
-
const settings = await settingsApi.loadSiteSettings(
|
|
136
|
+
async postMetadata(post) {
|
|
137
|
+
const settings = await settingsApi.loadSiteSettings();
|
|
154
138
|
const accum = {};
|
|
155
139
|
for (const plugin of plugins) {
|
|
156
140
|
if (!plugin.metadata) continue;
|
|
@@ -167,8 +151,8 @@ function createSeo(cmsConfig, settingsApi) {
|
|
|
167
151
|
}
|
|
168
152
|
return accum;
|
|
169
153
|
},
|
|
170
|
-
async siteMetadata(
|
|
171
|
-
const settings = await settingsApi.loadSiteSettings(
|
|
154
|
+
async siteMetadata() {
|
|
155
|
+
const settings = await settingsApi.loadSiteSettings();
|
|
172
156
|
const accum = {
|
|
173
157
|
title: settings.site.name,
|
|
174
158
|
description: settings.site.description
|
|
@@ -193,24 +177,25 @@ function createSeo(cmsConfig, settingsApi) {
|
|
|
193
177
|
|
|
194
178
|
// src/theme-active.ts
|
|
195
179
|
import { headers } from "next/headers";
|
|
196
|
-
import { DEFAULT_SITE_ID as DEFAULT_SITE_ID3 } from "ampless";
|
|
197
180
|
function createThemeActive(registry, storage) {
|
|
198
|
-
async function fetchActiveFromCache(
|
|
181
|
+
async function fetchActiveFromCache() {
|
|
199
182
|
if (!storage.isStorageConfigured()) return null;
|
|
200
183
|
let url;
|
|
201
184
|
try {
|
|
202
|
-
url = storage.publicAssetUrl(
|
|
185
|
+
url = storage.publicAssetUrl("public/site-settings.json");
|
|
203
186
|
} catch {
|
|
204
187
|
return null;
|
|
205
188
|
}
|
|
206
|
-
const res = await fetch(url, {
|
|
189
|
+
const res = await fetch(url, {
|
|
190
|
+
next: { revalidate: 60, tags: ["site-settings"] }
|
|
191
|
+
});
|
|
207
192
|
if (!res.ok) return null;
|
|
208
193
|
const flat = await res.json();
|
|
209
194
|
const v = flat["theme.active"];
|
|
210
195
|
return typeof v === "string" ? v : null;
|
|
211
196
|
}
|
|
212
197
|
return {
|
|
213
|
-
async resolveActiveTheme(
|
|
198
|
+
async resolveActiveTheme() {
|
|
214
199
|
let previewOverride = null;
|
|
215
200
|
try {
|
|
216
201
|
const h = await headers();
|
|
@@ -221,7 +206,7 @@ function createThemeActive(registry, storage) {
|
|
|
221
206
|
const mod2 = registry.themes[previewOverride];
|
|
222
207
|
if (mod2) return { name: previewOverride, module: mod2 };
|
|
223
208
|
}
|
|
224
|
-
const stored = await fetchActiveFromCache(
|
|
209
|
+
const stored = await fetchActiveFromCache().catch(() => null);
|
|
225
210
|
const name = stored && stored in registry.themes ? stored : registry.defaultTheme;
|
|
226
211
|
const mod = registry.themes[name] ?? registry.themes[registry.defaultTheme];
|
|
227
212
|
if (!mod) {
|
|
@@ -236,7 +221,6 @@ function createThemeActive(registry, storage) {
|
|
|
236
221
|
|
|
237
222
|
// src/theme-config.ts
|
|
238
223
|
import {
|
|
239
|
-
DEFAULT_SITE_ID as DEFAULT_SITE_ID4,
|
|
240
224
|
resolveThemeValues,
|
|
241
225
|
themeSettingKey
|
|
242
226
|
} from "ampless";
|
|
@@ -247,23 +231,23 @@ function validateColorScheme(raw) {
|
|
|
247
231
|
return DEFAULT_COLOR_SCHEME;
|
|
248
232
|
}
|
|
249
233
|
function createThemeConfig(themeActive, storage) {
|
|
250
|
-
async function fetchRemote(
|
|
234
|
+
async function fetchRemote() {
|
|
251
235
|
if (!storage.isStorageConfigured()) return null;
|
|
252
236
|
let url;
|
|
253
237
|
try {
|
|
254
|
-
url = storage.publicAssetUrl(
|
|
238
|
+
url = storage.publicAssetUrl("public/site-settings.json");
|
|
255
239
|
} catch {
|
|
256
240
|
return null;
|
|
257
241
|
}
|
|
258
|
-
const res = await fetch(url, { next: { revalidate: 60, tags: [
|
|
242
|
+
const res = await fetch(url, { next: { revalidate: 60, tags: ["site-settings"] } });
|
|
259
243
|
if (!res.ok) return null;
|
|
260
244
|
return await res.json();
|
|
261
245
|
}
|
|
262
246
|
return {
|
|
263
|
-
async loadThemeConfig(
|
|
247
|
+
async loadThemeConfig() {
|
|
264
248
|
const [active, flat] = await Promise.all([
|
|
265
|
-
themeActive.resolveActiveTheme(
|
|
266
|
-
fetchRemote(
|
|
249
|
+
themeActive.resolveActiveTheme(),
|
|
250
|
+
fetchRemote().catch(() => null)
|
|
267
251
|
]);
|
|
268
252
|
const manifest = active.module.manifest;
|
|
269
253
|
const stored = {};
|
|
@@ -299,9 +283,17 @@ ${lines.join("\n")}
|
|
|
299
283
|
}
|
|
300
284
|
|
|
301
285
|
// src/rendering.ts
|
|
286
|
+
import { marked } from "marked";
|
|
302
287
|
function escape(s) {
|
|
303
288
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
304
289
|
}
|
|
290
|
+
function textAlignStyle(attrs) {
|
|
291
|
+
const v = attrs?.textAlign;
|
|
292
|
+
if (v === "left" || v === "center" || v === "right" || v === "justify") {
|
|
293
|
+
return ` style="text-align: ${v}"`;
|
|
294
|
+
}
|
|
295
|
+
return "";
|
|
296
|
+
}
|
|
305
297
|
function renderTiptap(node) {
|
|
306
298
|
if (node.type === "text") {
|
|
307
299
|
let html = escape(node.text ?? "");
|
|
@@ -310,6 +302,8 @@ function renderTiptap(node) {
|
|
|
310
302
|
else if (mark.type === "italic") html = `<em>${html}</em>`;
|
|
311
303
|
else if (mark.type === "code") html = `<code>${html}</code>`;
|
|
312
304
|
else if (mark.type === "strike") html = `<s>${html}</s>`;
|
|
305
|
+
else if (mark.type === "underline") html = `<u>${html}</u>`;
|
|
306
|
+
else if (mark.type === "highlight") html = `<mark>${html}</mark>`;
|
|
313
307
|
else if (mark.type === "link") {
|
|
314
308
|
const href = escape(String(mark.attrs?.href ?? "#"));
|
|
315
309
|
html = `<a href="${href}" target="_blank" rel="noopener">${html}</a>`;
|
|
@@ -322,10 +316,10 @@ function renderTiptap(node) {
|
|
|
322
316
|
case "doc":
|
|
323
317
|
return children;
|
|
324
318
|
case "paragraph":
|
|
325
|
-
return `<p>${children}</p>`;
|
|
319
|
+
return `<p${textAlignStyle(node.attrs)}>${children}</p>`;
|
|
326
320
|
case "heading": {
|
|
327
321
|
const level = Number(node.attrs?.level ?? 1);
|
|
328
|
-
return `<h${level}>${children}</h${level}>`;
|
|
322
|
+
return `<h${level}${textAlignStyle(node.attrs)}>${children}</h${level}>`;
|
|
329
323
|
}
|
|
330
324
|
case "bulletList":
|
|
331
325
|
return `<ul>${children}</ul>`;
|
|
@@ -350,52 +344,39 @@ function renderTiptap(node) {
|
|
|
350
344
|
const display = node.attrs?.display ? ` data-display="${escape(String(node.attrs.display))}"` : "";
|
|
351
345
|
return `<img src="${src}" alt="${alt}"${title}${display} loading="lazy" />`;
|
|
352
346
|
}
|
|
347
|
+
case "table":
|
|
348
|
+
return `<table class="tiptap-table"><tbody>${children}</tbody></table>`;
|
|
349
|
+
case "tableRow":
|
|
350
|
+
return `<tr>${children}</tr>`;
|
|
351
|
+
case "tableHeader":
|
|
352
|
+
return `<th${tableCellAttrs(node.attrs)}>${children}</th>`;
|
|
353
|
+
case "tableCell":
|
|
354
|
+
return `<td${tableCellAttrs(node.attrs)}>${children}</td>`;
|
|
355
|
+
case "taskList":
|
|
356
|
+
return `<ul data-type="taskList">${children}</ul>`;
|
|
357
|
+
case "taskItem": {
|
|
358
|
+
const checked = node.attrs?.checked === true ? "true" : "false";
|
|
359
|
+
return `<li data-type="taskItem" data-checked="${checked}">${children}</li>`;
|
|
360
|
+
}
|
|
353
361
|
default:
|
|
354
362
|
return children;
|
|
355
363
|
}
|
|
356
364
|
}
|
|
357
|
-
function
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
out.push(`<h2>${escape(line.slice(3))}</h2>`);
|
|
368
|
-
i++;
|
|
369
|
-
} else if (line.startsWith("```")) {
|
|
370
|
-
const lang = line.slice(3).trim();
|
|
371
|
-
const code = [];
|
|
372
|
-
i++;
|
|
373
|
-
while (i < lines.length && !(lines[i] ?? "").startsWith("```")) {
|
|
374
|
-
code.push(lines[i] ?? "");
|
|
375
|
-
i++;
|
|
376
|
-
}
|
|
377
|
-
i++;
|
|
378
|
-
out.push(
|
|
379
|
-
`<pre><code${lang ? ` class="language-${escape(lang)}"` : ""}>${escape(code.join("\n"))}</code></pre>`
|
|
380
|
-
);
|
|
381
|
-
} else if (line.startsWith("- ")) {
|
|
382
|
-
const items = [];
|
|
383
|
-
while (i < lines.length && (lines[i] ?? "").startsWith("- ")) {
|
|
384
|
-
items.push(`<li>${renderInlineMarkdown((lines[i] ?? "").slice(2))}</li>`);
|
|
385
|
-
i++;
|
|
386
|
-
}
|
|
387
|
-
out.push(`<ul>${items.join("")}</ul>`);
|
|
388
|
-
} else if (line.trim() === "") {
|
|
389
|
-
i++;
|
|
390
|
-
} else {
|
|
391
|
-
out.push(`<p>${renderInlineMarkdown(line)}</p>`);
|
|
392
|
-
i++;
|
|
393
|
-
}
|
|
365
|
+
function tableCellAttrs(attrs) {
|
|
366
|
+
let out = "";
|
|
367
|
+
const colspan = Number(attrs?.colspan ?? 1);
|
|
368
|
+
if (colspan > 1) out += ` colspan="${colspan}"`;
|
|
369
|
+
const rowspan = Number(attrs?.rowspan ?? 1);
|
|
370
|
+
if (rowspan > 1) out += ` rowspan="${rowspan}"`;
|
|
371
|
+
const colwidth = attrs?.colwidth;
|
|
372
|
+
if (Array.isArray(colwidth) && colwidth.length > 0) {
|
|
373
|
+
const w = Number(colwidth[0]);
|
|
374
|
+
if (Number.isFinite(w) && w > 0) out += ` style="width: ${w}px"`;
|
|
394
375
|
}
|
|
395
|
-
return out
|
|
376
|
+
return out;
|
|
396
377
|
}
|
|
397
|
-
function
|
|
398
|
-
return
|
|
378
|
+
function renderMarkdown(md) {
|
|
379
|
+
return marked.parse(md, { gfm: true, breaks: false, async: false });
|
|
399
380
|
}
|
|
400
381
|
function renderBody(post) {
|
|
401
382
|
if (post.format === "html") return String(post.body);
|
|
@@ -426,6 +407,8 @@ function tiptapNodeToMarkdown(node) {
|
|
|
426
407
|
else if (mark.type === "italic") txt = `*${txt}*`;
|
|
427
408
|
else if (mark.type === "code") txt = `\`${txt}\``;
|
|
428
409
|
else if (mark.type === "strike") txt = `~~${txt}~~`;
|
|
410
|
+
else if (mark.type === "underline") txt = `<u>${txt}</u>`;
|
|
411
|
+
else if (mark.type === "highlight") txt = `<mark>${txt}</mark>`;
|
|
429
412
|
else if (mark.type === "link") txt = `[${txt}](${String(mark.attrs?.href ?? "#")})`;
|
|
430
413
|
}
|
|
431
414
|
return txt;
|
|
@@ -463,12 +446,54 @@ function tiptapNodeToMarkdown(node) {
|
|
|
463
446
|
const alt = String(node.attrs?.alt ?? "");
|
|
464
447
|
return ``;
|
|
465
448
|
}
|
|
449
|
+
case "table":
|
|
450
|
+
return tiptapTableToMarkdown(node);
|
|
451
|
+
case "taskList":
|
|
452
|
+
return children + "\n";
|
|
453
|
+
case "taskItem": {
|
|
454
|
+
const checked = node.attrs?.checked === true ? "x" : " ";
|
|
455
|
+
const inner = children.replace(/\n+$/, "");
|
|
456
|
+
const [first, ...rest] = inner.split("\n");
|
|
457
|
+
const cont = rest.map((l) => l ? " " + l : l).join("\n");
|
|
458
|
+
return `- [${checked}] ${first ?? ""}${cont ? "\n" + cont : ""}
|
|
459
|
+
`;
|
|
460
|
+
}
|
|
466
461
|
default:
|
|
467
462
|
return children;
|
|
468
463
|
}
|
|
469
464
|
}
|
|
465
|
+
function tiptapTableToMarkdown(node) {
|
|
466
|
+
const rows = node.content ?? [];
|
|
467
|
+
if (rows.length === 0) return "";
|
|
468
|
+
const renderedRows = [];
|
|
469
|
+
let headerIdx = -1;
|
|
470
|
+
for (let i = 0; i < rows.length; i++) {
|
|
471
|
+
const row = rows[i];
|
|
472
|
+
const cells = row.content ?? [];
|
|
473
|
+
const cellTexts = cells.map((c) => {
|
|
474
|
+
const inner = (c.content ?? []).map(tiptapNodeToMarkdown).join("");
|
|
475
|
+
return inner.replace(/\n+$/, "").replace(/\n/g, "<br>").replace(/\|/g, "\\|");
|
|
476
|
+
});
|
|
477
|
+
renderedRows.push(cellTexts);
|
|
478
|
+
if (headerIdx === -1 && cells.some((c) => c.type === "tableHeader")) headerIdx = i;
|
|
479
|
+
}
|
|
480
|
+
if (headerIdx === -1) headerIdx = 0;
|
|
481
|
+
const header = renderedRows[headerIdx] ?? [];
|
|
482
|
+
const body = renderedRows.filter((_, i) => i !== headerIdx);
|
|
483
|
+
const cols = header.length;
|
|
484
|
+
const headerLine = "| " + header.join(" | ") + " |";
|
|
485
|
+
const sepLine = "| " + Array.from({ length: cols }, () => "---").join(" | ") + " |";
|
|
486
|
+
const bodyLines = body.map((r) => {
|
|
487
|
+
const cells = Array.from({ length: cols }, (_, i) => r[i] ?? "");
|
|
488
|
+
return "| " + cells.join(" | ") + " |";
|
|
489
|
+
});
|
|
490
|
+
return "\n" + [headerLine, sepLine, ...bodyLines].join("\n") + "\n\n";
|
|
491
|
+
}
|
|
470
492
|
function htmlToMarkdown(html) {
|
|
471
493
|
let md = html;
|
|
494
|
+
md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, inner) => {
|
|
495
|
+
return "\n" + convertHtmlTable(String(inner)) + "\n";
|
|
496
|
+
});
|
|
472
497
|
md = md.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, text) => {
|
|
473
498
|
return "\n" + "#".repeat(Number(level)) + " " + String(text).trim() + "\n\n";
|
|
474
499
|
});
|
|
@@ -481,6 +506,9 @@ function htmlToMarkdown(html) {
|
|
|
481
506
|
md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
|
|
482
507
|
return "\n" + String(content).trim().split("\n").map((l) => "> " + l).join("\n") + "\n\n";
|
|
483
508
|
});
|
|
509
|
+
md = md.replace(/<ul[^>]*data-type="taskList"[^>]*>([\s\S]*?)<\/ul>/gi, (_, items) => {
|
|
510
|
+
return "\n" + convertHtmlTaskList(String(items)) + "\n";
|
|
511
|
+
});
|
|
484
512
|
md = md.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, items) => {
|
|
485
513
|
return "\n" + String(items).replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n") + "\n";
|
|
486
514
|
});
|
|
@@ -500,11 +528,63 @@ function htmlToMarkdown(html) {
|
|
|
500
528
|
md = md.replace(/<(em|i)>([\s\S]*?)<\/\1>/gi, "*$2*");
|
|
501
529
|
md = md.replace(/<s>([\s\S]*?)<\/s>/gi, "~~$1~~");
|
|
502
530
|
md = md.replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`");
|
|
531
|
+
const PH_U_OPEN = "AMP_U_OPEN";
|
|
532
|
+
const PH_U_CLOSE = "AMP_U_CLOSE";
|
|
533
|
+
const PH_MARK_OPEN = "AMP_MARK_OPEN";
|
|
534
|
+
const PH_MARK_CLOSE = "AMP_MARK_CLOSE";
|
|
535
|
+
md = md.replace(/<u>([\s\S]*?)<\/u>/gi, `${PH_U_OPEN}$1${PH_U_CLOSE}`);
|
|
536
|
+
md = md.replace(/<mark>([\s\S]*?)<\/mark>/gi, `${PH_MARK_OPEN}$1${PH_MARK_CLOSE}`);
|
|
503
537
|
md = md.replace(/<\/?[^>]+>/g, "");
|
|
538
|
+
md = md.split(PH_U_OPEN).join("<u>").split(PH_U_CLOSE).join("</u>").split(PH_MARK_OPEN).join("<mark>").split(PH_MARK_CLOSE).join("</mark>");
|
|
504
539
|
md = md.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ");
|
|
505
540
|
md = md.replace(/\n{3,}/g, "\n\n");
|
|
506
541
|
return md.trim() + "\n";
|
|
507
542
|
}
|
|
543
|
+
function convertHtmlTable(inner) {
|
|
544
|
+
const stripped = inner.replace(/<\/?(thead|tbody)[^>]*>/gi, "");
|
|
545
|
+
const rowRe = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
|
|
546
|
+
const rows = [];
|
|
547
|
+
let m;
|
|
548
|
+
while ((m = rowRe.exec(stripped)) !== null) {
|
|
549
|
+
const rowHtml = m[1] ?? "";
|
|
550
|
+
const cellRe = /<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi;
|
|
551
|
+
const cells = [];
|
|
552
|
+
let isHeader = false;
|
|
553
|
+
let cm;
|
|
554
|
+
while ((cm = cellRe.exec(rowHtml)) !== null) {
|
|
555
|
+
if ((cm[1] ?? "").toLowerCase() === "th") isHeader = true;
|
|
556
|
+
cells.push(normalizeTableCell(cm[2] ?? ""));
|
|
557
|
+
}
|
|
558
|
+
if (cells.length > 0) rows.push({ isHeader, cells });
|
|
559
|
+
}
|
|
560
|
+
if (rows.length === 0) return "";
|
|
561
|
+
let headerIdx = rows.findIndex((r) => r.isHeader);
|
|
562
|
+
if (headerIdx === -1) headerIdx = 0;
|
|
563
|
+
const header = rows[headerIdx].cells;
|
|
564
|
+
const body = rows.filter((_, i) => i !== headerIdx).map((r) => r.cells);
|
|
565
|
+
const cols = header.length;
|
|
566
|
+
const headerLine = "| " + header.join(" | ") + " |";
|
|
567
|
+
const sepLine = "| " + Array.from({ length: cols }, () => "---").join(" | ") + " |";
|
|
568
|
+
const bodyLines = body.map((cells) => {
|
|
569
|
+
const padded = Array.from({ length: cols }, (_, i) => cells[i] ?? "");
|
|
570
|
+
return "| " + padded.join(" | ") + " |";
|
|
571
|
+
});
|
|
572
|
+
return [headerLine, sepLine, ...bodyLines].join("\n") + "\n";
|
|
573
|
+
}
|
|
574
|
+
function normalizeTableCell(html) {
|
|
575
|
+
return html.replace(/<br\s*\/?>/gi, " ").replace(/<\/?[^>]+>/g, "").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
|
|
576
|
+
}
|
|
577
|
+
function convertHtmlTaskList(items) {
|
|
578
|
+
const liRe = /<li[^>]*data-checked="(true|false)"[^>]*>([\s\S]*?)<\/li>/gi;
|
|
579
|
+
const out = [];
|
|
580
|
+
let m;
|
|
581
|
+
while ((m = liRe.exec(items)) !== null) {
|
|
582
|
+
const checked = m[1] === "true" ? "x" : " ";
|
|
583
|
+
const inner = String(m[2] ?? "").replace(/<\/?p[^>]*>/gi, "").trim();
|
|
584
|
+
out.push(`- [${checked}] ${inner}`);
|
|
585
|
+
}
|
|
586
|
+
return out.join("\n");
|
|
587
|
+
}
|
|
508
588
|
|
|
509
589
|
// src/index.ts
|
|
510
590
|
function createAmpless(opts) {
|
|
@@ -517,13 +597,13 @@ function createAmpless(opts) {
|
|
|
517
597
|
const themeConfig = createThemeConfig(themeActive, storage);
|
|
518
598
|
return {
|
|
519
599
|
listPublishedPosts: (o) => posts.listPublishedPosts(o),
|
|
520
|
-
getPublishedPost: (slug
|
|
600
|
+
getPublishedPost: (slug) => posts.getPublishedPost(slug),
|
|
521
601
|
listPostsByTag: (tag, o) => posts.listPostsByTag(tag, o),
|
|
522
|
-
loadSiteSettings: (
|
|
523
|
-
resolveActiveTheme: (
|
|
524
|
-
loadThemeConfig: (
|
|
525
|
-
postMetadata: (post
|
|
526
|
-
siteMetadata: (
|
|
602
|
+
loadSiteSettings: () => settings.loadSiteSettings(),
|
|
603
|
+
resolveActiveTheme: () => themeActive.resolveActiveTheme(),
|
|
604
|
+
loadThemeConfig: () => themeConfig.loadThemeConfig(),
|
|
605
|
+
postMetadata: (post) => seo.postMetadata(post),
|
|
606
|
+
siteMetadata: () => seo.siteMetadata(),
|
|
527
607
|
renderBody: (post) => renderBody(post),
|
|
528
608
|
renderThemeCss: (cssVars) => renderThemeCss(cssVars),
|
|
529
609
|
publicAssetUrl: (key) => storage.publicAssetUrl(key),
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { Config } from 'ampless';
|
|
2
|
+
import { Config, PostMetadata } from 'ampless';
|
|
3
3
|
|
|
4
|
+
declare const runtime = "nodejs";
|
|
4
5
|
interface CreateMiddlewareOpts {
|
|
5
6
|
cmsConfig: Config;
|
|
7
|
+
/** AppSync GraphQL endpoint URL (from `amplify_outputs.json` `data.url`). */
|
|
8
|
+
appsyncUrl: string;
|
|
9
|
+
/** Public API key for the AppSync endpoint (from `amplify_outputs.json` `data.api_key`). */
|
|
10
|
+
apiKey: string;
|
|
6
11
|
}
|
|
7
|
-
type MiddlewareFn = (request: NextRequest) => NextResponse
|
|
12
|
+
type MiddlewareFn = (request: NextRequest) => Promise<NextResponse>;
|
|
13
|
+
declare function _resetFlagCache(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Compute the `Cache-Control` header for a post response. The
|
|
16
|
+
* strategy lives on `post.metadata.cache`; absent (or 'auto') falls
|
|
17
|
+
* back to cooldown-by-edit-time logic. See `CacheStrategy` /
|
|
18
|
+
* `CacheConfig` in ampless types for the semantic contract.
|
|
19
|
+
*/
|
|
20
|
+
declare function computeCacheControl(flags: {
|
|
21
|
+
metadata: PostMetadata | null;
|
|
22
|
+
updatedAt: string;
|
|
23
|
+
}, cmsConfig: Config): string;
|
|
8
24
|
/**
|
|
9
25
|
* Build the ampless public-site middleware. Performs:
|
|
10
26
|
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* branch is needed.
|
|
24
|
-
*
|
|
25
|
-
* The factory captures the multi-site flag at construction time so the
|
|
26
|
-
* hot path stays a pair of cheap header lookups.
|
|
27
|
+
* - AppSync flag fetch (`format` / `metadata` / `updatedAt`) per
|
|
28
|
+
* slug, with a 200-entry LRU keyed by slug, 60s TTL. Hot slugs
|
|
29
|
+
* cost zero queries for the cache lifetime.
|
|
30
|
+
* - Rewrite to `/r/<slug>(/<path>)` when the post is no_layout HTML
|
|
31
|
+
* (`format=html` + `metadata.no_layout=true`) or a static bundle
|
|
32
|
+
* (`format='static'`). Themed posts (default) get no rewrite —
|
|
33
|
+
* `app/[slug]/page.tsx` serves them directly.
|
|
34
|
+
* - `Cache-Control` header computed from `post.metadata.cache` +
|
|
35
|
+
* `post.updatedAt` + `cms.config.cache.*` and set on the response.
|
|
36
|
+
* - `?previewTheme` / `?previewColorScheme` → `x-preview-theme` /
|
|
37
|
+
* `x-preview-color-scheme` header forwarding for the admin's
|
|
38
|
+
* iframe-based theme preview.
|
|
27
39
|
*/
|
|
28
|
-
declare function createAmplessMiddleware(
|
|
40
|
+
declare function createAmplessMiddleware(opts: CreateMiddlewareOpts): MiddlewareFn;
|
|
29
41
|
/**
|
|
30
42
|
* Reference matcher config — admin / api / login / static assets /
|
|
31
43
|
* amplify_outputs.json are excluded so middleware doesn't rewrite
|
|
32
|
-
* legitimate non-blog routes
|
|
44
|
+
* legitimate non-blog routes.
|
|
33
45
|
*
|
|
34
46
|
* **You can't re-export this directly.** Next.js 16's Turbopack
|
|
35
47
|
* requires `export const config` in `proxy.ts` (or `middleware.ts`)
|
|
@@ -46,4 +58,4 @@ declare const defaultMatcherConfig: {
|
|
|
46
58
|
matcher: string[];
|
|
47
59
|
};
|
|
48
60
|
|
|
49
|
-
export { type CreateMiddlewareOpts, type MiddlewareFn, createAmplessMiddleware, defaultMatcherConfig };
|
|
61
|
+
export { type CreateMiddlewareOpts, type MiddlewareFn, _resetFlagCache, computeCacheControl, createAmplessMiddleware, defaultMatcherConfig, runtime };
|