@ox-content/vite-plugin 2.81.0 → 2.82.1
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/dist/incremental-dom.cjs +3 -2
- package/dist/incremental-dom.cjs.map +1 -1
- package/dist/incremental-dom.d.cts.map +1 -1
- package/dist/incremental-dom.d.mts.map +1 -1
- package/dist/incremental-dom.mjs +2 -1
- package/dist/incremental-dom.mjs.map +1 -1
- package/dist/index.cjs +1486 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -13
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +8 -13
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1429 -69
- package/dist/index.mjs.map +1 -1
- package/dist/interop.cjs.map +1 -1
- package/dist/interop.mjs.map +1 -1
- package/dist/tabs.cjs +3 -4
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.mjs +99 -2
- package/dist/tabs.mjs.map +1 -0
- package/dist/vitepress-cli.cjs +5 -4
- package/dist/vitepress-cli.cjs.map +1 -1
- package/dist/vitepress-cli.d.cts +1 -1
- package/dist/vitepress-cli.d.mts +1 -1
- package/dist/vitepress-cli.mjs +4 -2
- package/dist/vitepress-cli.mjs.map +1 -1
- package/dist/vitepress.cjs +106 -2
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.mjs +32 -2
- package/dist/vitepress.mjs.map +1 -1
- package/package.json +19 -19
- package/dist/chunk.cjs +0 -57
- package/dist/github.cjs +0 -690
- package/dist/github.cjs.map +0 -1
- package/dist/github.mjs +0 -625
- package/dist/github.mjs.map +0 -1
- package/dist/github2.mjs +0 -2
- package/dist/media.cjs +0 -310
- package/dist/media.cjs.map +0 -1
- package/dist/media.mjs +0 -302
- package/dist/media.mjs.map +0 -1
- package/dist/media2.mjs +0 -2
- package/dist/mermaid.cjs +0 -114
- package/dist/mermaid.cjs.map +0 -1
- package/dist/mermaid.mjs +0 -2
- package/dist/mermaid2.mjs +0 -92
- package/dist/mermaid2.mjs.map +0 -1
- package/dist/napi.cjs +0 -45
- package/dist/napi.cjs.map +0 -1
- package/dist/napi.mjs +0 -34
- package/dist/napi.mjs.map +0 -1
- package/dist/ogp.cjs +0 -341
- package/dist/ogp.cjs.map +0 -1
- package/dist/ogp.mjs +0 -2
- package/dist/ogp2.mjs +0 -302
- package/dist/ogp2.mjs.map +0 -1
- package/dist/pm.cjs +0 -45
- package/dist/pm.cjs.map +0 -1
- package/dist/pm.mjs +0 -2
- package/dist/pm2.mjs +0 -38
- package/dist/pm2.mjs.map +0 -1
- package/dist/tabs2.mjs +0 -56
- package/dist/tabs2.mjs.map +0 -1
- package/dist/youtube.cjs +0 -57
- package/dist/youtube.cjs.map +0 -1
- package/dist/youtube.mjs +0 -2
- package/dist/youtube2.mjs +0 -35
- package/dist/youtube2.mjs.map +0 -1
package/dist/ogp2.mjs
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import { t as interopDefault } from "./interop.mjs";
|
|
2
|
-
import { unified } from "unified";
|
|
3
|
-
import rehypeParsePlugin from "rehype-parse";
|
|
4
|
-
import rehypeStringifyPlugin from "rehype-stringify";
|
|
5
|
-
//#region src/plugins/ogp.ts
|
|
6
|
-
/**
|
|
7
|
-
* OGP Card Plugin - Link card embedding
|
|
8
|
-
*
|
|
9
|
-
* Transforms <OgCard> components into static link preview cards
|
|
10
|
-
* by fetching OGP metadata at build time.
|
|
11
|
-
*/
|
|
12
|
-
const rehypeParse = interopDefault(rehypeParsePlugin);
|
|
13
|
-
const rehypeStringify = interopDefault(rehypeStringifyPlugin);
|
|
14
|
-
const defaultOptions = {
|
|
15
|
-
timeout: 1e4,
|
|
16
|
-
cache: true,
|
|
17
|
-
cacheTTL: 36e5,
|
|
18
|
-
userAgent: "ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)"
|
|
19
|
-
};
|
|
20
|
-
const ogpCache = /* @__PURE__ */ new Map();
|
|
21
|
-
function isPrivateIPv4(hostname) {
|
|
22
|
-
const parts = hostname.split(".").map(Number);
|
|
23
|
-
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
24
|
-
const [a, b] = parts;
|
|
25
|
-
return a === 10 || a === 127 || a === 0 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
26
|
-
}
|
|
27
|
-
function isSafeOgpUrl(value) {
|
|
28
|
-
try {
|
|
29
|
-
const url = new URL(value);
|
|
30
|
-
const host = url.hostname.toLowerCase();
|
|
31
|
-
const ipv6 = host.replace(/^\[|\]$/g, "");
|
|
32
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
33
|
-
if (host === "localhost" || host.endsWith(".localhost")) return false;
|
|
34
|
-
if (ipv6.includes(":") && (ipv6 === "::1" || ipv6.startsWith("fc") || ipv6.startsWith("fd") || ipv6.startsWith("fe80"))) return false;
|
|
35
|
-
return !isPrivateIPv4(host);
|
|
36
|
-
} catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Get element attribute value.
|
|
42
|
-
*/
|
|
43
|
-
function getAttribute(el, name) {
|
|
44
|
-
const value = el.properties?.[name];
|
|
45
|
-
if (typeof value === "string") return value;
|
|
46
|
-
if (Array.isArray(value)) return value.join(" ");
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Extract domain from URL.
|
|
50
|
-
*/
|
|
51
|
-
function extractDomain(url) {
|
|
52
|
-
try {
|
|
53
|
-
return new URL(url).hostname;
|
|
54
|
-
} catch {
|
|
55
|
-
return url;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Get favicon URL for a domain.
|
|
60
|
-
*/
|
|
61
|
-
function getFaviconUrl(url) {
|
|
62
|
-
try {
|
|
63
|
-
return `https://www.google.com/s2/favicons?domain=${new URL(url).hostname}&sz=32`;
|
|
64
|
-
} catch {
|
|
65
|
-
return "";
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Parse OGP metadata from HTML.
|
|
70
|
-
*/
|
|
71
|
-
function parseOgpFromHtml(html, url) {
|
|
72
|
-
const result = {
|
|
73
|
-
url,
|
|
74
|
-
title: ""
|
|
75
|
-
};
|
|
76
|
-
const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
77
|
-
result.title = (html.match(/<meta[^>]*property=["']og:title["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:title["']/i))?.[1] || titleMatch?.[1] || extractDomain(url);
|
|
78
|
-
const descMatch = html.match(/<meta[^>]*property=["']og:description["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:description["']/i) || html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*name=["']description["']/i);
|
|
79
|
-
if (descMatch) result.description = descMatch[1];
|
|
80
|
-
const imageMatch = html.match(/<meta[^>]*property=["']og:image["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:image["']/i);
|
|
81
|
-
if (imageMatch) {
|
|
82
|
-
let imageUrl = imageMatch[1];
|
|
83
|
-
if (imageUrl.startsWith("/")) try {
|
|
84
|
-
const urlObj = new URL(url);
|
|
85
|
-
imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;
|
|
86
|
-
} catch {}
|
|
87
|
-
result.image = imageUrl;
|
|
88
|
-
}
|
|
89
|
-
const siteNameMatch = html.match(/<meta[^>]*property=["']og:site_name["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:site_name["']/i);
|
|
90
|
-
if (siteNameMatch) result.siteName = siteNameMatch[1];
|
|
91
|
-
result.favicon = getFaviconUrl(url);
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Fetch OGP data for a URL.
|
|
96
|
-
*/
|
|
97
|
-
async function fetchOgpData(url, options) {
|
|
98
|
-
if (!isSafeOgpUrl(url)) return null;
|
|
99
|
-
if (options.cache) {
|
|
100
|
-
const cached = ogpCache.get(url);
|
|
101
|
-
if (cached && Date.now() - cached.timestamp < options.cacheTTL) return cached.data;
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
const controller = new AbortController();
|
|
105
|
-
const timeoutId = setTimeout(() => controller.abort(), options.timeout);
|
|
106
|
-
const response = await fetch(url, {
|
|
107
|
-
headers: {
|
|
108
|
-
"User-Agent": options.userAgent,
|
|
109
|
-
Accept: "text/html,application/xhtml+xml"
|
|
110
|
-
},
|
|
111
|
-
signal: controller.signal
|
|
112
|
-
});
|
|
113
|
-
clearTimeout(timeoutId);
|
|
114
|
-
if (!response.ok) {
|
|
115
|
-
console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
const data = parseOgpFromHtml(await response.text(), url);
|
|
119
|
-
if (options.cache) ogpCache.set(url, {
|
|
120
|
-
data,
|
|
121
|
-
timestamp: Date.now()
|
|
122
|
-
});
|
|
123
|
-
return data;
|
|
124
|
-
} catch (error) {
|
|
125
|
-
if (error instanceof Error && error.name === "AbortError") console.warn(`Timeout fetching OGP for ${url}`);
|
|
126
|
-
else console.warn(`Error fetching OGP for ${url}:`, error);
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Create OGP card element.
|
|
132
|
-
*/
|
|
133
|
-
function createOgpCard(data) {
|
|
134
|
-
const children = [];
|
|
135
|
-
const contentChildren = [];
|
|
136
|
-
contentChildren.push({
|
|
137
|
-
type: "element",
|
|
138
|
-
tagName: "div",
|
|
139
|
-
properties: { className: ["ox-ogp-title"] },
|
|
140
|
-
children: [{
|
|
141
|
-
type: "text",
|
|
142
|
-
value: data.title
|
|
143
|
-
}]
|
|
144
|
-
});
|
|
145
|
-
if (data.description) contentChildren.push({
|
|
146
|
-
type: "element",
|
|
147
|
-
tagName: "div",
|
|
148
|
-
properties: { className: ["ox-ogp-description"] },
|
|
149
|
-
children: [{
|
|
150
|
-
type: "text",
|
|
151
|
-
value: data.description
|
|
152
|
-
}]
|
|
153
|
-
});
|
|
154
|
-
const metaChildren = [];
|
|
155
|
-
if (data.favicon) metaChildren.push({
|
|
156
|
-
type: "element",
|
|
157
|
-
tagName: "img",
|
|
158
|
-
properties: {
|
|
159
|
-
className: ["ox-ogp-favicon"],
|
|
160
|
-
src: data.favicon,
|
|
161
|
-
alt: "",
|
|
162
|
-
loading: "lazy"
|
|
163
|
-
},
|
|
164
|
-
children: []
|
|
165
|
-
});
|
|
166
|
-
metaChildren.push({
|
|
167
|
-
type: "element",
|
|
168
|
-
tagName: "span",
|
|
169
|
-
properties: { className: ["ox-ogp-domain"] },
|
|
170
|
-
children: [{
|
|
171
|
-
type: "text",
|
|
172
|
-
value: data.siteName || extractDomain(data.url)
|
|
173
|
-
}]
|
|
174
|
-
});
|
|
175
|
-
contentChildren.push({
|
|
176
|
-
type: "element",
|
|
177
|
-
tagName: "div",
|
|
178
|
-
properties: { className: ["ox-ogp-meta"] },
|
|
179
|
-
children: metaChildren
|
|
180
|
-
});
|
|
181
|
-
children.push({
|
|
182
|
-
type: "element",
|
|
183
|
-
tagName: "div",
|
|
184
|
-
properties: { className: ["ox-ogp-content"] },
|
|
185
|
-
children: contentChildren
|
|
186
|
-
});
|
|
187
|
-
if (data.image) children.push({
|
|
188
|
-
type: "element",
|
|
189
|
-
tagName: "img",
|
|
190
|
-
properties: {
|
|
191
|
-
className: ["ox-ogp-image"],
|
|
192
|
-
src: data.image,
|
|
193
|
-
alt: "",
|
|
194
|
-
loading: "lazy"
|
|
195
|
-
},
|
|
196
|
-
children: []
|
|
197
|
-
});
|
|
198
|
-
return {
|
|
199
|
-
type: "element",
|
|
200
|
-
tagName: "a",
|
|
201
|
-
properties: {
|
|
202
|
-
className: ["ox-ogp-card"],
|
|
203
|
-
href: isSafeOgpUrl(data.url) ? data.url : "#",
|
|
204
|
-
target: "_blank",
|
|
205
|
-
rel: "noopener noreferrer"
|
|
206
|
-
},
|
|
207
|
-
children
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Create fallback element when OGP data is unavailable.
|
|
212
|
-
*/
|
|
213
|
-
function createFallbackCard(url) {
|
|
214
|
-
return {
|
|
215
|
-
type: "element",
|
|
216
|
-
tagName: "a",
|
|
217
|
-
properties: {
|
|
218
|
-
className: ["ox-ogp-simple"],
|
|
219
|
-
href: isSafeOgpUrl(url) ? url : "#",
|
|
220
|
-
target: "_blank",
|
|
221
|
-
rel: "noopener noreferrer"
|
|
222
|
-
},
|
|
223
|
-
children: [{
|
|
224
|
-
type: "element",
|
|
225
|
-
tagName: "svg",
|
|
226
|
-
properties: {
|
|
227
|
-
viewBox: "0 0 24 24",
|
|
228
|
-
fill: "none",
|
|
229
|
-
stroke: "currentColor",
|
|
230
|
-
"stroke-width": "2"
|
|
231
|
-
},
|
|
232
|
-
children: [{
|
|
233
|
-
type: "element",
|
|
234
|
-
tagName: "path",
|
|
235
|
-
properties: { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3" },
|
|
236
|
-
children: []
|
|
237
|
-
}]
|
|
238
|
-
}, {
|
|
239
|
-
type: "text",
|
|
240
|
-
value: extractDomain(url)
|
|
241
|
-
}]
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Collect all OGP URLs from HTML for pre-fetching.
|
|
246
|
-
*/
|
|
247
|
-
async function collectOgpUrls(html) {
|
|
248
|
-
const urls = [];
|
|
249
|
-
const urlPattern = /<ogcard[^>]*\s+url=["']([^"']+)["']/gi;
|
|
250
|
-
let match;
|
|
251
|
-
while ((match = urlPattern.exec(html)) !== null) if (isSafeOgpUrl(match[1])) urls.push(match[1]);
|
|
252
|
-
return urls;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Pre-fetch all OGP data.
|
|
256
|
-
*/
|
|
257
|
-
async function prefetchOgpData(urls, options) {
|
|
258
|
-
const mergedOptions = {
|
|
259
|
-
...defaultOptions,
|
|
260
|
-
...options
|
|
261
|
-
};
|
|
262
|
-
const results = /* @__PURE__ */ new Map();
|
|
263
|
-
await Promise.all(urls.map(async (url) => {
|
|
264
|
-
const data = await fetchOgpData(url, mergedOptions);
|
|
265
|
-
results.set(url, data);
|
|
266
|
-
}));
|
|
267
|
-
return results;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Rehype plugin to transform OgCard components.
|
|
271
|
-
*/
|
|
272
|
-
function rehypeOgp(ogpDataMap) {
|
|
273
|
-
return (tree) => {
|
|
274
|
-
const visit = (node) => {
|
|
275
|
-
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
276
|
-
const child = node.children[i];
|
|
277
|
-
if (child.type === "element") if (child.tagName.toLowerCase() === "ogcard") {
|
|
278
|
-
const url = getAttribute(child, "url");
|
|
279
|
-
if (url) {
|
|
280
|
-
const ogpData = ogpDataMap.get(url);
|
|
281
|
-
const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);
|
|
282
|
-
node.children[i] = cardElement;
|
|
283
|
-
}
|
|
284
|
-
} else visit(child);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
visit(tree);
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Transform OgCard components in HTML.
|
|
292
|
-
*/
|
|
293
|
-
async function transformOgp(html, ogpDataMap, options) {
|
|
294
|
-
let dataMap = ogpDataMap;
|
|
295
|
-
if (!dataMap) dataMap = await prefetchOgpData(await collectOgpUrls(html), options);
|
|
296
|
-
const result = await unified().use(rehypeParse, { fragment: true }).use(rehypeOgp, dataMap).use(rehypeStringify).process(html);
|
|
297
|
-
return String(result);
|
|
298
|
-
}
|
|
299
|
-
//#endregion
|
|
300
|
-
export { transformOgp as a, prefetchOgpData as i, fetchOgpData as n, isSafeOgpUrl as r, collectOgpUrls as t };
|
|
301
|
-
|
|
302
|
-
//# sourceMappingURL=ogp2.mjs.map
|
package/dist/ogp2.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ogp2.mjs","names":[],"sources":["../src/plugins/ogp.ts"],"sourcesContent":["/**\n * OGP Card Plugin - Link card embedding\n *\n * Transforms <OgCard> components into static link preview cards\n * by fetching OGP metadata at build time.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParsePlugin from \"rehype-parse\";\nimport rehypeStringifyPlugin from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\nimport { interopDefault } from \"../interop\";\n\n// ESM-only plugins are double-wrapped by the CommonJS interop; unwrap. See #452.\nconst rehypeParse = interopDefault(rehypeParsePlugin);\nconst rehypeStringify = interopDefault(rehypeStringifyPlugin);\n\nexport interface OgpData {\n url: string;\n title: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n}\n\nexport interface OgpOptions {\n /**\n * Request timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Cache fetched Open Graph metadata in memory for the current process.\n * @default true\n */\n cache?: boolean;\n\n /**\n * Cache TTL in milliseconds.\n * @default 3600000\n */\n cacheTTL?: number;\n\n /**\n * User agent sent with metadata fetch requests.\n * @default 'ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)'\n */\n userAgent?: string;\n}\n\nconst defaultOptions: Required<OgpOptions> = {\n timeout: 10000,\n cache: true,\n cacheTTL: 3600000,\n userAgent: \"ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)\",\n};\n\n// Simple in-memory cache\nconst ogpCache = new Map<string, { data: OgpData; timestamp: number }>();\n\nfunction isPrivateIPv4(hostname: string): boolean {\n const parts = hostname.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return false;\n }\n const [a, b] = parts;\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254)\n );\n}\n\nexport function isSafeOgpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n const host = url.hostname.toLowerCase();\n const ipv6 = host.replace(/^\\[|\\]$/g, \"\");\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n if (\n ipv6.includes(\":\") &&\n (ipv6 === \"::1\" || ipv6.startsWith(\"fc\") || ipv6.startsWith(\"fd\") || ipv6.startsWith(\"fe80\"))\n )\n return false;\n return !isPrivateIPv4(host);\n } catch {\n return false;\n }\n}\n\n/**\n * Get element attribute value.\n */\nfunction getAttribute(el: Element, name: string): string | undefined {\n const value = el.properties?.[name];\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) return value.join(\" \");\n return undefined;\n}\n\n/**\n * Extract domain from URL.\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Get favicon URL for a domain.\n */\nfunction getFaviconUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use Google's favicon service as fallback\n return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32`;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Parse OGP metadata from HTML.\n */\nfunction parseOgpFromHtml(html: string, url: string): OgpData {\n const result: OgpData = {\n url,\n title: \"\",\n };\n\n // Extract title\n const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n const ogTitleMatch =\n html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:title[\"']/i);\n\n result.title = ogTitleMatch?.[1] || titleMatch?.[1] || extractDomain(url);\n\n // Extract description\n const descMatch =\n html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:description[\"']/i) ||\n html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n\n if (descMatch) {\n result.description = descMatch[1];\n }\n\n // Extract image\n const imageMatch =\n html.match(/<meta[^>]*property=[\"']og:image[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:image[\"']/i);\n\n if (imageMatch) {\n let imageUrl = imageMatch[1];\n // Handle relative URLs\n if (imageUrl.startsWith(\"/\")) {\n try {\n const urlObj = new URL(url);\n imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;\n } catch {\n // Keep as is\n }\n }\n result.image = imageUrl;\n }\n\n // Extract site name\n const siteNameMatch =\n html.match(/<meta[^>]*property=[\"']og:site_name[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:site_name[\"']/i);\n\n if (siteNameMatch) {\n result.siteName = siteNameMatch[1];\n }\n\n // Get favicon\n result.favicon = getFaviconUrl(url);\n\n return result;\n}\n\n/**\n * Fetch OGP data for a URL.\n */\nexport async function fetchOgpData(\n url: string,\n options: Required<OgpOptions>,\n): Promise<OgpData | null> {\n if (!isSafeOgpUrl(url)) {\n return null;\n }\n\n // Check cache\n if (options.cache) {\n const cached = ogpCache.get(url);\n if (cached && Date.now() - cached.timestamp < options.cacheTTL) {\n return cached.data;\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": options.userAgent,\n Accept: \"text/html,application/xhtml+xml\",\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);\n return null;\n }\n\n const html = await response.text();\n const data = parseOgpFromHtml(html, url);\n\n // Cache the result\n if (options.cache) {\n ogpCache.set(url, { data, timestamp: Date.now() });\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n console.warn(`Timeout fetching OGP for ${url}`);\n } else {\n console.warn(`Error fetching OGP for ${url}:`, error);\n }\n return null;\n }\n}\n\n/**\n * Create OGP card element.\n */\nfunction createOgpCard(data: OgpData): Element {\n const children: Element[\"children\"] = [];\n\n // Content section\n const contentChildren: Element[\"children\"] = [];\n\n // Title\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-title\"] },\n children: [{ type: \"text\", value: data.title }],\n });\n\n // Description\n if (data.description) {\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-description\"] },\n children: [{ type: \"text\", value: data.description }],\n });\n }\n\n // Meta (favicon + domain)\n const metaChildren: Element[\"children\"] = [];\n\n if (data.favicon) {\n metaChildren.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-favicon\"],\n src: data.favicon,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n metaChildren.push({\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"ox-ogp-domain\"] },\n children: [{ type: \"text\", value: data.siteName || extractDomain(data.url) }],\n });\n\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-meta\"] },\n children: metaChildren,\n });\n\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-content\"] },\n children: contentChildren,\n });\n\n // Image\n if (data.image) {\n children.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-image\"],\n src: data.image,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-card\"],\n href: isSafeOgpUrl(data.url) ? data.url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children,\n };\n}\n\n/**\n * Create fallback element when OGP data is unavailable.\n */\nfunction createFallbackCard(url: string): Element {\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-simple\"],\n href: isSafeOgpUrl(url) ? url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"svg\",\n properties: {\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3\",\n },\n children: [],\n },\n ],\n },\n { type: \"text\", value: extractDomain(url) },\n ],\n };\n}\n\n/**\n * Collect all OGP URLs from HTML for pre-fetching.\n */\nexport async function collectOgpUrls(html: string): Promise<string[]> {\n const urls: string[] = [];\n const urlPattern = /<ogcard[^>]*\\s+url=[\"']([^\"']+)[\"']/gi;\n\n let match;\n while ((match = urlPattern.exec(html)) !== null) {\n if (isSafeOgpUrl(match[1])) {\n urls.push(match[1]);\n }\n }\n\n return urls;\n}\n\n/**\n * Pre-fetch all OGP data.\n */\nexport async function prefetchOgpData(\n urls: string[],\n options?: OgpOptions,\n): Promise<Map<string, OgpData | null>> {\n const mergedOptions = { ...defaultOptions, ...options };\n const results = new Map<string, OgpData | null>();\n\n await Promise.all(\n urls.map(async (url) => {\n const data = await fetchOgpData(url, mergedOptions);\n results.set(url, data);\n }),\n );\n\n return results;\n}\n\n/**\n * Rehype plugin to transform OgCard components.\n */\nfunction rehypeOgp(ogpDataMap: Map<string, OgpData | null>) {\n return (tree: Root) => {\n const visit = (node: Root | Element) => {\n if (\"children\" in node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n\n if (child.type === \"element\") {\n // Check for <OgCard> component\n if (child.tagName.toLowerCase() === \"ogcard\") {\n const url = getAttribute(child, \"url\");\n\n if (url) {\n const ogpData = ogpDataMap.get(url);\n const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);\n node.children[i] = cardElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform OgCard components in HTML.\n */\nexport async function transformOgp(\n html: string,\n ogpDataMap?: Map<string, OgpData | null>,\n options?: OgpOptions,\n): Promise<string> {\n // If no pre-fetched data, collect and fetch\n let dataMap = ogpDataMap;\n if (!dataMap) {\n const urls = await collectOgpUrls(html);\n dataMap = await prefetchOgpData(urls, options);\n }\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeOgp, dataMap)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":"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"}
|
package/dist/pm.cjs
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
const require_chunk = require("./chunk.cjs");
|
|
2
|
-
const require_napi = require("./napi.cjs");
|
|
3
|
-
const require_tabs = require("./tabs.cjs");
|
|
4
|
-
//#region src/plugins/pm.ts
|
|
5
|
-
/**
|
|
6
|
-
* Package Manager Tabs Plugin
|
|
7
|
-
*
|
|
8
|
-
* Transforms <pm>npm install …</pm> blocks into a tab group with one tab per
|
|
9
|
-
* package manager (npm/pnpm/yarn/bun). The single npm-style command is converted
|
|
10
|
-
* to each package manager's equivalent natively in Rust (`transformPmEmbeds` in
|
|
11
|
-
* @ox-content/napi), and the result reuses the same `ox-tabs` widget markup as
|
|
12
|
-
* the generic `<tabs>` plugin so styling and keyboard navigation are consistent.
|
|
13
|
-
*
|
|
14
|
-
* Syncing is opt-in (off by default): when enabled, the rendered group carries a
|
|
15
|
-
* `data-ox-tab-group="pkg-manager"` attribute so the client runtime can keep
|
|
16
|
-
* every package-manager group on the page in sync via localStorage.
|
|
17
|
-
*
|
|
18
|
-
* Package-manager groups share the tab-group counter with the `<tabs>` plugin so
|
|
19
|
-
* `data-group` ids (and the CSS produced by `generateTabsCSS`) stay unique.
|
|
20
|
-
*/
|
|
21
|
-
var pm_exports = /* @__PURE__ */ require_chunk.__exportAll({ transformPm: () => transformPm });
|
|
22
|
-
/**
|
|
23
|
-
* Transform `<pm>` package-manager blocks in HTML into install tabs.
|
|
24
|
-
*
|
|
25
|
-
* @param html - Rendered HTML potentially containing `<pm>` blocks.
|
|
26
|
-
* @param options - Package-manager tab options (syncing is opt-in).
|
|
27
|
-
* @returns The rewritten HTML.
|
|
28
|
-
*/
|
|
29
|
-
async function transformPm(html, options) {
|
|
30
|
-
if (!/<pm[\s/>]/i.test(html)) return html;
|
|
31
|
-
const mod = await require_napi.importNapiModule();
|
|
32
|
-
const startGroup = require_tabs.getTabGroupCounter();
|
|
33
|
-
const result = mod.transformPmEmbeds(html, startGroup, { sync: options?.sync ?? false });
|
|
34
|
-
require_tabs.setTabGroupCounter(startGroup + result.groupCount);
|
|
35
|
-
return result.html;
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
|
-
Object.defineProperty(exports, "pm_exports", {
|
|
39
|
-
enumerable: true,
|
|
40
|
-
get: function() {
|
|
41
|
-
return pm_exports;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
//# sourceMappingURL=pm.cjs.map
|
package/dist/pm.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pm.cjs","names":["importNapiModule","getTabGroupCounter"],"sources":["../src/plugins/pm.ts"],"sourcesContent":["/**\n * Package Manager Tabs Plugin\n *\n * Transforms <pm>npm install …</pm> blocks into a tab group with one tab per\n * package manager (npm/pnpm/yarn/bun). The single npm-style command is converted\n * to each package manager's equivalent natively in Rust (`transformPmEmbeds` in\n * @ox-content/napi), and the result reuses the same `ox-tabs` widget markup as\n * the generic `<tabs>` plugin so styling and keyboard navigation are consistent.\n *\n * Syncing is opt-in (off by default): when enabled, the rendered group carries a\n * `data-ox-tab-group=\"pkg-manager\"` attribute so the client runtime can keep\n * every package-manager group on the page in sync via localStorage.\n *\n * Package-manager groups share the tab-group counter with the `<tabs>` plugin so\n * `data-group` ids (and the CSS produced by `generateTabsCSS`) stay unique.\n */\n\nimport { importNapiModule } from \"../napi\";\nimport { getTabGroupCounter, setTabGroupCounter } from \"./tabs\";\n\n/** Options for {@link transformPm}. */\nexport interface PmOptions {\n /**\n * Enable opt-in synced package-manager tab groups. When `true`, a\n * `data-ox-tab-group=\"pkg-manager\"` attribute is emitted so the client runtime\n * syncs the active package manager across every pm group on the page and\n * persists the choice in localStorage.\n * @default false\n */\n sync?: boolean;\n}\n\n/**\n * Transform `<pm>` package-manager blocks in HTML into install tabs.\n *\n * @param html - Rendered HTML potentially containing `<pm>` blocks.\n * @param options - Package-manager tab options (syncing is opt-in).\n * @returns The rewritten HTML.\n */\nexport async function transformPm(html: string, options?: PmOptions): Promise<string> {\n // Cheap marker check: skip the NAPI call entirely when there's no `<pm>`\n // element. The Rust side guards the same way, but short-circuiting here avoids\n // marshalling the whole document across the boundary.\n if (!/<pm[\\s/>]/i.test(html)) {\n return html;\n }\n\n const mod = await importNapiModule();\n const startGroup = getTabGroupCounter();\n const result = mod.transformPmEmbeds(html, startGroup, {\n sync: options?.sync ?? false,\n });\n setTabGroupCounter(startGroup + result.groupCount);\n return result.html;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,eAAsB,YAAY,MAAc,SAAsC;CAIpF,IAAI,CAAC,aAAa,KAAK,KAAK,EAC1B,OAAO;CAGT,MAAM,MAAM,MAAMA,aAAAA,kBAAkB;CACpC,MAAM,aAAaC,aAAAA,oBAAoB;CACvC,MAAM,SAAS,IAAI,kBAAkB,MAAM,YAAY,EACrD,MAAM,SAAS,QAAQ,OACxB,CAAC;CACF,aAAA,mBAAmB,aAAa,OAAO,WAAW;CAClD,OAAO,OAAO"}
|
package/dist/pm.mjs
DELETED
package/dist/pm2.mjs
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { t as importNapiModule } from "./napi.mjs";
|
|
2
|
-
import { i as setTabGroupCounter, n as getTabGroupCounter } from "./tabs2.mjs";
|
|
3
|
-
//#region src/plugins/pm.ts
|
|
4
|
-
/**
|
|
5
|
-
* Package Manager Tabs Plugin
|
|
6
|
-
*
|
|
7
|
-
* Transforms <pm>npm install …</pm> blocks into a tab group with one tab per
|
|
8
|
-
* package manager (npm/pnpm/yarn/bun). The single npm-style command is converted
|
|
9
|
-
* to each package manager's equivalent natively in Rust (`transformPmEmbeds` in
|
|
10
|
-
* @ox-content/napi), and the result reuses the same `ox-tabs` widget markup as
|
|
11
|
-
* the generic `<tabs>` plugin so styling and keyboard navigation are consistent.
|
|
12
|
-
*
|
|
13
|
-
* Syncing is opt-in (off by default): when enabled, the rendered group carries a
|
|
14
|
-
* `data-ox-tab-group="pkg-manager"` attribute so the client runtime can keep
|
|
15
|
-
* every package-manager group on the page in sync via localStorage.
|
|
16
|
-
*
|
|
17
|
-
* Package-manager groups share the tab-group counter with the `<tabs>` plugin so
|
|
18
|
-
* `data-group` ids (and the CSS produced by `generateTabsCSS`) stay unique.
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Transform `<pm>` package-manager blocks in HTML into install tabs.
|
|
22
|
-
*
|
|
23
|
-
* @param html - Rendered HTML potentially containing `<pm>` blocks.
|
|
24
|
-
* @param options - Package-manager tab options (syncing is opt-in).
|
|
25
|
-
* @returns The rewritten HTML.
|
|
26
|
-
*/
|
|
27
|
-
async function transformPm(html, options) {
|
|
28
|
-
if (!/<pm[\s/>]/i.test(html)) return html;
|
|
29
|
-
const mod = await importNapiModule();
|
|
30
|
-
const startGroup = getTabGroupCounter();
|
|
31
|
-
const result = mod.transformPmEmbeds(html, startGroup, { sync: options?.sync ?? false });
|
|
32
|
-
setTabGroupCounter(startGroup + result.groupCount);
|
|
33
|
-
return result.html;
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
|
-
export { transformPm as t };
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=pm2.mjs.map
|
package/dist/pm2.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pm2.mjs","names":[],"sources":["../src/plugins/pm.ts"],"sourcesContent":["/**\n * Package Manager Tabs Plugin\n *\n * Transforms <pm>npm install …</pm> blocks into a tab group with one tab per\n * package manager (npm/pnpm/yarn/bun). The single npm-style command is converted\n * to each package manager's equivalent natively in Rust (`transformPmEmbeds` in\n * @ox-content/napi), and the result reuses the same `ox-tabs` widget markup as\n * the generic `<tabs>` plugin so styling and keyboard navigation are consistent.\n *\n * Syncing is opt-in (off by default): when enabled, the rendered group carries a\n * `data-ox-tab-group=\"pkg-manager\"` attribute so the client runtime can keep\n * every package-manager group on the page in sync via localStorage.\n *\n * Package-manager groups share the tab-group counter with the `<tabs>` plugin so\n * `data-group` ids (and the CSS produced by `generateTabsCSS`) stay unique.\n */\n\nimport { importNapiModule } from \"../napi\";\nimport { getTabGroupCounter, setTabGroupCounter } from \"./tabs\";\n\n/** Options for {@link transformPm}. */\nexport interface PmOptions {\n /**\n * Enable opt-in synced package-manager tab groups. When `true`, a\n * `data-ox-tab-group=\"pkg-manager\"` attribute is emitted so the client runtime\n * syncs the active package manager across every pm group on the page and\n * persists the choice in localStorage.\n * @default false\n */\n sync?: boolean;\n}\n\n/**\n * Transform `<pm>` package-manager blocks in HTML into install tabs.\n *\n * @param html - Rendered HTML potentially containing `<pm>` blocks.\n * @param options - Package-manager tab options (syncing is opt-in).\n * @returns The rewritten HTML.\n */\nexport async function transformPm(html: string, options?: PmOptions): Promise<string> {\n // Cheap marker check: skip the NAPI call entirely when there's no `<pm>`\n // element. The Rust side guards the same way, but short-circuiting here avoids\n // marshalling the whole document across the boundary.\n if (!/<pm[\\s/>]/i.test(html)) {\n return html;\n }\n\n const mod = await importNapiModule();\n const startGroup = getTabGroupCounter();\n const result = mod.transformPmEmbeds(html, startGroup, {\n sync: options?.sync ?? false,\n });\n setTabGroupCounter(startGroup + result.groupCount);\n return result.html;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,eAAsB,YAAY,MAAc,SAAsC;CAIpF,IAAI,CAAC,aAAa,KAAK,KAAK,EAC1B,OAAO;CAGT,MAAM,MAAM,MAAM,kBAAkB;CACpC,MAAM,aAAa,oBAAoB;CACvC,MAAM,SAAS,IAAI,kBAAkB,MAAM,YAAY,EACrD,MAAM,SAAS,QAAQ,OACxB,CAAC;CACF,mBAAmB,aAAa,OAAO,WAAW;CAClD,OAAO,OAAO"}
|
package/dist/tabs2.mjs
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { t as importNapiModule } from "./napi.mjs";
|
|
2
|
-
//#region src/plugins/tabs.ts
|
|
3
|
-
/**
|
|
4
|
-
* Tabs Plugin - Pure CSS implementation
|
|
5
|
-
*
|
|
6
|
-
* Transforms <Tabs>/<Tab> components into accessible HTML with CSS :has()
|
|
7
|
-
* based tab switching (no JavaScript required).
|
|
8
|
-
*
|
|
9
|
-
* The HTML rewrite is performed in Rust (`transformTabsEmbeds` in
|
|
10
|
-
* @ox-content/napi), replacing the previous rehype parse/stringify round-trip.
|
|
11
|
-
* The per-build group counter (consumed by `generateTabsCSS`) stays here and is
|
|
12
|
-
* advanced by the number of groups the Rust transform reports, so CSS
|
|
13
|
-
* generation still covers exactly the groups that were emitted.
|
|
14
|
-
*/
|
|
15
|
-
let tabGroupCounter = 0;
|
|
16
|
-
/**
|
|
17
|
-
* Reset tab group counter (for testing and per dev-server request).
|
|
18
|
-
*/
|
|
19
|
-
function resetTabGroupCounter() {
|
|
20
|
-
tabGroupCounter = 0;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Read the shared tab group counter. Other tab-like transforms (such as the
|
|
24
|
-
* package-manager tabs) share this counter so `data-group` ids and the CSS
|
|
25
|
-
* generated by {@link generateTabsCSS} stay unique across a page.
|
|
26
|
-
*/
|
|
27
|
-
function getTabGroupCounter() {
|
|
28
|
-
return tabGroupCounter;
|
|
29
|
-
}
|
|
30
|
-
/** Set the shared tab group counter (advanced by other tab-like transforms). */
|
|
31
|
-
function setTabGroupCounter(value) {
|
|
32
|
-
tabGroupCounter = value;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Transform Tabs components in HTML.
|
|
36
|
-
*/
|
|
37
|
-
async function transformTabs(html) {
|
|
38
|
-
if (!/<tabs/i.test(html)) return html;
|
|
39
|
-
const result = (await importNapiModule()).transformTabsEmbeds(html, tabGroupCounter);
|
|
40
|
-
tabGroupCounter += result.groupCount;
|
|
41
|
-
return result.html;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Generate dynamic CSS for :has() based tab switching.
|
|
45
|
-
* This is needed because :has() selectors need unique IDs.
|
|
46
|
-
*/
|
|
47
|
-
function generateTabsCSS(groupCount) {
|
|
48
|
-
if (groupCount === 0) return "";
|
|
49
|
-
let css = "/* Dynamic Tabs CSS */\n";
|
|
50
|
-
for (let g = 0; g < groupCount; g++) for (let t = 0; t < 8; t++) css += `.ox-tabs[data-group="${g}"]:has(#ox-tab-${g}-${t}:checked) .ox-tab-panel[data-tab="${t}"] { display: block; }\n`;
|
|
51
|
-
return css;
|
|
52
|
-
}
|
|
53
|
-
//#endregion
|
|
54
|
-
export { transformTabs as a, setTabGroupCounter as i, getTabGroupCounter as n, resetTabGroupCounter as r, generateTabsCSS as t };
|
|
55
|
-
|
|
56
|
-
//# sourceMappingURL=tabs2.mjs.map
|
package/dist/tabs2.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tabs2.mjs","names":[],"sources":["../src/plugins/tabs.ts"],"sourcesContent":["/**\n * Tabs Plugin - Pure CSS implementation\n *\n * Transforms <Tabs>/<Tab> components into accessible HTML with CSS :has()\n * based tab switching (no JavaScript required).\n *\n * The HTML rewrite is performed in Rust (`transformTabsEmbeds` in\n * @ox-content/napi), replacing the previous rehype parse/stringify round-trip.\n * The per-build group counter (consumed by `generateTabsCSS`) stays here and is\n * advanced by the number of groups the Rust transform reports, so CSS\n * generation still covers exactly the groups that were emitted.\n */\n\nimport { importNapiModule } from \"../napi\";\n\nlet tabGroupCounter = 0;\n\n/**\n * Reset tab group counter (for testing and per dev-server request).\n */\nexport function resetTabGroupCounter(): void {\n tabGroupCounter = 0;\n}\n\n/**\n * Read the shared tab group counter. Other tab-like transforms (such as the\n * package-manager tabs) share this counter so `data-group` ids and the CSS\n * generated by {@link generateTabsCSS} stay unique across a page.\n */\nexport function getTabGroupCounter(): number {\n return tabGroupCounter;\n}\n\n/** Set the shared tab group counter (advanced by other tab-like transforms). */\nexport function setTabGroupCounter(value: number): void {\n tabGroupCounter = value;\n}\n\n/**\n * Transform Tabs components in HTML.\n */\nexport async function transformTabs(html: string): Promise<string> {\n // Cheap marker check: skip the NAPI call entirely when there's no `<tabs>`\n // element. The Rust side guards the same way, but short-circuiting here\n // avoids marshalling the whole document across the boundary.\n if (!/<tabs/i.test(html)) {\n return html;\n }\n\n const mod = await importNapiModule();\n const result = mod.transformTabsEmbeds(html, tabGroupCounter);\n tabGroupCounter += result.groupCount;\n return result.html;\n}\n\n/**\n * Generate dynamic CSS for :has() based tab switching.\n * This is needed because :has() selectors need unique IDs.\n */\nexport function generateTabsCSS(groupCount: number): string {\n if (groupCount === 0) return \"\";\n\n let css = \"/* Dynamic Tabs CSS */\\n\";\n\n for (let g = 0; g < groupCount; g++) {\n for (let t = 0; t < 8; t++) {\n css += `.ox-tabs[data-group=\"${g}\"]:has(#ox-tab-${g}-${t}:checked) .ox-tab-panel[data-tab=\"${t}\"] { display: block; }\\n`;\n }\n }\n\n return css;\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,IAAI,kBAAkB;;;;AAKtB,SAAgB,uBAA6B;CAC3C,kBAAkB;;;;;;;AAQpB,SAAgB,qBAA6B;CAC3C,OAAO;;;AAIT,SAAgB,mBAAmB,OAAqB;CACtD,kBAAkB;;;;;AAMpB,eAAsB,cAAc,MAA+B;CAIjE,IAAI,CAAC,SAAS,KAAK,KAAK,EACtB,OAAO;CAIT,MAAM,UAAS,MADG,kBAAkB,EACjB,oBAAoB,MAAM,gBAAgB;CAC7D,mBAAmB,OAAO;CAC1B,OAAO,OAAO;;;;;;AAOhB,SAAgB,gBAAgB,YAA4B;CAC1D,IAAI,eAAe,GAAG,OAAO;CAE7B,IAAI,MAAM;CAEV,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,GAAG,EAAE,oCAAoC,EAAE;CAInG,OAAO"}
|
package/dist/youtube.cjs
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const require_chunk = require("./chunk.cjs");
|
|
2
|
-
const require_napi = require("./napi.cjs");
|
|
3
|
-
//#region src/plugins/youtube.ts
|
|
4
|
-
/**
|
|
5
|
-
* YouTube Plugin - Privacy-enhanced iframe embedding
|
|
6
|
-
*
|
|
7
|
-
* Transforms <YouTube> components into responsive iframe embeds using
|
|
8
|
-
* youtube-nocookie.com for enhanced privacy.
|
|
9
|
-
*
|
|
10
|
-
* The HTML rewrite is performed in Rust (`transformYoutubeEmbeds` in
|
|
11
|
-
* @ox-content/napi), replacing the previous rehype parse/stringify
|
|
12
|
-
* round-trip. This module keeps the public TS surface and a cheap marker
|
|
13
|
-
* check so pages without a `<youtube>` element never cross the NAPI boundary.
|
|
14
|
-
*/
|
|
15
|
-
var youtube_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
16
|
-
extractVideoId: () => extractVideoId,
|
|
17
|
-
transformYouTube: () => transformYouTube
|
|
18
|
-
});
|
|
19
|
-
/**
|
|
20
|
-
* Extract YouTube video ID from various URL formats.
|
|
21
|
-
*/
|
|
22
|
-
function extractVideoId(input) {
|
|
23
|
-
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
|
|
24
|
-
for (const pattern of [/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/)([a-zA-Z0-9_-]{11})/, /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/]) {
|
|
25
|
-
const match = input.match(pattern);
|
|
26
|
-
if (match) return match[1];
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Transform YouTube components in HTML.
|
|
32
|
-
*/
|
|
33
|
-
async function transformYouTube(html, options) {
|
|
34
|
-
if (!/<youtube/i.test(html)) return html;
|
|
35
|
-
return (await require_napi.importNapiModule()).transformYoutubeEmbeds(html, options);
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
|
-
Object.defineProperty(exports, "extractVideoId", {
|
|
39
|
-
enumerable: true,
|
|
40
|
-
get: function() {
|
|
41
|
-
return extractVideoId;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
Object.defineProperty(exports, "transformYouTube", {
|
|
45
|
-
enumerable: true,
|
|
46
|
-
get: function() {
|
|
47
|
-
return transformYouTube;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
Object.defineProperty(exports, "youtube_exports", {
|
|
51
|
-
enumerable: true,
|
|
52
|
-
get: function() {
|
|
53
|
-
return youtube_exports;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
//# sourceMappingURL=youtube.cjs.map
|
package/dist/youtube.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"youtube.cjs","names":["importNapiModule"],"sources":["../src/plugins/youtube.ts"],"sourcesContent":["/**\n * YouTube Plugin - Privacy-enhanced iframe embedding\n *\n * Transforms <YouTube> components into responsive iframe embeds using\n * youtube-nocookie.com for enhanced privacy.\n *\n * The HTML rewrite is performed in Rust (`transformYoutubeEmbeds` in\n * @ox-content/napi), replacing the previous rehype parse/stringify\n * round-trip. This module keeps the public TS surface and a cheap marker\n * check so pages without a `<youtube>` element never cross the NAPI boundary.\n */\n\nimport { importNapiModule } from \"../napi\";\n\nexport interface YouTubeOptions {\n /**\n * Use privacy-enhanced mode (`youtube-nocookie.com`).\n * @default true\n */\n privacyEnhanced?: boolean;\n\n /**\n * Default iframe aspect ratio.\n * @default '16/9'\n */\n aspectRatio?: string;\n\n /**\n * Allow fullscreen playback.\n * @default true\n */\n allowFullscreen?: boolean;\n\n /**\n * Lazy load the iframe.\n * @default true\n */\n lazyLoad?: boolean;\n}\n\n/**\n * Extract YouTube video ID from various URL formats.\n */\nexport function extractVideoId(input: string): string | null {\n // Already a video ID (11 characters, alphanumeric + _ -)\n if (/^[a-zA-Z0-9_-]{11}$/.test(input)) {\n return input;\n }\n\n // Full URL patterns\n const patterns = [\n /(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/|youtube\\.com\\/v\\/)([a-zA-Z0-9_-]{11})/,\n /youtube\\.com\\/shorts\\/([a-zA-Z0-9_-]{11})/,\n ];\n\n for (const pattern of patterns) {\n const match = input.match(pattern);\n if (match) return match[1];\n }\n\n return null;\n}\n\n/**\n * Transform YouTube components in HTML.\n */\nexport async function transformYouTube(html: string, options?: YouTubeOptions): Promise<string> {\n // Cheap marker check: skip the NAPI call entirely when there's no\n // `<youtube>` element (the common case). The Rust side guards the same way,\n // but short-circuiting here avoids marshalling the whole document across\n // the boundary.\n if (!/<youtube/i.test(html)) {\n return html;\n }\n\n const mod = await importNapiModule();\n return mod.transformYoutubeEmbeds(html, options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,eAAe,OAA8B;CAE3D,IAAI,sBAAsB,KAAK,MAAM,EACnC,OAAO;CAST,KAAK,MAAM,WAAW,CAJpB,sGACA,4CAG4B,EAAE;EAC9B,MAAM,QAAQ,MAAM,MAAM,QAAQ;EAClC,IAAI,OAAO,OAAO,MAAM;;CAG1B,OAAO;;;;;AAMT,eAAsB,iBAAiB,MAAc,SAA2C;CAK9F,IAAI,CAAC,YAAY,KAAK,KAAK,EACzB,OAAO;CAIT,QAAO,MADWA,aAAAA,kBAAkB,EACzB,uBAAuB,MAAM,QAAQ"}
|
package/dist/youtube.mjs
DELETED
package/dist/youtube2.mjs
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { t as importNapiModule } from "./napi.mjs";
|
|
2
|
-
//#region src/plugins/youtube.ts
|
|
3
|
-
/**
|
|
4
|
-
* YouTube Plugin - Privacy-enhanced iframe embedding
|
|
5
|
-
*
|
|
6
|
-
* Transforms <YouTube> components into responsive iframe embeds using
|
|
7
|
-
* youtube-nocookie.com for enhanced privacy.
|
|
8
|
-
*
|
|
9
|
-
* The HTML rewrite is performed in Rust (`transformYoutubeEmbeds` in
|
|
10
|
-
* @ox-content/napi), replacing the previous rehype parse/stringify
|
|
11
|
-
* round-trip. This module keeps the public TS surface and a cheap marker
|
|
12
|
-
* check so pages without a `<youtube>` element never cross the NAPI boundary.
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* Extract YouTube video ID from various URL formats.
|
|
16
|
-
*/
|
|
17
|
-
function extractVideoId(input) {
|
|
18
|
-
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
|
|
19
|
-
for (const pattern of [/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/)([a-zA-Z0-9_-]{11})/, /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/]) {
|
|
20
|
-
const match = input.match(pattern);
|
|
21
|
-
if (match) return match[1];
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Transform YouTube components in HTML.
|
|
27
|
-
*/
|
|
28
|
-
async function transformYouTube(html, options) {
|
|
29
|
-
if (!/<youtube/i.test(html)) return html;
|
|
30
|
-
return (await importNapiModule()).transformYoutubeEmbeds(html, options);
|
|
31
|
-
}
|
|
32
|
-
//#endregion
|
|
33
|
-
export { transformYouTube as n, extractVideoId as t };
|
|
34
|
-
|
|
35
|
-
//# sourceMappingURL=youtube2.mjs.map
|
package/dist/youtube2.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"youtube2.mjs","names":[],"sources":["../src/plugins/youtube.ts"],"sourcesContent":["/**\n * YouTube Plugin - Privacy-enhanced iframe embedding\n *\n * Transforms <YouTube> components into responsive iframe embeds using\n * youtube-nocookie.com for enhanced privacy.\n *\n * The HTML rewrite is performed in Rust (`transformYoutubeEmbeds` in\n * @ox-content/napi), replacing the previous rehype parse/stringify\n * round-trip. This module keeps the public TS surface and a cheap marker\n * check so pages without a `<youtube>` element never cross the NAPI boundary.\n */\n\nimport { importNapiModule } from \"../napi\";\n\nexport interface YouTubeOptions {\n /**\n * Use privacy-enhanced mode (`youtube-nocookie.com`).\n * @default true\n */\n privacyEnhanced?: boolean;\n\n /**\n * Default iframe aspect ratio.\n * @default '16/9'\n */\n aspectRatio?: string;\n\n /**\n * Allow fullscreen playback.\n * @default true\n */\n allowFullscreen?: boolean;\n\n /**\n * Lazy load the iframe.\n * @default true\n */\n lazyLoad?: boolean;\n}\n\n/**\n * Extract YouTube video ID from various URL formats.\n */\nexport function extractVideoId(input: string): string | null {\n // Already a video ID (11 characters, alphanumeric + _ -)\n if (/^[a-zA-Z0-9_-]{11}$/.test(input)) {\n return input;\n }\n\n // Full URL patterns\n const patterns = [\n /(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/|youtube\\.com\\/v\\/)([a-zA-Z0-9_-]{11})/,\n /youtube\\.com\\/shorts\\/([a-zA-Z0-9_-]{11})/,\n ];\n\n for (const pattern of patterns) {\n const match = input.match(pattern);\n if (match) return match[1];\n }\n\n return null;\n}\n\n/**\n * Transform YouTube components in HTML.\n */\nexport async function transformYouTube(html: string, options?: YouTubeOptions): Promise<string> {\n // Cheap marker check: skip the NAPI call entirely when there's no\n // `<youtube>` element (the common case). The Rust side guards the same way,\n // but short-circuiting here avoids marshalling the whole document across\n // the boundary.\n if (!/<youtube/i.test(html)) {\n return html;\n }\n\n const mod = await importNapiModule();\n return mod.transformYoutubeEmbeds(html, options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2CA,SAAgB,eAAe,OAA8B;CAE3D,IAAI,sBAAsB,KAAK,MAAM,EACnC,OAAO;CAST,KAAK,MAAM,WAAW,CAJpB,sGACA,4CAG4B,EAAE;EAC9B,MAAM,QAAQ,MAAM,MAAM,QAAQ;EAClC,IAAI,OAAO,OAAO,MAAM;;CAG1B,OAAO;;;;;AAMT,eAAsB,iBAAiB,MAAc,SAA2C;CAK9F,IAAI,CAAC,YAAY,KAAK,KAAK,EACzB,OAAO;CAIT,QAAO,MADW,kBAAkB,EACzB,uBAAuB,MAAM,QAAQ"}
|