@mailoo/blog 1.33.0 → 1.34.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/README.md +26 -1
- package/dist/client/index.cjs +100 -37
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +101 -38
- package/dist/client/index.js.map +1 -1
- package/dist/client/printable.cjs +100 -37
- package/dist/client/printable.cjs.map +1 -1
- package/dist/client/printable.d.cts +19 -2
- package/dist/client/printable.d.ts +19 -2
- package/dist/client/printable.js +101 -38
- package/dist/client/printable.js.map +1 -1
- package/dist/routes/index.cjs +212 -21
- package/dist/routes/index.cjs.map +1 -1
- package/dist/routes/index.d.cts +11 -0
- package/dist/routes/index.d.ts +11 -0
- package/dist/routes/index.js +212 -21
- package/dist/routes/index.js.map +1 -1
- package/dist/server/index.cjs +39 -16
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +39 -16
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -3
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
type BlogPrintEvent = {
|
|
2
|
+
section: HTMLElement;
|
|
3
|
+
printTitle: string;
|
|
4
|
+
};
|
|
1
5
|
interface BlogPrintableButtonsProps {
|
|
2
6
|
/** Button label (host provides i18n) */
|
|
3
7
|
printLabel?: string;
|
|
4
8
|
buttonClassName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Limit enhancement to `[data-mailoo-print]` inside this root.
|
|
11
|
+
* When the root is missing, no buttons are added.
|
|
12
|
+
* Omit to enhance every section in the document.
|
|
13
|
+
*/
|
|
14
|
+
containerSelector?: string;
|
|
15
|
+
/** Called before the print window opens. The package does not call analytics. */
|
|
16
|
+
onPrint?: (event: BlogPrintEvent) => void;
|
|
17
|
+
/**
|
|
18
|
+
* When set, the print document appends a source block (label, document
|
|
19
|
+
* title, page URL). Host provides the locale string.
|
|
20
|
+
*/
|
|
21
|
+
sourceLabel?: string;
|
|
5
22
|
}
|
|
6
23
|
/**
|
|
7
24
|
* Enhances `[data-mailoo-print]` sections from API-generated article HTML with
|
|
8
25
|
* a print button.
|
|
9
26
|
*/
|
|
10
|
-
declare function BlogPrintableButtons({ printLabel, buttonClassName, }?: BlogPrintableButtonsProps): null;
|
|
27
|
+
declare function BlogPrintableButtons({ printLabel, buttonClassName, containerSelector, onPrint, sourceLabel, }?: BlogPrintableButtonsProps): null;
|
|
11
28
|
|
|
12
|
-
export { BlogPrintableButtons, type BlogPrintableButtonsProps };
|
|
29
|
+
export { type BlogPrintEvent, BlogPrintableButtons, type BlogPrintableButtonsProps };
|
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
type BlogPrintEvent = {
|
|
2
|
+
section: HTMLElement;
|
|
3
|
+
printTitle: string;
|
|
4
|
+
};
|
|
1
5
|
interface BlogPrintableButtonsProps {
|
|
2
6
|
/** Button label (host provides i18n) */
|
|
3
7
|
printLabel?: string;
|
|
4
8
|
buttonClassName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Limit enhancement to `[data-mailoo-print]` inside this root.
|
|
11
|
+
* When the root is missing, no buttons are added.
|
|
12
|
+
* Omit to enhance every section in the document.
|
|
13
|
+
*/
|
|
14
|
+
containerSelector?: string;
|
|
15
|
+
/** Called before the print window opens. The package does not call analytics. */
|
|
16
|
+
onPrint?: (event: BlogPrintEvent) => void;
|
|
17
|
+
/**
|
|
18
|
+
* When set, the print document appends a source block (label, document
|
|
19
|
+
* title, page URL). Host provides the locale string.
|
|
20
|
+
*/
|
|
21
|
+
sourceLabel?: string;
|
|
5
22
|
}
|
|
6
23
|
/**
|
|
7
24
|
* Enhances `[data-mailoo-print]` sections from API-generated article HTML with
|
|
8
25
|
* a print button.
|
|
9
26
|
*/
|
|
10
|
-
declare function BlogPrintableButtons({ printLabel, buttonClassName, }?: BlogPrintableButtonsProps): null;
|
|
27
|
+
declare function BlogPrintableButtons({ printLabel, buttonClassName, containerSelector, onPrint, sourceLabel, }?: BlogPrintableButtonsProps): null;
|
|
11
28
|
|
|
12
|
-
export { BlogPrintableButtons, type BlogPrintableButtonsProps };
|
|
29
|
+
export { type BlogPrintEvent, BlogPrintableButtons, type BlogPrintableButtonsProps };
|
package/dist/client/printable.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/components/blog-printable-buttons.tsx
|
|
4
|
-
import { useEffect } from "react";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
5
|
var PRINT_BUTTON_CLASSNAME = [
|
|
6
6
|
"inline-flex items-center justify-center rounded-lg font-medium transition-colors",
|
|
7
7
|
"focus:outline-none focus:ring-2 focus:ring-offset-2",
|
|
@@ -9,58 +9,117 @@ var PRINT_BUTTON_CLASSNAME = [
|
|
|
9
9
|
"dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800",
|
|
10
10
|
"px-3 py-1.5 text-sm"
|
|
11
11
|
].join(" ");
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function printMailooSection(section) {
|
|
16
|
-
const body = section.querySelector(".mailoo-printable-body");
|
|
17
|
-
if (!body) return;
|
|
18
|
-
const printTitle = section.getAttribute("data-print-title")?.trim() || document.title;
|
|
19
|
-
const win = window.open("", "_blank", "noopener,noreferrer");
|
|
20
|
-
if (!win) return;
|
|
21
|
-
const html = `<!DOCTYPE html>
|
|
22
|
-
<html lang="en">
|
|
23
|
-
<head>
|
|
24
|
-
<meta charset="utf-8">
|
|
25
|
-
<title>${escapeHtml(printTitle)}</title>
|
|
26
|
-
<style>
|
|
12
|
+
var ENHANCED_ATTR = "data-mailoo-print-enhanced";
|
|
13
|
+
var SECTION_SELECTOR = `[data-mailoo-print]:not([${ENHANCED_ATTR}])`;
|
|
14
|
+
var PRINT_WINDOW_STYLES = `
|
|
27
15
|
body {
|
|
28
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
16
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
29
17
|
line-height: 1.6;
|
|
30
|
-
|
|
31
|
-
margin: 2rem auto;
|
|
32
|
-
padding: 0 1rem;
|
|
18
|
+
margin: 1.5rem;
|
|
33
19
|
color: #111;
|
|
34
20
|
}
|
|
21
|
+
h1, h2, h3, h4 {
|
|
22
|
+
margin-top: 1.25em;
|
|
23
|
+
margin-bottom: 0.5em;
|
|
24
|
+
line-height: 1.25;
|
|
25
|
+
}
|
|
26
|
+
p { margin: 0.75em 0; }
|
|
27
|
+
ul, ol { padding-left: 1.5em; margin: 0.75em 0; }
|
|
28
|
+
li { margin: 0.25em 0; }
|
|
35
29
|
img { max-width: 100%; height: auto; }
|
|
36
30
|
a { color: #2563eb; }
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
blockquote {
|
|
32
|
+
margin: 1em 0;
|
|
33
|
+
padding-left: 1em;
|
|
34
|
+
border-left: 3px solid #d4d4d4;
|
|
35
|
+
color: #525252;
|
|
36
|
+
}
|
|
37
|
+
.mailoo-print-source {
|
|
38
|
+
margin-top: 2rem;
|
|
39
|
+
padding: 0.75rem 1rem;
|
|
40
|
+
border: 1px solid #e5e5e5;
|
|
41
|
+
border-radius: 0.5rem;
|
|
42
|
+
background: #fafafa;
|
|
43
|
+
font-size: 0.8125rem;
|
|
44
|
+
line-height: 1.45;
|
|
45
|
+
color: #525252;
|
|
46
|
+
}
|
|
47
|
+
.mailoo-print-source-label {
|
|
48
|
+
display: block;
|
|
49
|
+
margin-bottom: 0.35rem;
|
|
50
|
+
font-size: 0.6875rem;
|
|
51
|
+
font-weight: 650;
|
|
52
|
+
letter-spacing: 0.06em;
|
|
53
|
+
text-transform: uppercase;
|
|
54
|
+
color: #737373;
|
|
55
|
+
}
|
|
56
|
+
.mailoo-print-source-title {
|
|
57
|
+
display: block;
|
|
58
|
+
margin: 0;
|
|
59
|
+
font-size: 0.875rem;
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
color: #171717;
|
|
62
|
+
text-wrap: pretty;
|
|
63
|
+
}
|
|
64
|
+
.mailoo-print-source-url {
|
|
65
|
+
display: block;
|
|
66
|
+
margin-top: 0.25rem;
|
|
67
|
+
word-break: break-all;
|
|
68
|
+
color: #525252;
|
|
69
|
+
text-decoration: none;
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
function escapeHtml(text) {
|
|
73
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
74
|
+
}
|
|
75
|
+
function buildSourceBlock(sourceLabel) {
|
|
76
|
+
const pageTitle = document.title;
|
|
77
|
+
const pageUrl = window.location.href;
|
|
78
|
+
return `<aside class="mailoo-print-source"><span class="mailoo-print-source-label">${escapeHtml(sourceLabel)}</span><span class="mailoo-print-source-title">${escapeHtml(pageTitle)}</span><a class="mailoo-print-source-url" href="${escapeHtml(pageUrl)}">${escapeHtml(pageUrl)}</a></aside>`;
|
|
79
|
+
}
|
|
80
|
+
function printMailooSection(section, sourceLabel) {
|
|
81
|
+
const body = section.querySelector(".mailoo-printable-body");
|
|
82
|
+
if (!body) return;
|
|
83
|
+
const printTitle = section.getAttribute("data-print-title")?.trim() || document.title;
|
|
84
|
+
const win = window.open("", "_blank");
|
|
85
|
+
if (!win) return;
|
|
86
|
+
win.opener = null;
|
|
87
|
+
const lang = document.documentElement.lang;
|
|
88
|
+
const source = sourceLabel?.trim() ? buildSourceBlock(sourceLabel.trim()) : "";
|
|
89
|
+
const html = `<!DOCTYPE html><html lang="${escapeHtml(lang)}"><head><meta charset="utf-8"><title>${escapeHtml(printTitle)}</title><style>${PRINT_WINDOW_STYLES}</style></head><body>${body.innerHTML}${source}</body></html>`;
|
|
43
90
|
win.document.open();
|
|
44
91
|
win.document.write(html);
|
|
45
92
|
win.document.close();
|
|
46
93
|
win.focus();
|
|
47
|
-
|
|
48
|
-
|
|
94
|
+
const triggerPrint = () => {
|
|
95
|
+
try {
|
|
96
|
+
win.print();
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
49
99
|
};
|
|
50
|
-
|
|
51
|
-
win.
|
|
52
|
-
}
|
|
100
|
+
if (win.document.readyState === "complete") {
|
|
101
|
+
win.setTimeout(triggerPrint, 0);
|
|
102
|
+
} else {
|
|
103
|
+
win.addEventListener("load", triggerPrint, { once: true });
|
|
104
|
+
}
|
|
53
105
|
}
|
|
54
106
|
function BlogPrintableButtons({
|
|
55
107
|
printLabel = "Print summary",
|
|
56
|
-
buttonClassName = PRINT_BUTTON_CLASSNAME
|
|
108
|
+
buttonClassName = PRINT_BUTTON_CLASSNAME,
|
|
109
|
+
containerSelector,
|
|
110
|
+
onPrint,
|
|
111
|
+
sourceLabel
|
|
57
112
|
} = {}) {
|
|
113
|
+
const onPrintRef = useRef(onPrint);
|
|
114
|
+
const sourceLabelRef = useRef(sourceLabel);
|
|
115
|
+
onPrintRef.current = onPrint;
|
|
116
|
+
sourceLabelRef.current = sourceLabel;
|
|
58
117
|
useEffect(() => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
);
|
|
118
|
+
const root = containerSelector?.trim() ? document.querySelector(containerSelector) : document;
|
|
119
|
+
if (!root) return;
|
|
120
|
+
const sections = root.querySelectorAll(SECTION_SELECTOR);
|
|
62
121
|
for (const section of sections) {
|
|
63
|
-
section.setAttribute(
|
|
122
|
+
section.setAttribute(ENHANCED_ATTR, "true");
|
|
64
123
|
const actions = document.createElement("div");
|
|
65
124
|
actions.className = "mailoo-printable-actions";
|
|
66
125
|
const button = document.createElement("button");
|
|
@@ -68,12 +127,16 @@ function BlogPrintableButtons({
|
|
|
68
127
|
button.className = buttonClassName;
|
|
69
128
|
button.textContent = printLabel;
|
|
70
129
|
button.addEventListener("click", () => {
|
|
71
|
-
|
|
130
|
+
const body = section.querySelector(".mailoo-printable-body");
|
|
131
|
+
if (!body) return;
|
|
132
|
+
const printTitle = section.getAttribute("data-print-title")?.trim() || document.title;
|
|
133
|
+
onPrintRef.current?.({ section, printTitle });
|
|
134
|
+
printMailooSection(section, sourceLabelRef.current);
|
|
72
135
|
});
|
|
73
136
|
actions.appendChild(button);
|
|
74
137
|
section.insertBefore(actions, section.firstChild);
|
|
75
138
|
}
|
|
76
|
-
}, [printLabel, buttonClassName]);
|
|
139
|
+
}, [printLabel, buttonClassName, containerSelector]);
|
|
77
140
|
return null;
|
|
78
141
|
}
|
|
79
142
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/blog-printable-buttons.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\n\nconst PRINT_BUTTON_CLASSNAME = [\n 'inline-flex items-center justify-center rounded-lg font-medium transition-colors',\n 'focus:outline-none focus:ring-2 focus:ring-offset-2',\n 'border border-gray-300 bg-transparent text-gray-700 hover:bg-gray-50 focus:ring-gray-500',\n 'dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800',\n 'px-3 py-1.5 text-sm',\n].join(' ')\n\nfunction escapeHtml(text: string): string {\n return text\n .
|
|
1
|
+
{"version":3,"sources":["../../src/components/blog-printable-buttons.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef } from 'react'\n\nconst PRINT_BUTTON_CLASSNAME = [\n 'inline-flex items-center justify-center rounded-lg font-medium transition-colors',\n 'focus:outline-none focus:ring-2 focus:ring-offset-2',\n 'border border-gray-300 bg-transparent text-gray-700 hover:bg-gray-50 focus:ring-gray-500',\n 'dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800',\n 'px-3 py-1.5 text-sm',\n].join(' ')\n\nconst ENHANCED_ATTR = 'data-mailoo-print-enhanced'\nconst SECTION_SELECTOR = `[data-mailoo-print]:not([${ENHANCED_ATTR}])`\n\nconst PRINT_WINDOW_STYLES = `\n body {\n font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n line-height: 1.6;\n margin: 1.5rem;\n color: #111;\n }\n h1, h2, h3, h4 {\n margin-top: 1.25em;\n margin-bottom: 0.5em;\n line-height: 1.25;\n }\n p { margin: 0.75em 0; }\n ul, ol { padding-left: 1.5em; margin: 0.75em 0; }\n li { margin: 0.25em 0; }\n img { max-width: 100%; height: auto; }\n a { color: #2563eb; }\n blockquote {\n margin: 1em 0;\n padding-left: 1em;\n border-left: 3px solid #d4d4d4;\n color: #525252;\n }\n .mailoo-print-source {\n margin-top: 2rem;\n padding: 0.75rem 1rem;\n border: 1px solid #e5e5e5;\n border-radius: 0.5rem;\n background: #fafafa;\n font-size: 0.8125rem;\n line-height: 1.45;\n color: #525252;\n }\n .mailoo-print-source-label {\n display: block;\n margin-bottom: 0.35rem;\n font-size: 0.6875rem;\n font-weight: 650;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: #737373;\n }\n .mailoo-print-source-title {\n display: block;\n margin: 0;\n font-size: 0.875rem;\n font-weight: 600;\n color: #171717;\n text-wrap: pretty;\n }\n .mailoo-print-source-url {\n display: block;\n margin-top: 0.25rem;\n word-break: break-all;\n color: #525252;\n text-decoration: none;\n }\n`\n\nfunction escapeHtml(text: string): string {\n return text\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''')\n}\n\nfunction buildSourceBlock(sourceLabel: string): string {\n const pageTitle = document.title\n const pageUrl = window.location.href\n return (\n `<aside class=\"mailoo-print-source\">` +\n `<span class=\"mailoo-print-source-label\">${escapeHtml(sourceLabel)}</span>` +\n `<span class=\"mailoo-print-source-title\">${escapeHtml(pageTitle)}</span>` +\n `<a class=\"mailoo-print-source-url\" href=\"${escapeHtml(pageUrl)}\">${escapeHtml(pageUrl)}</a>` +\n `</aside>`\n )\n}\n\nfunction printMailooSection(\n section: HTMLElement,\n sourceLabel: string | undefined\n): void {\n const body = section.querySelector('.mailoo-printable-body')\n if (!body) return\n\n const printTitle =\n section.getAttribute('data-print-title')?.trim() || document.title\n // Do not pass noopener: some browsers return null from window.open while\n // still opening a blank tab, so document.write never runs.\n const win = window.open('', '_blank')\n if (!win) return\n\n win.opener = null\n const lang = document.documentElement.lang\n const source = sourceLabel?.trim() ? buildSourceBlock(sourceLabel.trim()) : ''\n const html =\n `<!DOCTYPE html><html lang=\"${escapeHtml(lang)}\">` +\n `<head><meta charset=\"utf-8\"><title>${escapeHtml(printTitle)}</title>` +\n `<style>${PRINT_WINDOW_STYLES}</style></head>` +\n `<body>${body.innerHTML}${source}</body></html>`\n\n win.document.open()\n win.document.write(html)\n win.document.close()\n win.focus()\n\n const triggerPrint = () => {\n try {\n win.print()\n } catch {\n // Window may already be closed by the user.\n }\n }\n\n if (win.document.readyState === 'complete') {\n win.setTimeout(triggerPrint, 0)\n } else {\n win.addEventListener('load', triggerPrint, { once: true })\n }\n}\n\nexport type BlogPrintEvent = {\n section: HTMLElement\n printTitle: string\n}\n\nexport interface BlogPrintableButtonsProps {\n /** Button label (host provides i18n) */\n printLabel?: string\n buttonClassName?: string\n /**\n * Limit enhancement to `[data-mailoo-print]` inside this root.\n * When the root is missing, no buttons are added.\n * Omit to enhance every section in the document.\n */\n containerSelector?: string\n /** Called before the print window opens. The package does not call analytics. */\n onPrint?: (event: BlogPrintEvent) => void\n /**\n * When set, the print document appends a source block (label, document\n * title, page URL). Host provides the locale string.\n */\n sourceLabel?: string\n}\n\n/**\n * Enhances `[data-mailoo-print]` sections from API-generated article HTML with\n * a print button.\n */\nexport function BlogPrintableButtons({\n printLabel = 'Print summary',\n buttonClassName = PRINT_BUTTON_CLASSNAME,\n containerSelector,\n onPrint,\n sourceLabel,\n}: BlogPrintableButtonsProps = {}) {\n const onPrintRef = useRef(onPrint)\n const sourceLabelRef = useRef(sourceLabel)\n onPrintRef.current = onPrint\n sourceLabelRef.current = sourceLabel\n\n useEffect(() => {\n const root = containerSelector?.trim()\n ? document.querySelector(containerSelector)\n : document\n if (!root) return\n\n const sections = root.querySelectorAll<HTMLElement>(SECTION_SELECTOR)\n\n for (const section of sections) {\n section.setAttribute(ENHANCED_ATTR, 'true')\n\n const actions = document.createElement('div')\n actions.className = 'mailoo-printable-actions'\n\n const button = document.createElement('button')\n button.type = 'button'\n button.className = buttonClassName\n button.textContent = printLabel\n button.addEventListener('click', () => {\n const body = section.querySelector('.mailoo-printable-body')\n if (!body) return\n const printTitle =\n section.getAttribute('data-print-title')?.trim() || document.title\n onPrintRef.current?.({ section, printTitle })\n printMailooSection(section, sourceLabelRef.current)\n })\n\n actions.appendChild(button)\n section.insertBefore(actions, section.firstChild)\n }\n }, [printLabel, buttonClassName, containerSelector])\n\n return null\n}\n"],"mappings":"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","names":[]}
|
package/dist/routes/index.cjs
CHANGED
|
@@ -37,12 +37,133 @@ var import_next_core2 = require("@mailoo/next-core");
|
|
|
37
37
|
// src/server/client.ts
|
|
38
38
|
var import_navigation = require("next/navigation");
|
|
39
39
|
var import_next_core = require("@mailoo/next-core");
|
|
40
|
+
|
|
41
|
+
// src/server/image-rewrite.ts
|
|
40
42
|
var import_images = require("@mailoo/images");
|
|
43
|
+
function asRecord(value) {
|
|
44
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function readEmbedBase(...candidates) {
|
|
50
|
+
for (const candidate of candidates) {
|
|
51
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
52
|
+
return candidate.trim();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
57
|
+
function rewriteBlogPostMedia(post, canonicalPrefixWithIdEquals, proxyPrefixWithIdEquals) {
|
|
58
|
+
return (0, import_images.rewriteMailooBlogMediaUrls)(post, {
|
|
59
|
+
canonicalPrefixWithIdEquals,
|
|
60
|
+
proxyPrefixWithIdEquals
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function rewriteCategoryDescriptionMedia(data, canonicalPrefixWithIdEquals, proxyPrefixWithIdEquals) {
|
|
64
|
+
const opts = {
|
|
65
|
+
canonicalPrefixWithIdEquals,
|
|
66
|
+
proxyPrefixWithIdEquals
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
...data,
|
|
70
|
+
description: typeof data.description === "string" ? (0, import_images.rewriteMailooPublicImageUrls)(data.description, opts) : data.description,
|
|
71
|
+
descriptionHtml: typeof data.descriptionHtml === "string" ? (0, import_images.rewriteMailooPublicImageUrls)(data.descriptionHtml, opts) : data.descriptionHtml
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function rewriteEmbedUrlsAnyHost(value, proxyPrefix) {
|
|
75
|
+
const rewritten = (0, import_images.rewriteMailooPublicImageUrlsAnyHost)(
|
|
76
|
+
JSON.stringify(value),
|
|
77
|
+
proxyPrefix
|
|
78
|
+
);
|
|
79
|
+
return JSON.parse(rewritten);
|
|
80
|
+
}
|
|
81
|
+
function rewritePostRecord(post, canonical, proxy) {
|
|
82
|
+
return rewriteBlogPostMedia(
|
|
83
|
+
post,
|
|
84
|
+
canonical,
|
|
85
|
+
proxy
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function rewriteBlogListEnvelope(body, proxyPrefix) {
|
|
89
|
+
const record = asRecord(body);
|
|
90
|
+
const canonical = readEmbedBase(record?.imagePublicEmbedBaseUrl);
|
|
91
|
+
if (!record || !canonical) {
|
|
92
|
+
return { ok: false, reason: "missing-embed-base" };
|
|
93
|
+
}
|
|
94
|
+
if (!Array.isArray(record.data)) {
|
|
95
|
+
return { ok: true, body };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
body: {
|
|
100
|
+
...record,
|
|
101
|
+
data: record.data.map((item) => {
|
|
102
|
+
const post = asRecord(item);
|
|
103
|
+
if (!post) return item;
|
|
104
|
+
return rewritePostRecord(post, canonical, proxyPrefix);
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function rewriteBlogPostEnvelope(body, proxyPrefix) {
|
|
110
|
+
const record = asRecord(body);
|
|
111
|
+
const data = asRecord(record?.data);
|
|
112
|
+
const canonical = readEmbedBase(
|
|
113
|
+
data?.imagePublicEmbedBaseUrl,
|
|
114
|
+
record?.imagePublicEmbedBaseUrl
|
|
115
|
+
);
|
|
116
|
+
if (!record || !data || !canonical) {
|
|
117
|
+
return { ok: false, reason: "missing-embed-base" };
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
body: {
|
|
122
|
+
...record,
|
|
123
|
+
data: rewritePostRecord(data, canonical, proxyPrefix)
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function rewriteBlogCategoryDescriptionEnvelope(body, proxyPrefix) {
|
|
128
|
+
const record = asRecord(body);
|
|
129
|
+
const data = asRecord(record?.data);
|
|
130
|
+
const canonical = readEmbedBase(
|
|
131
|
+
data?.imagePublicEmbedBaseUrl,
|
|
132
|
+
record?.imagePublicEmbedBaseUrl
|
|
133
|
+
);
|
|
134
|
+
if (!record || !data || !canonical) {
|
|
135
|
+
return { ok: false, reason: "missing-embed-base" };
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
body: {
|
|
140
|
+
...record,
|
|
141
|
+
data: rewriteCategoryDescriptionMedia(
|
|
142
|
+
data,
|
|
143
|
+
canonical,
|
|
144
|
+
proxyPrefix
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function rewriteBlogJsonLdEnvelope(body, proxyPrefix) {
|
|
150
|
+
try {
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
body: rewriteEmbedUrlsAnyHost(body, proxyPrefix)
|
|
154
|
+
};
|
|
155
|
+
} catch {
|
|
156
|
+
return { ok: false, reason: "invalid-json" };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/server/client.ts
|
|
41
161
|
function getMailooBlogConfigFromEnv() {
|
|
42
162
|
return (0, import_next_core.readMailooIntegrationConfig)({ prefix: "MAILOO_BLOG" });
|
|
43
163
|
}
|
|
44
164
|
|
|
45
165
|
// src/routes/index.ts
|
|
166
|
+
var EMBED_BASE_ERROR = "Blog API contract violation: imagePublicEmbedBaseUrl missing";
|
|
46
167
|
function resolveConfig(options) {
|
|
47
168
|
return options?.getConfig?.() ?? getMailooBlogConfigFromEnv();
|
|
48
169
|
}
|
|
@@ -52,7 +173,46 @@ function notConfigured(logger) {
|
|
|
52
173
|
);
|
|
53
174
|
return (0, import_next_core2.jsonError)({ error: "Blog API not configured" }, 503);
|
|
54
175
|
}
|
|
55
|
-
|
|
176
|
+
function imageProxyPrefix(options) {
|
|
177
|
+
const raw = options?.imageProxyPrefixWithIdEquals?.trim();
|
|
178
|
+
return raw || void 0;
|
|
179
|
+
}
|
|
180
|
+
function localeSearchParams(request, options, mode) {
|
|
181
|
+
const source = request.nextUrl.searchParams;
|
|
182
|
+
const params = mode === "all" ? new URLSearchParams(source) : new URLSearchParams();
|
|
183
|
+
if (mode === "locale-only") {
|
|
184
|
+
const fromQuery = source.get("locale");
|
|
185
|
+
if (fromQuery) params.set("locale", fromQuery);
|
|
186
|
+
}
|
|
187
|
+
if (!params.get("locale")) {
|
|
188
|
+
const resolved = options?.resolveLocale?.(request);
|
|
189
|
+
const locale = typeof resolved === "string" ? resolved.trim() : "";
|
|
190
|
+
if (locale) params.set("locale", locale);
|
|
191
|
+
}
|
|
192
|
+
return params;
|
|
193
|
+
}
|
|
194
|
+
function jsonResponse(body, status) {
|
|
195
|
+
return new Response(JSON.stringify(body), {
|
|
196
|
+
status,
|
|
197
|
+
headers: { "Content-Type": "application/json" }
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function rewriteProxiedBody(kind, body, proxyPrefix) {
|
|
201
|
+
switch (kind) {
|
|
202
|
+
case "list":
|
|
203
|
+
return rewriteBlogListEnvelope(body, proxyPrefix);
|
|
204
|
+
case "post":
|
|
205
|
+
return rewriteBlogPostEnvelope(body, proxyPrefix);
|
|
206
|
+
case "category-description":
|
|
207
|
+
return rewriteBlogCategoryDescriptionEnvelope(body, proxyPrefix);
|
|
208
|
+
case "json-ld":
|
|
209
|
+
return rewriteBlogJsonLdEnvelope(body, proxyPrefix);
|
|
210
|
+
case "passthrough":
|
|
211
|
+
return { ok: true, body };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function proxyGet(config, pathSuffix, searchParams, options, kind) {
|
|
215
|
+
const logger = options?.logger ?? import_next_core2.defaultMailooLogger;
|
|
56
216
|
const url = new URL(
|
|
57
217
|
`${config.apiBaseUrl}/api/v1/blog/${config.projectUid}/integrations/${config.integrationId}${pathSuffix}`
|
|
58
218
|
);
|
|
@@ -69,10 +229,20 @@ async function proxyGet(config, pathSuffix, searchParams) {
|
|
|
69
229
|
}
|
|
70
230
|
});
|
|
71
231
|
const data = await response.json();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
232
|
+
const prefix = imageProxyPrefix(options);
|
|
233
|
+
if (!response.ok || !prefix || kind === "passthrough") {
|
|
234
|
+
return jsonResponse(data, response.status);
|
|
235
|
+
}
|
|
236
|
+
const rewritten = rewriteProxiedBody(kind, data, prefix);
|
|
237
|
+
if (!rewritten.ok) {
|
|
238
|
+
if (rewritten.reason === "missing-embed-base") {
|
|
239
|
+
logger.error(EMBED_BASE_ERROR);
|
|
240
|
+
return (0, import_next_core2.jsonError)({ error: EMBED_BASE_ERROR }, 502);
|
|
241
|
+
}
|
|
242
|
+
logger.error("Blog API JSON-LD rewrite failed");
|
|
243
|
+
return (0, import_next_core2.jsonError)({ error: "Internal server error" }, 500);
|
|
244
|
+
}
|
|
245
|
+
return jsonResponse(rewritten.body, response.status);
|
|
76
246
|
}
|
|
77
247
|
function createBlogListHandler(options) {
|
|
78
248
|
const logger = options?.logger ?? import_next_core2.defaultMailooLogger;
|
|
@@ -80,7 +250,13 @@ function createBlogListHandler(options) {
|
|
|
80
250
|
const config = resolveConfig(options);
|
|
81
251
|
if (!config) return notConfigured(logger);
|
|
82
252
|
try {
|
|
83
|
-
return await proxyGet(
|
|
253
|
+
return await proxyGet(
|
|
254
|
+
config,
|
|
255
|
+
"",
|
|
256
|
+
localeSearchParams(request, options, "all"),
|
|
257
|
+
options,
|
|
258
|
+
"list"
|
|
259
|
+
);
|
|
84
260
|
} catch (error) {
|
|
85
261
|
logger.logException?.(error, "Blog API proxy error");
|
|
86
262
|
return (0, import_next_core2.jsonError)({ error: "Internal server error" }, 500);
|
|
@@ -94,10 +270,13 @@ function createBlogSlugHandler(options) {
|
|
|
94
270
|
if (!config) return notConfigured(logger);
|
|
95
271
|
const { slug } = await ctx.params;
|
|
96
272
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
273
|
+
return await proxyGet(
|
|
274
|
+
config,
|
|
275
|
+
`/slug/${encodeURIComponent(slug)}`,
|
|
276
|
+
localeSearchParams(request, options, "locale-only"),
|
|
277
|
+
options,
|
|
278
|
+
"post"
|
|
279
|
+
);
|
|
101
280
|
} catch (error) {
|
|
102
281
|
logger.logException?.(error, "Blog slug API proxy error");
|
|
103
282
|
return (0, import_next_core2.jsonError)({ error: "Internal server error" }, 500);
|
|
@@ -110,7 +289,13 @@ function createBlogCategoriesHandler(options) {
|
|
|
110
289
|
const config = resolveConfig(options);
|
|
111
290
|
if (!config) return notConfigured(logger);
|
|
112
291
|
try {
|
|
113
|
-
return await proxyGet(
|
|
292
|
+
return await proxyGet(
|
|
293
|
+
config,
|
|
294
|
+
"/categories",
|
|
295
|
+
localeSearchParams(request, options, "all"),
|
|
296
|
+
options,
|
|
297
|
+
"passthrough"
|
|
298
|
+
);
|
|
114
299
|
} catch (error) {
|
|
115
300
|
logger.logException?.(error, "Blog categories API proxy error");
|
|
116
301
|
return (0, import_next_core2.jsonError)({ error: "Internal server error" }, 500);
|
|
@@ -123,7 +308,13 @@ function createBlogTagsHandler(options) {
|
|
|
123
308
|
const config = resolveConfig(options);
|
|
124
309
|
if (!config) return notConfigured(logger);
|
|
125
310
|
try {
|
|
126
|
-
return await proxyGet(
|
|
311
|
+
return await proxyGet(
|
|
312
|
+
config,
|
|
313
|
+
"/tags",
|
|
314
|
+
localeSearchParams(request, options, "all"),
|
|
315
|
+
options,
|
|
316
|
+
"passthrough"
|
|
317
|
+
);
|
|
127
318
|
} catch (error) {
|
|
128
319
|
logger.logException?.(error, "Blog tags API proxy error");
|
|
129
320
|
return (0, import_next_core2.jsonError)({ error: "Internal server error" }, 500);
|
|
@@ -140,7 +331,9 @@ function createBlogTagSlugHandler(options) {
|
|
|
140
331
|
return await proxyGet(
|
|
141
332
|
config,
|
|
142
333
|
`/tags/slug/${encodeURIComponent(slug)}`,
|
|
143
|
-
request
|
|
334
|
+
localeSearchParams(request, options, "all"),
|
|
335
|
+
options,
|
|
336
|
+
"passthrough"
|
|
144
337
|
);
|
|
145
338
|
} catch (error) {
|
|
146
339
|
logger.logException?.(error, "Blog tag slug API proxy error");
|
|
@@ -155,13 +348,12 @@ function createBlogCategoryDescriptionHandler(options) {
|
|
|
155
348
|
if (!config) return notConfigured(logger);
|
|
156
349
|
const { slug } = await ctx.params;
|
|
157
350
|
try {
|
|
158
|
-
const locale = request.nextUrl.searchParams.get("locale");
|
|
159
|
-
const qs = new URLSearchParams();
|
|
160
|
-
if (locale) qs.set("locale", locale);
|
|
161
351
|
return await proxyGet(
|
|
162
352
|
config,
|
|
163
353
|
`/categories/slug/${encodeURIComponent(slug)}/description`,
|
|
164
|
-
|
|
354
|
+
localeSearchParams(request, options, "locale-only"),
|
|
355
|
+
options,
|
|
356
|
+
"category-description"
|
|
165
357
|
);
|
|
166
358
|
} catch (error) {
|
|
167
359
|
logger.logException?.(error, "Blog category description API proxy error");
|
|
@@ -176,13 +368,12 @@ function createBlogJsonLdHandler(options) {
|
|
|
176
368
|
if (!config) return notConfigured(logger);
|
|
177
369
|
const { slug } = await ctx.params;
|
|
178
370
|
try {
|
|
179
|
-
const locale = request.nextUrl.searchParams.get("locale");
|
|
180
|
-
const qs = new URLSearchParams();
|
|
181
|
-
if (locale) qs.set("locale", locale);
|
|
182
371
|
return await proxyGet(
|
|
183
372
|
config,
|
|
184
373
|
`/slug/${encodeURIComponent(slug)}/json-ld`,
|
|
185
|
-
|
|
374
|
+
localeSearchParams(request, options, "locale-only"),
|
|
375
|
+
options,
|
|
376
|
+
"json-ld"
|
|
186
377
|
);
|
|
187
378
|
} catch (error) {
|
|
188
379
|
logger.logException?.(error, "Blog JSON-LD API proxy error");
|